net mvc asp asp.net asp.net-mvc asp.net-mvc-3 architecture

asp.net - mvc - @model razor



Modelos, ViewModels, DTOs en aplicaciĆ³n MVC 3 (3)

introduzca ViewModels que viven en el proyecto WebUI y exponga IQueryables y el contexto de datos EF del servicio al proyecto WebUI. Entonces podría proyectar directamente en esos ViewModels.

El problema con esto es que pronto se encontrará con problemas al utilizar EF al intentar "aplanar" los modelos. Encontré algo similar cuando tuve una clase CommentViewModel que se veía así:

public class CommentViewModel { public string Content { get; set; } public string DateCreated { get; set; } }

La siguiente proyección de consulta EF4 para CommentViewModel no funcionaría, ya que no pudo traducir el método ToString () a SQL :

var comments = from c in DbSet where c.PostId == postId select new CommentViewModel() { Content = c.Content, DateCreated = c.DateCreated.ToShortTimeString() };

Usar algo como Automapper es una buena opción, especialmente si tiene muchas conversiones que hacer. Sin embargo, también puede crear sus propios convertidores que básicamente convierten su modelo de dominio a su modelo de vista. En mi caso, creé mis propios métodos de extensión para convertir mi modelo de dominio de Comment a mi CommentViewModel esta manera:

public static class ViewModelConverters { public static CommentViewModel ToCommentViewModel(this Comment comment) { return new CommentViewModel() { Content = comment.Content, DateCreated = comment.DateCreated.ToShortDateString() }; } public static IEnumerable<CommentViewModel> ToCommentViewModelList(this IEnumerable<Comment> comments) { List<CommentViewModel> commentModels = new List<CommentViewModel>(comments.Count()); foreach (var c in comments) { commentModels.Add(c.ToCommentViewModel()); } return commentModels; } }

Básicamente, lo que hago es realizar una consulta estándar de EF para recuperar un modelo de dominio y luego usar los métodos de extensión para convertir los resultados en un modelo de vista. Por ejemplo, los siguientes métodos ilustran el uso:

public Comment GetComment(int commentId) { return CommentRepository.GetById(commentId); } public CommentViewModel GetCommentViewModel(int commentId) { return CommentRepository.GetById(commentId).ToCommentViewModel(); } public IEnumerable<Comment> GetCommentsForPost(int postId) { return CommentRepository.GetCommentsForPost(postId); } public IEnumerable<CommentViewModel> GetCommentViewModelsForPost(int postId) { return CommentRepository.GetCommentsForPost(postId).ToCommentViewModelList(); }

Tengo una solución web (en VS2010) con dos subproyectos:

  1. Domain que contiene las clases de Model (asignadas a tablas de base de datos a través de Entity Framework) y Services que (además de otras cosas) son responsables de las operaciones de CRUD

  2. WebUI que hace referencia al proyecto de dominio

Para las primeras páginas que he creado, he usado las clases de modelo del proyecto de dominio directamente como modelo en mis vistas con tipos fuertes porque las clases eran pequeñas y quería mostrar y modificar todas las propiedades.

Ahora tengo una página que solo debería funcionar con una pequeña parte de todas las propiedades del modelo de dominio correspondiente. Recupero esas propiedades utilizando una proyección del resultado de la consulta en mi clase de Servicio. Pero necesito proyectar en un tipo , y aquí vienen mis preguntas sobre las soluciones que se me ocurren:

  1. ViewModels que viven en el proyecto WebUI y ViewModels y el EF data context del servicio al proyecto WebUI. Entonces podría proyectar directamente en esos ViewModels.

  2. Si no quiero exponer IQueryables y el contexto de datos de EF, coloco las clases de ViewModel en el proyecto de Domain , entonces puedo devolver los ViewModels directamente como resultado de las consultas y proyecciones de las clases de Servicio.

  3. Además de los ViewModels en el proyecto WebUI , introduzco Data transfer objects que mueven los datos de las consultas en las clases de servicio a los ViewModels .

Las soluciones 1 y 2 parecen la misma cantidad de trabajo y me inclino a preferir la solución 2 para mantener todas las inquietudes de la base de datos en un proyecto separado. Pero de alguna manera suena mal tener View -Models en el proyecto de dominio.

