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!

viernes, 14 de febrero de 2014

Introducción a NUnit (II): Atributos básicos

Si en el anterior post vimos una introducción a NUnit y su !tooling”, en este post repasaremos cuales son algunos de los atributos básicos que debes conocer para trabajar con NUnit.

Como ya habrás observado, NUnit se basa principalmente en atributos. Hay gente a las que no les gusta los atributos pero, en el caso de NUnit, se usan para evitar caer en un complejo y enrevesado sistema de herencia simplemente para describir y ejecutar nuestros tests. Además esta la imposición del lenguaje por la que no se soporta la herencia múltiple. En definitiva, NUnit está basado en atributos y, personalmente, no me parece una mala idea. De todas formas frameworks hay muchos y, por ejemplo, un framework basado en convenciones y que parece ahora estar muy de moda es fixie (chivatazo vía @gulnor).

En principio, los dos atributos clave sin los cuales no podrías vivir son TestFixture y Test.

TestFixture decora una clase e indica que será un contenedor de métodos de test. En realidad no es un atributo obligado, sólo debemos indicarlo si queremos crear un TestFixture parametrizado o un TestFixture genérico. Aunque todo esto lo veremos en un siguiente post…, un ejemplo de TestFixture parametrizado sería como sigue:

[TestFixture(1, 2, 3)]
[TestFixture(2, 2, 4)]
class CalculadoraText
{
    private readonly int _num1;
    private readonly int _num2;
    private readonly int _expected;
 
    public CalculadoraText(int num1, int num2, int expected)
    {
        _num1 = num1;
        _num2 = num2;
        _expected = expected;
    }
 
    [Test]
    public void Sumar()
    {
        var calculadora = new Calculadora();
        var resultado = calculadora.Sumar(_num1, _num2);
        Assert.AreEqual(_expected, resultado);
    }
}

Como era de esperar, ahora el método de test Sumar se ejecutará 2 veces porque se instanciará la clase CalculadoraTest 2 veces, cada vez con un conjunto distinto de parámetros.

image 

En cualquier caso, por ahora no utilizaremos el atributo TestFixture.

El siguiente atributo must-know es Test. Con Test indicamos que un método es una prueba. En principio los métodos de test no tiene que tener ningún parámetro ni tampoco devolver ningún valor. Más adelante veremos que, al igual que con TestFixture, existe el concepto de tests parametrizados y entonces esta regla de la firma de los métodos ya no será cierta.

Aunque tanto en el atributo Test como en el atributo TestFixture se puede asignar un valor al parámetro Description, sólo se percatará del mismo los test runners de NUnit (nunit.exe y nunit-console.exe). Además, tampoco es una información muy visible (sólo aparece en las propiedades del test o en el fichero de resultados XML). Otra opción es usar el atributo Description (aunque de nuevo sólo sigue apareciendo en los test runners de NUnit).

[Test][Description("Sumando 2 números enteros")]
public void Sumar()

{
    var calculadora = new Calculadora();
    var resultado = calculadora.Sumar(1, 2);
    Assert.AreEqual(3, resultado);
}

image

Algo bastante útil cuando empezamos a tener un gran número de tests es agruparlos. Con el atributo Category podemos agrupar tanto TestFixtures como Tests. No hay ningún problema en que un test pertenezca a varias categorías. De hecho, en el caso de un Test podría pasar que perteneciera a 2 distintas categorías si su TestFixture tiene establecido una categoría.

[Category("Pruebas de calculadora")]
class CalculadoraText
{
    [Test]
    [Category("Pruebas de suma")]
    public void Sumar()
    {
        var calculadora = new Calculadora();
        var resultado = calculadora.Sumar(1, 2);
        Assert.AreEqual(3, resultado);
    }
}

image

La gran ventaja de agrupar tests en categorías es que podemos seleccionar cuales queremos ejecutar y cuales no. Por ejemplo, nunit-exe tiene toda una pestaña dedicada a la gestión de las categorías, desde donde podemos seleccionar que categorías queremos ejecutar o que categorías no queremos ejecutar.


image

Si utilizamos nunit-console.exe, podemos realizar la misma operación de inclusión o exclusión de categorías con los parámetros /include y /exclude, e incluso en ReSharper y Test Explorer también podemos agrupar los test por categoría.

Si no te gusta harcodear el nombre de la categoría, también se puede crear un nuevo atributo que herede de CategoryAttribute y agrupar así los tests de forma programática.

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class Critico : CategoryAttribute { }
 
class CalculadoraText
{
    [Test]
    [Critico]
    public void Sumar()
    {
        var calculadora = new Calculadora();
        var resultado = calculadora.Sumar(1, 2);
        Assert.AreEqual(3, resultado);
    }
}

Como último apunte sobre categorías, si estás trabajando con ReSharper podrías forzar que ciertas categorías sólo se ejecuten si se seleccionan explícitamente:


image 

También es importante reseñar como en las propias opciones de ReSharper hay un nodo dedicado en exclusivo a su integración con NUnit.

Otros atributos relacionados con la agrupación/ejecución de tests y que son muy utilizados son Ignore y Explicit.

Cuando decoramos un TestFixture o un Test con Explicit, estamos queriendo decir que el test sólo se ejecutará si explícitamente si le decimos que lo haga seleccionándolo directamente desde alguno de los test runners. En la práctica, se marca un test como Explicit bien cuando el test es de larga duración, bien cuando no debería ejecutarse siempre o bien cuando requiere de servicios externos, en definitiva, cuando queramos controlar cuando se ejecuta el test.

