Angular 2 PathLocationStrategy y Asp.Net Core Mvc

Si has hosteado una aplicación de angular 1 o angular 2 en Asp.Net Core Mvc una de las cosas con las que te habrás peleado es con la configuración del routing de cliente.

En angular 1, por defecto se utiliza el # como separador para identificar la parte de la url que define la ruta de cliente, pero en angular 2, ya no es así, y no se utiliza ningún separador (aunque se puede cambiar para que vuelva a utilizar el #).

Así, una ruta que en angular 1 podría ser http://myapp.com/#ruta/local, por defecto en angular 2 sería http://myapp.com/ruta/local.

Esto supone un problema cuando se realiza la petición al servidor, ya que como es una ruta normal (sin #), el framework intentará buscar el controlador y el método de acción pare devolver el Html generado, pero en realidad no va a existir y recibiremos un bonito 404.

Hay varias opciones para configurar el framework para que soporte routing de cliente, pero la más sencilla es usar el paquete de NuGet Microsoft.AspNetCore.SpaServices.

Entre otras cosas, nos ofrece un método de extensión sobre IRouteBuilder para registrar un controlador y método de acción que sirva de fallback para todas las rutas de cliente que no sepa encontrar.

app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "default",
        template: "{controller=Home}/{action=Index}/{id?}");

    routes.MapSpaFallbackRoute("spa-fallback", new { controller = "Home", action = "Index" });
});

En esta aplicación, la página que contiene la SPA se sirve desde el controlador HomeController y la acción Index. Así que cualquier ruta que no se corresponda con un controlador y método de acción existente, entenderemos que forma parte de la SPA. Por lo tanto, devolveremos el punto de entrada de la SPA para que angular, en este caso, pueda gestionar sin problemas la parte de la url que corresponda al routing local.

Si estás trabajando con Asp.Net Core Mvc y angular (o React, o Knockout…), te recomiendo que te pases por este repo de GitHub https://github.com/aspnet/JavaScriptServices y revises algunas joyitas interesantes que aparecen por allí.

¡Que lo disfrutes!

Asp.Net Core Mvc y las convenciones

Asp.Net Core Mvc incluye un completo sistema de convenciones con el que podemos modificar completamente el comportamiento por defecto que tiene el framework.

Concretamente tenemos las siguientes interfaces:

  • IApplicationModelConvention
  • IControllerModelConvention
  • IActionModelConvention
  • IParameterModelConvention

Todas definen un sólo método (Apply) que recibe un modelo que se corresponde al contexto  con el que estamos trabajando(aplicación, controlador, método de acción o parámetro de método de acción) .

public interface IApplicationModelConvention
{
    void Apply(ApplicationModel application);
}

Lo realmente interesante, es que ese modelo que recibimos no es de sólo lectura, sino que podemos modificar a nuestro antojo las características de los controladores, acciones y parámetros, es decir, modificar el comportamiento del framework de forma implícita al cargar nuestra aplicación.

Podemos, por ejemplo, añadir un filtro a una acción si cumple una determinada condición. Vamos a verlo con un ejemplo sencillo.

A mi me gusta tener un ActionFilter que valide el ModelState en vez de tener que repetir el código de validación en todos los métodos de acción. Algo como esto:

public class ValidateModelStateAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if (context.ModelState.IsValid == false)
        {
            context.Result = new BadRequestObjectResult(context.ModelState);
        }
    }
}

Este filtro se ejecuta antes de que se ejecute la acción pero habiendo hecho ya el ModelBinding de la petición, por lo que ya se han hecho las validaciones del modelo y, si ha habido errores, estos están reflejados en el ModelStateDictionary. Por lo tanto, si hay errores en el ModelState, no queremos que se ejecute el método de acción, y para eso, establecemos en el filtro el resultado. En este caso, la respuesta será un BadRequest  (400) incluyendo en la respuesta la información de los campos que no cumplen las reglas de validación.

