lunes, 17 de febrero de 2014

Convenciones de nombrado para ViewModels en ASP.NET MVC

En ASP.NET MVC es muy utilizado (y también aconsejable) practicar el patrón ViewModel. Básicamente, el patrón ViewModel consiste en crear una clase específica para una vista con todo lo necesario para que pueda renderizarse. De este modo, evitamos el paso de información no estructura a la vista desde el controlador (como hacemos cuando utilizamos ViewBag).

En cualquier caso, el propósito de este post no es contar qué es un ViewModel sino que convenciones utilizo “yo” para su nombrado y ubicación dentro del código. Te prometo que cuando empiezas a tener muchos controladores, muchas acciones y muchos ViewModels, o te organizas o terminas con una maraña de clases importante (al menos a mí me pasa).

“Mis” convenciones son:

Crear una carpeta raíz en el proyecto con el nombre ViewModels.

Aquí a veces pienso en crear un ensamblado para guardar los ViewModels, pero bueno…

Dentro de esta carpeta, crear una nueva carpeta por cada controlador con el nombre del mismo. Por ejemplo y para el controlador Account: ViewModels\Account

Por cada método de acción de un controlador crear una clase con el nombre {Controller}{Action}ViewModel

¿Por qué pongo {Controller} en el nombre de la clase cuando ya está contenida en una carpeta (y por ende un espacio de nombres) también con {Controller}?

Pues lo hago porque me ayuda a reconocer mejor la clase fuera de ciertos contextos. Por ejemplo para un método de acción Index del controlador Customers, puedo llamar al ViewModel bien IndexViewModel o bien CustomersIndexViewModel. El problema de la segunda forma (la que utilizo) es que podría llegar a ser muy verbose... pero no me importa, con franqueza. Sin embargo, el problema de la primera forma (la que no incluye {Controller}) es que si la utilizo sin estar plenamente cualificada (esto es importando el espacio de nombres - using ViewModels.Customers), fuera del ámbito del controlador o su vista pierde significado y no puedo ubicarla directamente ni saber su propósito de un primer vistazo.

Otra convención (o convención sugerida, que no obligada, porque la cumplo cuando quiero y cuando no quiero no), es cambiar el nombre del ViewModel a {Controller}{View}ViewModel. Es decir, cambiamos {Action} por {View}. Esto lo hago porque no siempre la vista tiene el mismo nombre que la acción y entonces me parece que es justo que el nombre del ViewModel esté más cerca de a quién realmente pertenece, que es la vista y no a la acción.

Por último, si tengo dos ViewModels para la misma acción (normalmente una para la petición y otro para la respuesta), utilizo la palabra Request y Response para diferenciarlos  (o también Get y Post) para diferenciarlos. Es decir, el nombre del ViewModel con este matiz queda así: {Controller}{Action}[Request|Response]ViewModel.

¿Y tus convenciones para ViewModel cuáles son?

¡¡Actualizado!!

Los buenos de @gulnor, @2Flores y @rf1souto me comentan otras opciones como la de crear una estructura basada en funcionalidades en vez de en la convención sugerida por MVC.

El post semilla de todo esto es Feature Folders in ASP.NET MVC. Para llevarlo a cabo (y agregando algo de nuestra propia cosecha) hemos hecho lo siguiente: 

Agregar una nueva ruta donde buscar vistas al motor RazorViewEngine en el global.asax (este código no es el mejor, está claro, accede al motor por índice, pero ya se mejorará…). Lo importante es que preserva las rutas originales (las que siguen la convención de MVC) pero agregar una nueva:

            var razorViewEngine = ViewEngines.Engines[1] as RazorViewEngine;
            var viewLocationFormats = razorViewEngine.ViewLocationFormats;
            var newViewLocationFormats = new List<string>(viewLocationFormats);
            newViewLocationFormats.Add("~/Features/{1}/Views/{0}.cshtml");
            razorViewEngine.ViewLocationFormats = newViewLocationFormats.ToArray();

Después simplemente crear una estructura de carpetas donde, como puedes ver, está agrupando por característica tanto el controlador como las vistas y ¿por qué no también los ViewModels?

Financiero

El fichero Web.config lo hemos tenido que meter porque al principio nos daba un error la vista porque no heredaba de WebViewPage o WebViewPage<TModel>, pero rápidamente llegó stackoveflow al rescate y nos dio la idea.

Un saludo!

5 comentarios:

  1. Hola Sergio, bueno en verdad que es algo que da mucho de que hablar, en mi caso prefiero siempre mandar los viewmodel a un proyecto diferente, allí siempre los nombre con [Clase]ViewModel, así se que solo los voy a usar para la parte de UI, en dicho proyecto solo los ubico dentro de una carpeta llamada ViewModel, ya que allí muchas veces tengo otras entidades más de dominio que de presentación, siempre para entre las de dominio y los ViewModel me ayudo con AutoMapper (en las dos vías).

    Saludos

    ResponderEliminar
    Respuestas
    1. Hola Julio,
      Lo primero gracias por comentar.
      Lo de mover los VM a un ensamblado propio me parece una buena idea, algún día lo probaré, tiene que sencillo y además no son parte de la convención MVC, luego nadie se quejará ni habrá que sobrescribir nada.
      Me intriga lo que has dicho de "otras entidades más de dominio que de presentación" ¿Tienes juntas las entidades del dominio (anémicas o no) junto a los VM? ¿De que "otro" tipo de entidades hablas? La verdad es que la palabra "entidad" es como un saco roto, todo es susceptible de llamarse "entidad" en algún momento :)
      Gracias!

      Eliminar
    2. Hola Sergio, bueno cuando digo entidades de dominio son como tal las que persisto en la BD, en algunos casos si la app es pequeña las dejo todas juntas, igual al ser entidades POCO pues nada que ver con mi ORM o lo que use para persistir.

      Eliminar
  2. Sergio ¿Para todos los escenarios utilizas un ViewModel diferente para response y request? ¿Incluso si el mismo para ambos lo diferencias?

    ResponderEliminar
  3. Hola Miguel,
    Yo si ambos 2 VM son exactamente iguales, no los diferencio. Como ahora estas muy SOLID, esto es YAGNI, no? :-)
    Gracias por comentar!

    ResponderEliminar