2011-05-13 17 views
16

tengo una solución web (en VS2010) con dos subproyectos:Modelos, ViewModels, dtos en MVC 3 aplicación

  1. Domain que sostiene los Model clases (asignada a las tablas de bases de datos a través de Entity Framework) y Services la que (además de otras cosas) son responsables de las operaciones 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 fuertemente tipadas porque las clases eran pequeñas y quería mostrar y modificar todas las propiedades de.

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 proyecto en un tipo - y aquí vienen mis preguntas acerca de las soluciones que se me ocurre:

  1. presento ViewModels cuales viven en el proyecto WebUI y exponer IQueryables y la EF data context desde el servicio de el proyecto WebUI. Entonces podría proyectar directamente en esos ViewModels.

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

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

la Solución 1 y 2 parecerse a la misma cantidad de trabajo y me siento inclinado a preferir la solución 2 a mantener todas las preocupaciones de base de datos en un proyecto separado. Pero de alguna manera suena mal tener Ver -Modelos en el proyecto de Dominio.

La solución 3 suena como mucho más trabajo ya que tengo más clases para crear y para preocuparme por el mapeo Model-DTO-ViewModel. Tampoco entiendo cuál sería la diferencia entre los DTO y los ViewModels. ¿No son ViewModels exactamente la colección de las propiedades seleccionadas de mi clase Model 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 las desventajas? ¿Hay otras opciones?

¡Gracias por enviarnos tu opinión con anticipación!

Editar (porque tenía tal vez una demasiado larga pared de texto y han pedido para el 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 muestra (y quizás permite editar) el Name de clientes en una lista. En una clase de servicio puedo extraer 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(); 
     } 
    } 
} 

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

Cualquiera de esta manera (a) ...

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

... o mejor de esta manera (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 yo diría: SomeClass1 (vive en el proyecto Domain) es un DTO y SomeClass2 (vive en el proyecto 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)? ¿Hay motivos para presentar ViewModel (SomeClass2) además del DTO (SomeClass1)?

+0

¿Se puede publicar el código? – jfar

+0

@jfar: Ahora hay un ejemplo con código. – Slauma

+0

Tengo curiosidad, ¿cómo ha funcionado tu enfoque elegido para ti? – kenwarner

Respuesta

6

introducir ViewModels que habitan en el proyecto WebUI y exponer IQueryables y el contexto de datos del servicio de EF al proyecto WebUI. Entonces I podría proyectar directamente en esos ViewModels.

El problema con esto es que pronto se encuentra con problemas al utilizar EF tratando de 'aplanar' los modelos. Me encontré con 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 consulta EF4 a la CommentViewModel no funcionaría como el couldn't translate the ToString() method into SQL:

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

usando algo como es AutoMapper una buena elección, especialmente si tiene que hacer muchas conversiones. Sin embargo, también puede crear sus propios convertidores que básicamente conviertan su modelo de dominio a su modelo de vista. En mi caso he creado mis propios métodos de extensión para convertir mi modelo Comment dominio a mi CommentViewModel así:

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 de EF estándar para traer de vuelta a un modelo de dominio y luego usar los métodos de extensión para convertir los resultados a 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(); 
} 
+0

Hm, pero esos métodos de extensión no resolverían su problema para proyectar directamente un DateTime a una cadena dentro de una consulta de EF, ¿o sí? Los métodos de extensión solo funcionan en un objeto de comentario "completo". Pero si quieres una proyección, no usarías un 'Comentario', sino un tipo intermedio. ¿Que estas haciendo entonces? No puede usar su 'ViewModel' (debido al problema descrito) y no desea cargar un objeto' Comment' completo en la consulta. Entonces, ¿en qué tipo proyectas? ¿Esto no da como resultado un tercer tipo entremedio - finalmente un 'DTO'? – Slauma

+0

@Slauma He actualizado mi respuesta para mostrar cómo los uso. Básicamente devuelvo un modelo de dominio de EF y luego lo convierto a su equivalente de modelo de vista. –