Para utilizarlo en un método de acción, símplemente decoramos el método de acción con este atributo.

[HttpPost("api/products")]
[Authorize(Policies.ManageProducts)]
[ValidateModelState]
public async Task CreateProduct([FromBody]ProductBindingModel model)
{
    ...
}

Pero si me quiero evitar ir añadiendo este atributo a todos los métodos que lo necesitan, lo que me ofrecen las convenciones, es la posibilidad de añadir este atributo de forma automática a todos los métodos que lo necesiten, que serán todos los que tengan parámetros con tipos complejos.

La verdad es que con el api que expone el modelo de la aplicación, no es una tarea complicada. Quedaría algo así:

public class AddValidateModelStateAttributeConvention : IApplicationModelConvention
{
    public void Apply(ApplicationModel application)
    {
        var actionsWithComplexTypeParameters = application.Controllers
            .SelectMany(c => c.Actions.Where(a => a.Parameters.Any(p => p.ParameterInfo.ParameterType.IsComplexType())));
 
        foreach (var action in actionsWithComplexTypeParameters)
        {
            if (action.Attributes.Any(a => a.GetType() == typeof(ValidateModelStateAttribute)) == false)
            {
                action.Filters.Add(new ValidateModelStateAttribute());
            }
        }
    }
}

En primer lugar recuperamos todos los métodos de acción que tienen parámetros de tipos complejos (clases). Sobre estos métodos de acción iteramos y, si no tienen ya aplicado el atributo, lo añadimos a su colección de filtros. Así de sencillo!

Para añadir la convención, sólo tenemos que ir al método Configure de nuestro Startup.cs y añadirlo a las convenciones que usa Mvc

services.AddMvc()
    .AddMvcOptions(options =>
    {
        options.Conventions.Add(new ValidateModelStateAttributeConvention());
    });

Problema

Desde mi prespectiva, a la definición y uso de convenciones personalizadas le veo un par de problemas que para mi son importantes.

En función del proyecto y de las convenciones que se hayan aplicado al mismo, código que va a tener el mismo comportamiento puede llegar a estar escrito de forma sensíblemente diferente. Pueden faltar atributos que se aplican por convención, no se valida el ModelState porque se hace por convención, y escenarios parecidos. Desde mi punto de vista, eso dificulta enormemente las revisiones de código en escenarios de trabajo multiproyecto y hace más dificil el revisar, de un vistazo, si nos hemos dejado algo en la implementación o no.

De la misma manera, un desarrollador acostumbrado a tener aplicadas unas convenciones en un proyecto, cuando se mueve a otro en el que no se han aplicado las mimas convenciones, es más fácil que escriba código con errores.

Llamadme antíguo, pero a mi me gusta que el código sea lo más explícito posible, no dando lugar a la interpretación. Es como el establecer el ámbito de visibilidad de clases y variables. Hay una visibilidad por defecto, pero creo que es mejor establecer siempre la visibilidad para que sea explícita y nadie se pueda equivocar al interpretarla.

Esto me ha llevado a no prestarles atención a las convencionas durante un tiempo. Hasta ahora…

¿Pero y si no sirven sólo para eso?

Pero hace poco, se me pasó por la cabeza un escenario en el que si que porían ser muy útiles las convenciones. El de los test de integración.

En vez de aplicar las convenciones de manera automática, vamos a utilizarlas para verificar que se han establecido unos atributos determinados en los métodos que los necesitan. Al final, con el modelo que exponen las convenciones ya hemos visto que este escenario sería muy sencillo.

Con esta perspectiva, en el ejemplo que hemos visto antes, mi convención debería validar que todos los métodos de acción que tienen tipos complejos como parámetros tienen aplicados este atributo, y si no lo tienen, lanzar una excepción con el nombre del controlador y del método de acción que no lo tiene.

De esta forma la nueva convención quedaría así:

