Creando middlewares en ASP.NET Core

Una de las cosas más interesantes que tiene ASP.NET Core a mi gusto, es la idea de trabajar como se hace en node.js con frameworks como express.js. En este tipo de frameworks existe el concepto de middlewares, que no son más funciones aplicadas a los requests secuencialmente. Podemos pensar entonces; que los requests se procesan como una línea de montaje, pasando por distintas estaciones de trabajo (los middlewares) que basándose en la materia prima (el request), generan un producto (la respuesta).

Algunos ejemplos típicos de middlewares son los utilizados para logging, manejo de errores, autenticación y autorización. Muchos de estos ejemplos se pueden ver en los templates existentes (como por ejemplo el de Web Application).

Cada middleware tiene la responsabilidad de pasar el control al siguiente y puede ejecutar código tanto antes como después de la ejecución del siguiente middleware. El siguiente gráfico muestra una idea del flujo de ejecución de los diferentes middlewares.

Aplicación de middlewares. Imagen de la documentación oficial

Los middlewares se configuran dentro del método Configure de la clase utilizada como Startup. En el caso del template de Web Application nombrado antes, podemos ver los middlewares de:

  1. Manejo de excepciones, tanto para development como para el resto de los ambientes (todos los métodos que empiezan con Use* dentro del if else)
  2. Server de archivos estáticos (UseStaticFiles)
  3. Autenticación (UseIdentity)
  4. MVC (UseMvc)
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
  if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
app.UseBrowserLink();

}
else
{
app.UseExceptionHandler("/Home/Error");
}
  app.UseStaticFiles();

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

Algo importante para notar es que el orden en el cual se configuran los middlewares es importante. En este caso, primero se configuran el manejo de errores, así pueden atrapar todo tipo de excepciones que ocurran después. Luego de esto se configura todo el manejo de archivos estáticos y recién después de eso la autenticación, permitiendo que se devuelvan siempre los archivos estaticos tales como archivos de CSS, JavaScript o imágenes. Por último se configura MVC, logrando con esto contar con la información del usuario desde los controladores.

Creando nuestro primer Middleware

Para arrancar, vamos a crear una estructura básica de una app, para lo cual pueden ver mi artículo anterior y cambiar el archivo Startup.cs con lo siguiente. Con esto, cuando corramos la aplicación y naveguemos a localhost:5000, vamos a ver “Hello World!”.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
namespace aspnetcoreapp
{
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Hello World!");
});
}
}
}

Ahora vamos a crear nuestro primer middleware. Lo único que va a hacer es agregar a la respuesta un texto antes de la respuesta original (el Hello World!) y un texto después del mismo. Para esto vamos a agregar una llamada al método Use del application builder y vamos a pasarle una función que tiene 2 parámetros: context y next. El primero tiene toda la información incluyendo el request y la respuesta. El segundo es lo que vamos a utilizar para llamar al siguiente middleware, gracias al método Invoke.

public void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("Principio.");
await next.Invoke();
await context.Response.WriteAsync("Fin.");
});
  app.Run(async context =>
{
await context.Response.WriteAsync("Hello World!");
});
}

Si corremos la aplicación nuevamente vamos a ver el siguiente mensaje.

Principio.Hello World!Fin.

Creando middlewares más complejos

A la hora de crear middlewares más complejos, dejar todo el código dentro del método Configure de la clase Startup no es lo más útil. Para solucionar todos los problemas que esto conlleva, podemos separar el código en una nueva clase. Dicha clase, debe tener un constructor que reciba el RequestDelegate del siguiente middleware e implementar el método Invoke que recibe el HttpContext como parámetro. El código de nuestro Middleware de ejemplo quedaría de la siguiente forma.

using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace aspnetcoreapp
{
public class DemoMiddleware
{
private readonly RequestDelegate next;
    public DemoMiddleware(RequestDelegate next)
{
this.next = next;
}
    public async Task Invoke(HttpContext context)
{
await context.Response.WriteAsync("Principio.");
await this.next.Invoke(context);
await context.Response.WriteAsync("Fin.");
}
}
}

Para usar este middleware desde el método Configuration en la clase Startup podemos simplemente usar el extension method UseMiddleware<T> o crear un extension method de IApplicationBuilder que lo use.

public void Configure(IApplicationBuilder app)
{
app.UseMiddleware<DemoMiddleware>();
   app.Run(async context =>
{
await context.Response.WriteAsync("Hello World!");
});
}

Si ejecutamos nuevamente el proyecto, vamos a poder observar el mismo mensaje que la última vez, pero esta vez, nuestro middleware está separado del resto.

Like what you read? Give Nicolás Bello Camilletti a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.