La Solución 3 parece mucho más trabajo, ya que tengo más clases para crear y para preocuparme por el mapeo Modelo-DTO-ViewModel. Tampoco entiendo cuál sería la diferencia entre los DTO y los ViewModels. ¿No son los ViewModels exactamente la colección de las propiedades seleccionadas de mi clase de modelo que quiero mostrar? ¿No contendrían los mismos miembros que los DTO? ¿Por qué querría diferenciar entre ViewModels y DTO?

¿Cuál de estas tres soluciones es preferible y cuáles son los beneficios y desventajas? ¿Hay otras opciones?

Gracias por sus comentarios de antemano!

Editar (porque tal vez tenía una pared de texto demasiado larga y me pidieron un código)

Ejemplo: Tengo una Entidad Customer ...

public class Customer { public int ID { get; set; } public string Name { get; set; } public City { get; set; } // ... and many more properties }

... y desea crear una Vista que solo muestre (y quizás permita editar) el Name de los clientes en una lista. En una clase de servicio, extraigo los datos que necesito para la vista a través de una proyección:

public class CustomerService { public List<SomeClass1> GetCustomerNameList() { using (var dbContext = new MyDbContext()) { return dbContext.Customers .Select(c => new SomeClass1 { ID = c.ID, Name = c.Name }) .ToList(); } } }

Luego hay un CustomerController con un método de acción. ¿Cómo debería ser esto?

De esta manera (a) ...

public ActionResult Index() { List<SomeClass1> list = _service.GetCustomerNameList(); return View(list); }

... o mejor así (b):

public ActionResult Index() { List<SomeClass1> list = _service.GetCustomerNameList(); List<SomeClass2> newList = CreateNewList(list); return View(newList); }

Con respecto a la opción 3 anterior, diría: SomeClass1 (vive en el proyecto de Domain ) es un DTO y SomeClass2 (vive en el proyecto de WebUI ) es un ViewModel .

Me pregunto si alguna vez tiene sentido distinguir las dos clases. ¿Por qué no siempre elegiría la opción (a) para la acción del controlador (porque es más fácil)? ¿Existen motivos para presentar el ViewModel ( SomeClass2 ) además del DTO ( SomeClass1 )?


Hablar de modelos, modelos de vista y DTO es confuso, personalmente no me gusta usar estos términos. Prefiero hablar sobre entidades de dominio , servicios de dominio , entrada / resultado de operación (también conocido como DTO). Todos estos tipos viven en la capa de dominio. Operaciones es el comportamiento de Entidades y Servicios. A menos que esté creando una aplicación CRUD pura, la capa de presentación solo trata con los tipos de Entrada / Resultado, no con Entidades. No necesita tipos de ViewModel adicionales, estos son los ViewModels (en otras palabras, el Modelo de la Vista). La Vista está ahí para traducir los Resultados de la Operación a HTML, pero el mismo Resultado podría ser serializado como XML o JSON. Lo que use como ViewModel es parte del dominio, no la capa de presentación.


Solucionaría su problema usando una herramienta de mapeo automático (como AutoMapper ) para hacer el mapeo por usted. En los casos en que la asignación es fácil (por ejemplo, si todas las propiedades de una clase deben asignarse a propiedades con el mismo nombre en otra clase), AutoMapper podrá hacer todo el trabajo de conexión por usted, y tendrá que indique un par de líneas de código para observar que debería haber un mapa entre los dos.

De esa manera, puede tener sus entidades en Domain , y un par de clases de modelos de vista en su WebUI , y en algún lugar (preferiblemente en WebUI o un espacio de nombres secundario del mismo) definir mapas entre ellas. Sus modelos de vista serán en efecto DTO, pero no tendrá que preocuparse mucho por el proceso de conversión entre el dominio y sus clases de DTO.

Nota: Recomiendo encarecidamente que no le den a sus entidades de dominio directamente las vistas de su interfaz de usuario web de MVC. No desea que EF se "quede pegado" hasta la capa frontal, en caso de que luego quiera usar algo que no sea EF.