public void Apply(ApplicationModel application)
{
    var actionsWithComplexTypeParameters = application.Controllers
        .SelectMany(c => c.Actions.Where(a => a.Parameters.Any(p => p.ParameterInfo.ParameterType.IsComplexType())));
    foreach (var action in actionsWithComplexTypeParameters)
    {
        if (action.Attributes.Any(a => a.GetType() == typeof(ValidateModelStateAttribute)) == false)
        {
            throw new InvalidOperationException(string.Format(
                "The action [{0}] in the controller [{1}] has a complexType parameter and has not a [ValidateModelState] attribute",
                action.ActionName,
                action.Controller.ControllerName));
        }
     }
}

Básicamente es el mismo código que hemos escrito antes, pero en vez de añadir el filtro, en este caso lanzará una excepción.

Esta nueva convención la añadiríamos sólo en el Startup.cs de nuestro proyecto de test de integración y, como parte de la validación de nuestra Api, verificaríamos que no nos hemos dejado nada importante en nuestra implementación.

La verdad es que las convenciones, aplicadas desde esta perspectiva, aportan mucho valor para validar el modelo de nuestra aplicación, permitiéndonos validar aspectos que sólo podríamos identificar de otra forma con test de integración atacando directamente al Api para validar estos escenarios.

¡Que lo disfrutéis!

Autorización en WebApi… ¿y si fuera como la de Asp.Net Core 1.0?

Una de las muchas novedades que nos ofrece Asp.Net Core 1.0 es el rediseño de los filtros de autenticación. Se deja atrás el concepto de autorización por roles o usuarios y se introduce un nuevo concepto de autorización por políticas. Cada una de estas políticas define que elementos quiere tener en cuenta para autorizar al usuario. Puede ser la presencia de un determinado claim, que un claim tenga uno o varios valores específicos, o incluso que el nombre del usuario o el rol tengan unos valores determinados (no olvidemos que los roles o el nombre del usuario siguen siendo claims ). Además se introduce también el concepto de un servicio de autorización (IAuthorizationService) que podremos inyectar en nuestros controladores para validar si un usuario está autorizado o no a realizar una acción, no sólo teniendo en cuenta las características del usuario, sino el propio estado del recurso al que se quiere acceder.

Estas políticas las definiremos de esta forma:

// Add Authorization policies
services.AddAuthorization(options =>
{
    options.AddPolicy("MiPolitica", policy =>
    {
        policy.RequireAuthenticatedUser();
        policy.RequireClaim("department", "sales");
    });
    options.AddPolicy("Mayor18", policy =>
    {
        policy.Requirements.Add(new MinimumAgeRequirement(18));
    });
});

En Asp.Net Core 1.0, esta configuración se hace en la clase Startup, dentro del método ConfigureServices. Como seguramente ya sabrás, este método lo que hace es configurar y registrar una serie de clases para que estén disponibles para el mecanismo integrado de inyección de dependencias, y puedan ser utilizadas por la propia infraestructura e incluso por el propio desarrollador.

Una vez definidas las políticas, se define con el atributo Authorize la política que se quiere aplicar a nivel de controlador o acción. De hecho, se ha creado una sobrecarga en el constructor de este conocido atributo para poder definir el nombre de la política que se va a utilizar para validar la autorización.

[Authorize("MiPolitica")]

La verdad es que toda esta nueva infraestructura nos permite abordar escenarios realmente complejos que en las versiones actuales de WebApi y Mvc exigirían mucho código personalizado.

¿Lo puedo utilizar en WebApi hoy?

Pero toda esta infraestructura no está disponible en los proyectos de WebApi en los que estamos trabajando hoy. Tenemos que seguir utilizando el atributo Authorize de toda la vida en el que sólo podemos especificar los roles o los usuarios que están autorizados.

¿O no?

Gracias al paquete de NuGet Acheve.Web.Http.Authorization podemos tener a nuestra disposición en los proyectos de WebApi toda la potencia de la autorización basada en políticas que nos ofrece Asp.Net Core 1.0.

