Mapeo, DTO’s y ViewModels en ASP.NET MVC

Los DTO’s –Data Transfer Objects- (algunos los llaman ViewModels/EditModels según el uso para el que existan) nacen por una razón específica: El modelo de lo que ves en la vista no representa directamente tu modelo de negocio. Mi ejemplo tradicional siempre ha sido el formulario de Login/Usuario/Contraseña.

Con el pasar del tiempo mi postura hacia este tema ha sido variada y se ha hecho bastante flexible. Por ejemplo, si tu modelo y tu aplicación es lo suficientemente sencilla y sabes que luego de eso jamás se va a modificar ni cambiar entonces es suficientemente fácil quedarte con el modelo y permearlo hacia la vista y vice versa, eso es precisamente lo que hace la generación Rails/Django/Whatever y lo que solemos hacer con MonoRail y Castle ActiveRecord, ¿la razón? simple, el modelo es sencillo, el dominio es sencillo y no es un proyecto complicado.

Para bien o para mal, en el entorno en que muchos se mueven no se ven cosas tan estáticas y los modelos y reglas de negocio son mucho más complejos siendo generalmente nuestros modelos de negocio , de hecho, si trasladamos eso a una visión de diseño orientado a servicio las cosas cambian aún más (ni hablemos de una "DDD shop"), lugares donde vemos que esta regla se revienta es en sistemas empresariales al estilo CRM/ERP o las siglas que sean…

En el contexto de una aplicación MVC se sobreentiende que el viewmodel es un concern de la vista, por lo tanto esta restringido a los conceptos de presentación. Recordemos también que un controller también es un concern de la vista (si, para aquellos que siguen creyendo que el controlador es la lógica de negocio, les vuelvo a romper el corazón en este momento), un buen lugar para que vivan los viewmodels es dentro de la aplicación MVC, surge el problema entonces, ¿cómo mapeo de un modelo de negocio al modelo de la vista?, bueno, les presento un par de ideas.

Usando el mapper como un servicio

Podemos usar un mapper automático como nuestro intermediario en el controlador entre el modelo de dominio y el viewmodel y de esta manera separar e injectar el mapper en el controlador

public class OrdersController : BaseController {
    readonly IOrdersRepository _ordersRepository;
    readonly IOrdersMapper _mapper;

    public OrdersController(IOrdersRepository ordersRepository, IOrdersMapper mapper) {
        _ordersRepository = ordersRepository;
        _mapper = mapper;
    }

    public ViewResult List() {
        var orders = _ordersRepository.FindAll();
        View(_mapper.Map(orders));
    }
}

Usando un Application Service

En este caso el Application Service se encarga de tomar las decisiones y luego retornar hacia el controlador los resultados, en este caso el controlador no sabe nada de la entidad original del dominio ni sobre el repositorio original, este es sumamente flexible cuando nuestro dominio original suele cambiar drásticamente o tomar decisiones muy complejas para delegárselas al controlador. En este caso es posible que a su vez el mapper sea inyectado y sea una dependencia del servicio. De hecho, podría ser que el servicio no use un mapper para nada, si no que simplemente arme el resultado usando varias uniones de otros resultados sobre la marcha (usando LINQ por ejemplo).

public interface IOrdersService {
    IEnumerable<OrderView> GetOrders();
}

public class OrdersController : BaseController {
    readonly IOrdersService _orderService;
    public OrdersController(IOrdersService orderService) {
        _orderService =  orderService;
    }

    public ViewResult List() {
        var orders = orderService.GetOrders();
        View(orders);
    }
}

Delegando el mapeo a un ModelBinder y ActionFilter

Debo admitir que aunque este es uno de mis resultados o formas favoritas de hacerlo en una aplicación MVC, lo siento a mi criterio demasiado “opionated” (entiéndase, fumado o cargado). En este escenario no es responsabilidad directa ni de un mapper ni de un servicio el retornar los resultados esperados, si no de un Model Binder junto con un ActionFilter en ASP.NET MVC. El controlador luciría algo así:

public class OrdersController : BaseController {
    readonly IOrdersRepository _ordersRepository;

    public OrdersController(IOrdersRepository ordersRepository) {
        _ordersRepository = ordersRepository;
    }

    [Automap(typeof(Order), typeof(OrderView)]
    public ViewResult List() {
        var orders = _ordersRepository.FindAll();
        View(orders);
    }
}

Este último caso es bastante flexible pero involucra que conozcamos bien que es un Model Binder y un ActionFilter, claro, nada que un par de minutos al frente de Visual Studio no pueda solucionar :)

Debo admitir que el caso de usar un Application Service me parece más natural si nuestra aplicación es simplemente un front end a una infraestructura orientada más hacia servicios que hacia un modelo directo de consumo (entiéndase, cosas como un ERP/CRM que probablemente usemos sus interfaces por otro lado y no solamente en nuestra aplicación de “CRUD”) y es mucho más flexible para casos en que necesitamos aislar completamente la “orquestración” del resultado del “despliegue” del resultado, suena extraño al principio pero se me ocurren bastantes casos donde esto se puede presentar.