+0

@Dan Diplo: Ya veo, entonces básicamente ya no tienes una proyección en tu consulta. Usted carga el objeto completo y luego "proyecta" en la memoria a su ViewModel. Esto podría estar bien en muchos casos si el objeto de dominio es pequeño. Pero piense en el caso en el que tiene un objeto de dominio grande, digamos 50 propiedades, pero solo desea mostrar 10 propiedades en la página web. Entonces tendría una "sobrecarga de consulta" y cargaría 40 propiedades que no necesita. – Slauma

9

Resolvería su problema mediante el uso de una herramienta de asignación automática (como AutoMapper) para hacer la asignación por usted. En los casos donde el mapeo es fácil (por ejemplo, si todas las propiedades de una clase se deben mapear a propiedades con el mismo nombre en otra clase) AutoMapper podrá hacer todo el trabajo de conexión para usted, y tendrá que dar un par de líneas de código para observar que debe haber un mapa entre los dos en absoluto.

De esta manera, usted puede tener sus entidades en Domain, y un par de clases de vista del modelo en su WebUI, y en algún lugar (preferiblemente en WebUI o una sub espacio de nombres de la misma) definen mapas entre ellos. Sus modelos de vista en efecto serán DTO, pero no tendrá que preocuparse mucho por el proceso de conversión entre el dominio y sus clases de DTO.

Nota: Yo recomendaría contra dando sus entidades de dominio directamente a las vistas de su interfaz de usuario web de MVC. No quiere que EF se "quede fijo" hasta la capa de front-end, en caso de que luego quiera usar algo que no sea EF.

+0

"Sus modelos de vista serán en efecto DTO": ¿Quiere decir que no necesito clases de DTO separadas? Tenga en cuenta la "proyección": no consulto una entidad completa sino solo un subconjunto de propiedades. ¿En qué tipo yo proyectaría esas propiedades? Si no tengo DTO separados, tendría que proyectarlos directamente en ViewModels, ¿o no? Pero entonces mis clases de ViewModel serían parte del proyecto del Dominio y no de la WebUI. – Slauma

+1

+1 - Formulé una [pregunta similar] (http://stackoverflow.com/questions/5119349/ef-entities-vs-service-models-vs-view-models-mvc), y obtuve algunas buenas respuestas. Esos también pueden ser de ayuda. –

+0

@Slauma: Como usa EF, que usa de forma predeterminada la carga diferida, puede hacer la proyección en su controlador y asignar la proyección directamente en 'ViewModel's, sin obtener más datos de los que necesita de su base de datos. –

1

Hablando 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, Operación Entrada/Resultado (aka DTO). Todos estos tipos viven en la capa de Dominio. Operaciones es el comportamiento de Entidades y Servicios. A menos que esté construyendo una aplicación CRUD pura, la capa de presentación solo trata con los tipos de entrada/resultado, no con las entidades.No necesita tipos adicionales de ViewModel, estos son los ViewModels (en otras palabras, el Modelo de la Vista). La vista está allí para traducir los resultados de la operación a HTML, pero el mismo resultado se puede serializar como XML o JSON. Lo que usa como ViewModel es parte del dominio, no de la capa de presentación.

+0

OK, esa sería básicamente mi opción (2) (reemplazando mi término "ViewModel" por "Operación Entrada/Resultado"). – Slauma

+1

Un poco tarde aquí:) Algunos estarían en desacuerdo aquí, creo. Un ViewModel puede contener lógica de presentación, por lo que parecería formar parte de la capa de presentación."ViewModel encapsula la lógica de presentación y el estado" http://blogs.msdn.com/b/dphill/archive/2009/01/31/the-viewmodel-pattern.aspx –

+0

@AdamTuliper Sí, puede tener ViewModels en la presentación layer, solo digo que no es obligatorio, puedes usar los DTO que obtienes de la capa de dominio como ViewModel. –

Cuestiones relacionadas