Sólo hay que tener en cuenta una cosa importante, y es que, por defecto, WebApi no nos proporciona un mecanismo de inyección de dependencias integrado en el framework, y, por lo tanto, para que funcione todo esto estamos obligados a configurar nuestro contenedor de inyección de dependencias preferido con WebApi y a registrar en el los tipos que hacen posible que todo esto funcione.

Configurar un contenedor de inyección de dependencias externo en WebApi es sencillo. Sólo tenemos que asignar el Adapter para nuestro contenedor a la propiedad DependencyResolver de la configuración de WebApi.

Por ejemplo con Autofac, la configuración sería algo parecida a esto:

public class Startup
{
  public void Configuration(IAppBuilder app)
  {
    var builder = new ContainerBuilder();

    // STANDARD WEB API SETUP:

    // Get your HttpConfiguration. In OWIN, you'll create one
    // rather than using GlobalConfiguration.
    var config = new HttpConfiguration();

    // Register your Web API controllers.
    builder.RegisterApiControllers(Assembly.GetExecutingAssembly());

    // Run other optional steps, like registering filters,
    // per-controller-type services, etc., then set the dependency resolver
    // to be Autofac.
    var container = builder.Build();
    config.DependencyResolver = new AutofacWebApiDependencyResolver(container);

    // OWIN WEB API SETUP:

    // Register the Autofac middleware FIRST, then the Autofac Web API middleware,
    // and finally the standard Web API middleware.
    app.UseAutofacMiddleware(container);
    app.UseAutofacWebApi(config);
    app.UseWebApi(config);
  }
}

A partir de este momento, nuestros controladores pueden definir dependencias en el constructor que serán resueltas por el contenedor.

Si instalamos el paquete de NuGet Acheve.Web.Http.Authorization.Autofac, podremos utilizar un método de extensión sobre el ContainerBuilder de Autofac que nos permitirá registrar nuestras políticas exactamente de la misma manera que en Asp.Net Core 1.0.

builder.UsePolicyAuthorization(options =>
{
    options.AddPolicy(Policies.Sales, policy =>
    {
        policy.RequireAuthenticatedUser();
        policy.RequireClaim("department", "sales");
    });
    options.AddPolicy(Policies.Over18Years, policy =>
    {
        policy.Requirements.Add(new MinimumAgeRequirement(18));   
    });
});

A partir de este momento, podremos usar los atributos definidos en el ensamblado Microsoft.Asp.Net.Authorization (este ya es Asp.Net Core!) para definir las políticas de autorización de nuestros controladores y métodos de acción. De hecho, el atributo se llama igual que el definido por WebApi, por lo que tendremos que utilizarlo definiendo el nombre completo del tipo.

    [Microsoft.AspNetCore.Authorization.Authorize]
    [RoutePrefix("products")]
    public class ProductsController : ApiController

Pero todavía nos falta una cosa. El nuevo modelo de Asp.Net Core permite que estos atributos no sean atributos de autorización del framework (lo que por otra parte nos permite reutilizarlos en una plataforma diferente). Así que si no hacemos nada más, realmente WebApi no sabría qué hacer con ellos. Para solucionarlo, configuraremos un filtro global en la configuración de WebApi que será el que se encargue de descubrir estos atributos e integrarlos en la autorización de WebApi.

config.Filters.Add(new UseAspNetCoreAuthorizationModelAttribute());

Voy a ir escribiendo una serie de posts sobre el uso de toda esta nueva infraestructura de autorización para descubrir toda la potencia que nos ofrece. Pero lo más importante es que todo lo que veamos, lo vamos a poder utilizar tanto en WebApi 2 como en Asp.Net Core 1.0.

El código fuente de los paquetes de NuGet está aquí: https://github.com/hbiarge/Acheve.Web.Http.Authorization e incluye ejemplos de uso de este mecanismo de autorización en un proyecto de WebApi 2.

