Middleware en ASP.NET 6 - Introducción y conceptos básicos

¡Bienvenidos, queridos amigos desarrolladores, para comprender los middlewares y las solicitudes pipeline te presentamos esta nueva serie sobre middleware en .NET 6!
Vamos a hablar sobre qué es el middleware, qué hace, por qué lo usamos y mostraremos varias implementaciones de varios tipos de middleware. También hablaremos sobre la canalización (Request Pipeline) en la que existe el middleware, cómo crearlo y por qué es importante el orden de las operaciones en esa canalización. Finalmente, incluso mostraremos dos formas de ejecutar condicionalmente el middleware en la canalización para brindarte un control más detallado de lo que hace tu aplicación.
Esta publicación es la parte 1 de una serie de cuatro partes. En los siguientes enlaces puedes leer la parte 2, la parte 3 o la parte 4.
Conceptos básicos del software intermedio (Middleware)
En su forma más fundamental, cualquier interacción dada mediante HTTP
se compone de una solicitud (generalmente de un navegador o API
) y una respuesta. Los navegadores, las API
u otros solicitantes envían una solicitud y esperan a que el objetivo (un servidor web, otra API, algo más) devuelva una respuesta, es decir que les responda la solicitud.
El middleware se encuentra entre el solicitante y el objetivo, y puede modificar directamente la respuesta, registrar cosas o, en general, modificar el comportamiento del código que genera la respuesta, utilizando opcionalmente los datos dentro de la solicitud para hacerlo.
Echa un vistazo a este diagrama:
ASP.NET 6 implementa una canalización o pipeline que consta de una serie de clases de middleware. Una solicitud se filtra por la canalización hasta que llega a un punto en el que una clase de middleware (o algo invocado por ese middleware) crea una respuesta. Luego, la respuesta se vuelve a filtrar a través del middleware en orden inverso hasta que llega al solicitante.
Cada componente de middleware consta de un delegado de solicitud, un tipo específico de objeto en .NET que puede pasar el control de ejecución al siguiente objeto. Cada delegado de solicitud elige si pasa o no la solicitud al siguiente delegado en la canalización. Dependiendo de los resultados de sus cálculos, un middleware puede optar por no dar control de ejecución al siguiente elemento.
¿Para qué sirve el software intermedio o middleware?
Antes de continuar, probablemente deberíamos hablar sobre por qué podríamos querer usar middleware en una aplicación ASP.NET 6. Para hacer eso, hablemos de escenarios comunes.
Un escenario común en el que el middleware funciona bien (y en el que nos sumergiremos en una publicación posterior) es el registro. El middleware permite fácilmente el registro de solicitudes, incluidas direcciones URL y rutas, en un sistema de registro para generar informes más adelante.
El middleware también es un excelente lugar para realizar la autorización y la autenticación, el diagnóstico y el registro y manejo de errores.
En resumen, el middleware se usa para operaciones que no son una lógica específica de dominio y deben ocurrir en cada solicitud, o en la mayoría de las solicitudes.
Un archivo Program.cs simple
Echemos un vistazo a un archivo Program.cs
predeterminado ligeramente modificado generado por Visual Studio cuando crea una nueva aplicación web .NET 6
:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
//Add various middleware to the app pipeline
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
//Finally, run the app
app.Run();
Este archivo crea la canalización que utiliza la aplicación web ASP.NET 6 para procesar las solicitudes. También agrega un conjunto de middleware "predeterminado" a la canalización mediante métodos especiales proporcionados por .NET 6, como UseStaticFiles()
, que permite que la aplicación devuelva archivos estáticos, como .js
y .css
, y UseRouting()
, que agrega .NET Routing para manejar el enrutamiento de direcciones URL a los puntos finales del servidor. De forma predeterminada, si usas una aplicación ASP.NET 6, ya estás usando middleware.
Además, las aplicaciones ASP.NET 6 pueden usar bastante de este middleware "incorporado" o "built-in
" proporcionado por .NET 6. Se puede encontrar una lista completa en el sitio de documentos de Microsoft.
Un middleware personalizado simple
Vamos a crear un middleware súper simple que haga exactamente una cosa: devuelva "¡Hola, bienvenido a Estrada Web Group!
" como su respuesta.
En Program.cs
, agregamos un nuevo middleware usando el método Run()
, así:
app.Run(async context =>
{
await context.Response.WriteAsync("¡Hola, bienvenido a Estrada Web Group!");
});
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
Cuando ejecutemos la aplicación, veremos este resultado muy simple:
¡Listo! ¡Hemos implementado nuestro propio middleware personalizado en ASP.NET 6! Sin embargo, hay un problema, como veremos en breve.
Métodos Run(), Use() y Map()
Al leer el archivo Program.cs
, generalmente puedes identificar qué partes de la aplicación se consideran middleware observando el método utilizado para agregarlas a la canalización. Lo más común es que esto se haga mediante los métodos Run()
, Use()
y Map()
.
Método Run()
El método Run()
invoca un middleware en ese punto de la canalización. Sin embargo, ese middleware siempre será terminal, por ejemplo, el último middleware ejecutado antes de que se devuelva la respuesta. Recuerda este bloque de código de la sección anterior:
//Rest of Program.cs
app.Run(async context =>
{
await context.Response.WriteAsync("Hello Dear Readers!");
});
//Nothing below this point will be executed
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
Debido a la invocación de Run()
, no se ejecutará nada escrito después de esa invocación.
Método Use()
El método Use()
coloca un middleware en la canalización y permite que ese middleware pase el control al siguiente elemento de la canalización.
app.Use(async (context, next) =>
{
//Do work that does not write to the Response
await next.Invoke();
//Do logging or other work that does not write to the Response.
});
Ten en cuenta el siguiente parámetro. Ese parámetro es el delegado de solicitud mencionado anteriormente. Representa la siguiente pieza de middleware en la tubería, sin importar lo que sea. Al esperar en next.Invoke()
, estamos permitiendo que la solicitud avance hasta el siguiente middleware.
Además, ten en cuenta que generalmente es una mala práctica modificar la respuesta en este tipo de middleware a menos que la canalización deje de procesarse aquí. La modificación de una respuesta que ya se ha generado podría causar que la respuesta se corrompa.
Dicho todo esto, la mayoría de las veces querremos agregar middleware a la canalización con Use()
en lugar de Run()
.
Método Map()
El método Map()
es un caso especial, del que hablaremos más en una publicación posterior. Nos permite "ramificar" la tubería; podemos usarlo para invocar condicionalmente el middleware según la ruta de la solicitud.
app.Map("/branch1", HandleBranchOne);
app.Map("/branch2", HandleBranchTwo);
//Rest of Program.cs file
app.Run();
static void HandleBranchOne(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("You're on Branch 1!");
});
}
static void HandleBranchTwo(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("You're on Branch 2!");
});
}
Resumen
El middleware son módulos de código o clases que forman una canalización (pipeline
). Esa canalización procesa las solicitudes entrantes y las respuestas salientes. En el archivo Program.cs
, podemos colocar el middleware en un orden específico, que luego ejecutará las solicitudes en ese orden y las respuestas en orden inverso.
.NET 6 incluye una gran cantidad de middleware integrado, algunos de los cuales se utilizan en casi todas las aplicaciones web.
Finalmente, una forma de agregar middleware a una canalización de aplicaciones es usar los métodos Run()
, Use()
y Map()
. Sin embargo, esta puede no ser la forma más común, como veremos en nuestra próxima publicación.
¡A continuación!
Ahora que hemos cubierto los aspectos básicos del middleware, en la Parte 2 de esta serie vamos a pasar a la creación de algunas clases personalizadas que se pueden insertar en una canalización de aplicaciones.