En teoría un test Explicit no debería contar en en el número total de tests, simplemente no se ejecuta y se reporta como skipped (distinto de ignorado). En la práctica sólo los test runners de NUnit incorporan este concepto y, por ejemplo, en Reshaper es etiquetado como Ignored.

Con Ignored estamos ignorando deliberadamente un test y al igual que pasa con Explicit, no se ejecutará. Normalmente se marca un test como ignorado cuando no queremos olvidarlo pero por contra no podemos implementarlo (sería un TODO test) o tenemos que fixearlo porque algo está roto. Es decir, seguirá apareciendo en los resúmenes, en el conteo total, seguirá compilándose como parte del proyecto… pero no nos olvidaremos de él porque siempre es reportado. Es una alternativa digna a comentar el código de un test, que podría acabar en un test olvidado y del que nadie sabe nada al respecto.

Un test ignorado es tratado de forma especial en los test runners de NUnit y aparece bajo la pestaña “Test Not Run”.

Ambos atributos (Explicit e Ignore) permite especificar un texto como motivo en el parámetro reason (que de uno u otro modo aparece en todos los test runners).

Otro atributo clave y muy utilizado es ExpectedException. Con este atributo indicamos que el test fallará si no se produce una excepción del tipo indicado durante la ejecución del test.

Por último, otros atributos interesantes podían ser Timeout y MaxTime (lo cierto es que todo lo relacionado con timeouts me intriga y fascina…¡qué se le va a hacer!).

Timeout indica el tiempo máximo en el que el test debe ejecutarse, si el test (o TextFixture) lo sobrepasa se cancelará su ejecución y será reportado como fallo (lógicamente si estamos depurando no se tiene en cuenta este valor). Si Timeout es indicado a nivel de TextFixture, podría pasar que incluso algunos tests no llegarán a ejecutarse porque se canceló el TextFixture.

Por otro lado MaxTime también reporta el test como no superado si sobrepasa el tiempo indicado pero, a diferencia de Timeout, no cancela la ejecución del test. Además, cualquier aserción prevalecerá sobre MaxTime. Por ejemplo, el siguiente test fallará por la aserción y no por MaxTime:

[Test]
[MaxTime(2000)]
public void Sumar()
{
    System.Threading.Thread.Sleep(3000);
    var calculadora = new Calculadora();
    var resultado = calculadora.Sumar(1, 2);
    Assert.AreEqual(5, resultado);
}
Mas posts de esta serie:
Un saludo!

viernes, 7 de febrero de 2014

Tipos complejos o value objects con Code First

Cuando diseñamos nuestro modelo conceptual (me atrevería a decir nuestro modelo de dominio en escenarios DDD), surge la necesidad de diferenciar entre entidades y value objects.

Si hemos optado por Database First, la operación es muy sencilla y se realiza en apenas un par de clicks en el diseñador de EF.

clip_image002

 

Si por el contrario estamos usando Code First la cosa cambia. Para que se percate de que queremos un value object tenemos que seguir ciertas convenciones o bien instruir a EF con DataAnnotations o FluentAPI.

 

Las convenciones que sigue Code First para descubrir un tipo complejo (así lo llama en terminología EF) son las siguientes:

  • El tipo no puede tener una clave.
  • El tipo sólo puede contener propiedades de tipos básicos.
  • El tipo sólo puede ser una referencia en una propiedad de navegación (es decir, no puede estar en un extremo “varios”).

 

Partiendo de una sencilla clase Cliente y una Dirección, veremos cómo crear el tipo complejo y salvar algunos escollos si se produjeran.

 

class Cliente

{

    public int ClienteId { get; set; }

    public virtual Direccion Direccion { get; set; }

}

 

class Direccion

{

    public string Provincia { get; set; }

    public string CodigoPostal { get; set; }

    public string Calle { get; set; }       

}

Se vuelca en la base de datos de la siguiente forma:

clip_image003

Como veras la convención de nombrado de los campos en la bd es NombrePropiedadClasePadre_NombrePropiedad.

Si hacemos algo que incumple las reglas por las que Code First descubre un tipo complejo, tendremos que utilizar DataAnnotations o FluentAPI para “corregir” a Code First.

class Direccion

{

    public int DireccionId { get; set; }

    public string Provincia { get; set; }

    public string CodigoPostal { get; set; }

    public string Calle { get; set; }

}

clip_image004

¡Oye! Eso no lo que queríamos… ¡Claro!, encontró una clave en Direccion (DireccionId) y creo una entidad y no un value object.

Actualización: Me corrige vía twitter Nicolás Herrara y me dice que si Dirección ahora tiene Id, ya tiene identidad luego ya no es un value object. ¡Correcto! Asumo que es un ejemplo bastante rebuscado a la par que desafortunado, pero lo que quería mostrar era que con [ComplexType] puedes prevalecer sobre las convenciones de Code First. ¡Gracias Nicolás!

Para solucionarlo tenemos 2 opciones bien sencillas:

Con DataAnnotations:

    [ComplexType]

    class Direccion

Con FluentApi:

protected override void OnModelCreating(DbModelBuilder modelBuilder)

{

    modelBuilder.ComplexType<Direccion>();

}

¡Ahora sí!

clip_image005

Un saludo!