WebApi: Tests de integración con diferentes identidades

tldr;
Acabo de publicar un pequeño paquete de NuGet para facilitar los test de integración de WebApi cuando incluyen peticiones autenticadas y autorización por claims.
Lo puedes encontrar aqui: Acheve.Owin.Testing.Security
Y el código fuente aquí: Github

 

En general, no me gusta hacer test unitarios de mis controladores de WebApi. Me da la sensación que me aportan muy poco valor. Hay que mockear un montón de infraestructura, haciendo que los tests sean farragosos de configurar (por muchos métodos de extensión que creemos para facilitarnos el trabajo).

Una de las soluciones que más me gusta en estos escenarios es la de hacer test de integración del api. Juanma (aka @gulnor) lo comentaba por aquí (post de lectura muy recomendada) hace un tiempo, y yo no puedo estar más de acuerdo con esta visión.

“La complejidad reside en la configuración de los componentes”

El componente que suelo utilizar para estos test de integración es el paquete de NuGet Microsoft.Owin.Testing. Básicamente me ofrece un host basado en Owin para el api. Muy sencillo de configurar con su clase Startup y muy rápido de ejecutar.

Pero el problema es cuando el api, no sólo requiere peticiones autenticadas, sino que además incluye algún mecanismo de autorización basado en roles claims y hay determinados endpoints que tienen un comportamiento u otro en base a esta información. Desde luego, este es el escenario perfecto en el que los test de integración aportan muchísimo. Pero no es fácil de configurar. ¿Cómo puedo incluir de forma sencilla en mis test de integración los claims del usuario que quiero suplantar al realizar la petición?

La verdad es que he utilizado diferentes aproximaciones en diferentes proyectos, pero aquí te voy a contar la que más me ha gustado.

¿Dónde configuro el mecanismo de autenticación de mi api?

Antes de entrar en faena, déjame que haga una pequeña reflexión sobre este punto. Creo que es importante.

Con WebApi voy a poder “hostear” mi api de diferentes maneras. El propio framework ya permitía el concepto de Self Host cuando se publicó su versión 1. Esto quiere decir que no necesito un IIS para exponer el api, sino que lo puedo hacer en cualquier tipo de aplicación: consola, servicio de Windows, etc. Además, con la aparición de OWIN y “katana”, es todavía más fácil utilizar diferentes “hosts” para el api.

La idea es que mi api debe incluir la lógica de autorización, para lo que seguramente, necesitará un usuario autenticado para poder saber quién es y que permisos tiene sobre el api. Sobre todo en el escenario que planteábamos en el que hay que discriminar si el usuario que hace la petición está autorizado o le tenemos que devolver un 401 o un 403.

Pero en realidad es el host el que debe decidir qué mecanismo concreto de autenticación  se ha de utilizar. Es decir, qué elemento de la petición Http se debe utilizar para identificar al usuario y conocer cuales son sus credenciales (en el sentido de claims). En un host podré utilizar tokens bearer en la cabecera estándar “Authorization”, en otro una cabecera personalizada, en otro autenticación integrada de Windows o en otro autenticación básica. O incluso una combinación de ellas. Pero en cualquier caso, mi api, una vez autenticado el usuario por el mecanismo que considere el host debería responder de forma consistente a los requisitos de autorización.

Creando un middleware de autenticación personalizado

Por lo tanto, la aproximación está clara. El TestServer que me ofrece el paquete de NuGet Microsoft.Owin.Testing  no es más que un host de mi api en el que voy a configurar un mecanismo de autenticación personalizado que me permita, de forma sencilla, establecer las credenciales del usuario que quiero suplantar en la petición.

No voy a entrar en este momento en los detalles de cómo implementar un middleware de autenticación (lo dejamos para otro post), pero no es complicado. Básicamente nos vale con saber que, para el escenario de los test de integración, la información de los claims del usuario va a viajar codificada en Base64 en la cabecera estandar de autorización. Nada complicado. Pero cuidado. Nada seguro tampoco. !Ni se te ocurra utilizar este middleware en producción!