Debo hacer notar que estas observaciones son meramente orientadas al contexto de una aplicación en ASP.NET MVC y no directamente se pueden trasladar a una aplicación o a un Tier en WCF, en este caso lo único que debemos compartir entre servicios es su esquema, no sus objetos.

Como siempre, espero sus comentarios o críticas al respecto, siempre son bienvenidas, recuerden, estamos en este barco del aprendizaje todos juntos como marineros.

¡Saludos!

UPDATE: Mi amigo José Romaniello me comenta del uso de una interface estilo IMapper en vez de una especializada para mapeo (como lo es IOrdersMapper), este patrón de uso lo considero igual al uso de una especializada, por lo tanto no la incluyo.

UPDATE 2: No incluyo implementaciones definidas de como crear estos mappers, para esto se puede hacer de forma directa o usando una herramienta de mapeo de clases como Automapper, pero eso lo dejo para otro post futuro.

  • Pingback: Tweets that mention Mapeo, DTO’s y ViewModels en ASP.NET MVC | IDisposable Thoughts -- Topsy.com

  • Emilio

    Has tratado de usar este automapper en Entity Fwk 3.5 y DTO’s?

  • Emilio

    Has tratado de usar este automapper en Entity Fwk 3.5 y DTO’s?

  • http://www.fabiomaulo.blogspot.com/ Fabio Maulo

    A mi me gusta lo segundo.
    Tambien tengo que admitir que el tercero me fascina.

  • http://www.fabiomaulo.blogspot.com/ Fabio Maulo

    A mi me gusta lo segundo.
    Tambien tengo que admitir que el tercero me fascina.

    • http://www.cprieto.com cprieto

      Creo que eso me da incentivo para publicar en algún lugar el código :)

  • http://www.fabiomaulo.blogspot.com/ Fabio Maulo

    Pruba este si quieres meterle el turbo
     http://emitmapper.codeplex.com/

  • http://www.cprieto.com cprieto

    Creo que eso me da incentivo para publicar en algún lugar el código :)

  • Emilio

    Has tratado de usar este automapper en Entity Fwk 3.5 y DTO's?

  • http://www.fabiomaulo.blogspot.com/ Fabio Maulo

    A mi me gusta lo segundo.
    Tambien tengo que admitir que el tercero me fascina.

  • http://www.fabiomaulo.blogspot.com/ Fabio Maulo

    Pruba este si quieres meterle el turbo
    http://emitmapper.codeplex.com/

  • http://www.cprieto.com cprieto

    Creo que eso me da incentivo para publicar en algún lugar el código :)

  • http://twitter.com/jfroma José F. Romaniello

    En SharpArchitecture hay un artefacto interesante que es un modelbinder y le inyecta IRepository, para resolver ciertos casos como many-to-one. Interesante para verlo.. si bien no he usado nunca SharpArchitecture.

  • http://twitter.com/jfroma José F. Romaniello

    En SharpArchitecture hay un artefacto interesante que es un modelbinder y le inyecta IRepository, para resolver ciertos casos como many-to-one. Interesante para verlo.. si bien no he usado nunca SharpArchitecture.

    • http://www.cprieto.com cprieto

      Realmente no esta directamente relacionado a un mapper en el model binder el caso de SharpArchitecture, en realidad utiliza un ModelBinder para facilitar el llenado de la entidad en la entrada de la acción, algo muy similar a como lo hacen las frameworks basadas en MVC como Rails y MonoRail (este último hace lo mismo usando un ActiveRecordAdapter). Espero pronto escribir un poco más de como podemos aprovechar los ModelBinders para nuestro provecho… deseame suerte :)

  • http://www.cprieto.com cprieto

    Realmente no esta directamente relacionado a un mapper en el model binder el caso de SharpArchitecture, en realidad utiliza un ModelBinder para facilitar el llenado de la entidad en la entrada de la acción, algo muy similar a como lo hacen las frameworks basadas en MVC como Rails y MonoRail (este último hace lo mismo usando un ActiveRecordAdapter). Espero pronto escribir un poco más de como podemos aprovechar los ModelBinders para nuestro provecho… deseame suerte :)

  • http://twitter.com/jfroma José F. Romaniello

    En SharpArchitecture hay un artefacto interesante que es un modelbinder y le inyecta IRepository, para resolver ciertos casos como many-to-one. Interesante para verlo.. si bien no he usado nunca SharpArchitecture.

  • http://www.cprieto.com cprieto

    Realmente no esta directamente relacionado a un mapper en el model binder el caso de SharpArchitecture, en realidad utiliza un ModelBinder para facilitar el llenado de la entidad en la entrada de la acción, algo muy similar a como lo hacen las frameworks basadas en MVC como Rails y MonoRail (este último hace lo mismo usando un ActiveRecordAdapter). Espero pronto escribir un poco más de como podemos aprovechar los ModelBinders para nuestro provecho… deseame suerte :)