Los test de integración

La configuración del TestServer es sencilla. Lo único que tengo que hacer es crear la clase Startup que va a definir el comportamiento del servidor y allí, configurar el middleware de autenticación personalizada justo antes de usar mi api. Por ejemplo, así:

    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.UseTestServerAuthentication();

            var config = new HttpConfiguration();
            Sample.Api.WebApiConfiguration.Configure(config);
            app.UseWebApi(config);
        }
    }

En los ejemplos, voy usar xUnit. Cada test que se ejecuta, crea una nueva instancia de la clase que lo contiene. Además utiliza mecanismos estándar (nada de atributos) para la inicialización de los tests (en el constructor de la clase) y para la limpieza (implementar la interfaz IDisposable).

Por lo tanto, en el contexto de mis test, crearé la instancia del TestServer en el constructor de la clase de test de la siguiente forma:

        private readonly TestServer _server;
        private readonly HttpClient _userHttpCient;

        public VauesWithDefaultUserTests()
        {
            _server = TestServer.Create<Startup>();
            _userHttpCient = _server.HttpClient
                .WithDefaultIdentity(Identities.User);
        }

La magia ocurre en ese método de extensión WithDefaultIdentity . En este caso, añade una cabecera por defecto a todas las peticiones que se hagan con el HttpClient que configura. En esta cabecera, viajará la información necesaria para que el middleware de autenticación que hemos configurado cree la instancia del ClaimsPrincipal que representará la petición.

De la misma forma, hay otro método de extensión sobre la clase RequestBuilder que nos permitirá configurar las credenciales de una única petición:

        [Fact]
        public async Task WithRequestBuilder()
        {
            var response = await _server.CreateRequest("api/values")
                .WithIdentity(Identities.User)
                .GetAsync();

            response.EnsureSuccessStatusCode();
        }

Los dos métodos de extensión aceptan el mismo tipo de parámetro: un IEnumerable<Claim> en el que definiremos la información que tenga nuestro usuario en el contexto de las peticiones al api. Por ejemplo, en los ejemplos de antes, he creado una clase estática que definirá todas las identidades que quiera utilizar en el contexto de los tests:

    public static class Identities
    {
        public static readonly IEnumerable User = new[]
        {
            new Claim(ClaimTypes.NameIdentifier, "1"),
            new Claim(ClaimTypes.Name, "User"),
        };
    }

Con estos dos métodos de extensión y con el middleware configurado en la clase Startup que utiliza el servidor de test, es todo lo que necesitamos para poder realizar peticiones autenticadas a cualquier api.

Sencillo, ¿no? Desde luego, ya no tienes excusa para no tener unos bonitos test de integración de tu WebApi.

¡Que lo disfrutes!

Si tenéis curiosidad, el código fuente está aquí: https://github.com/hbiarge/Acheve.Owin.Testing.Security. En la carpeta samples podrás encontrar un ejemplo completo de uso de la librería.

csc HelloWorld.cs

He aprendido un buen montón de cosas leyendo y releyendo post en Geeks.ms. Siempre ha sido una de mis fuentes de información preferidas por la calidad del contenido y por que está bien leer temas técnicos en la lengua de Cervantes.

Así que me hace especial ilusión tener un pequeño espacio en esta comunidad para compartir mis experimentos, ideas y descubrimientos relacionados con el desarrollo en general, pero muy especialmente con la plataforma .Net. Sobre todo en estos momentos de cambio (y algo de incertidumbre, la verdad) que estamos viviendo en estos momentos.

Quiero aprovechar para darle las gracias a la gente de Plain Concepts por mantener esta comunidad viva y darme la oprtunidad de tener un hueco por aquí.

Si quieres ver algo del contenido que puede aparecer por aquí pásate por el otro blog que mantengo en arosbi.com.

Hasta pronto!

Hugo Biarge