Middleware de ASP.NET Core

Nota:

Esta no es la versión más reciente de este artículo. Para la versión actual, vea la versión ASP.NET Core 8.0 de este artículo.

Por Rick Anderson y Steve Smith

El software intermedio es un software que se ensambla en una canalización de una aplicación para controlar las solicitudes y las respuestas. Cada componente puede hacer lo siguiente:

  • Elegir si se pasa la solicitud al siguiente componente de la canalización.
  • Realizar trabajos antes y después del siguiente componente de la canalización.

Los delegados de solicitudes se usan para crear la canalización de solicitudes. Estos también controlan las solicitudes HTTP.

Los delegados de solicitudes se configuran con los métodos de extensión Run, Map y Use. Un delegado de solicitudes se puede especificar en línea como un método anónimo (denominado middleware en línea) o se puede definir en una clase reutilizable. Estas clases reutilizables y métodos anónimos en línea se conocen como software intermedio o componentes de software intermedio. Cada componente de software intermedio de la canalización de solicitudes es responsable de invocar el siguiente componente de la canalización o de cortocircuitar la canalización, en caso de ser necesario. Cuando un middleware se cortocircuita, se llama middleware de terminal porque impide el procesamiento de la solicitud por parte de middleware adicional.

En Migración de controladores y módulos HTTP a middleware de ASP.NET Core se explica la diferencia entre las canalizaciones de solicitudes en ASP.NET Core y ASP.NET 4.x y se proporcionan más ejemplos de middleware.

Análisis de código de middleware

ASP.NET Core incluye muchos analizadores de plataforma del compilador que inspeccionan el código de la aplicación para comprobar la calidad. Para más información, vea Análisis de código en aplicaciones ASP.NET Core.

Creación de una canalización de middleware con WebApplication

La canalización de solicitudes de ASP.NET Core consiste en una secuencia de delegados de solicitud a los que se llama de uno en uno. En el siguiente diagrama se muestra este concepto. El subproceso de ejecución sigue las flechas negras.

En el patrón de procesamiento de solicitudes se muestra una solicitud entrante, el procesamiento a través de tres middleware y la respuesta que sale de la aplicación. Cada middleware ejecuta su lógica y pasa la solicitud al middleware siguiente con la instrucción next(). Después de que el tercer middleware procese la solicitud, esta vuelve a pasar por los dos middleware anteriores en orden inverso. Esto se hace a modo de procesamiento adicional después de las instrucciones next() y antes de salir de la aplicación como respuesta al cliente.

Cada delegado puede realizar operaciones antes y después del siguiente. Los delegados que controlan excepciones deben llamarse al principio de la canalización para que puedan capturar las excepciones que se producen en las fases siguientes de la canalización.

La aplicación ASP.NET Core más sencilla posible configura un solo delegado de solicitudes que controla todas las solicitudes. En este caso no se incluye una canalización de solicitudes real. En su lugar, solo se llama a una única función anónima en respuesta a todas las solicitudes HTTP.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello world!");
});

app.Run();

Encadene varios delegados de solicitudes con Use. El parámetro next representa el siguiente delegado de la canalización. Si no llama al next parámetro, puede cortocircuitar la canalización. Normalmente, puede realizar acciones antes y después del delegado next, tal como se muestra en el ejemplo siguiente:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Use(async (context, next) =>
{
    // Do work that can write to the Response.
    await next.Invoke();
    // Do logging or other work that doesn't write to the Response.
});

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from 2nd delegate.");
});

app.Run();

Cortocircuito de la canalización de solicitudes

Cuando un delegado no pasa una solicitud al siguiente delegado, se denomina cortocircuitar la canalización de solicitudes. Este proceso es necesario muchas veces, ya que previene la realización de trabajo innecesario. Por ejemplo, el middleware de archivos estáticos puede actuar como middleware de terminal procesando una solicitud para un archivo estático y cortocircuitando el resto de la canalización. El middleware agregado a la canalización antes del middleware que finaliza el procesamiento sigue procesando código después de sus instrucciones next.Invoke. Sin embargo, consulte la siguiente advertencia sobre intentar escribir en una respuesta que ya se ha enviado.

Advertencia

No llame a next.Invoke durante o después de haber enviado la respuesta al cliente. Una vez iniciada una HttpResponse, los cambios producen una excepción. Por ejemplo, se inicia una excepción al establecer encabezados y un código de estado después de que se inicie la respuesta. Si escribe en el cuerpo de la respuesta después de llamar a next:

  • Puede provocar una violación del protocolo, como escribir más del valor indicado de Content-Length.
  • Puede dañar el formato del cuerpo, como escribir un pie de página HTML en un archivo CSS.

HasStarted es una sugerencia útil para indicar si se han enviado los encabezados o se han realizado escrituras en el cuerpo.

Para obtener más información, vea Middleware de cortocircuito después del enrutamiento.

Run delegados

Los delegados de Run no reciben un parámetro next. El primer delegado de Run siempre es terminal y finaliza la canalización. Run es una convención. Es posible que algunos componentes de middleware expongan métodos Run[Middleware] que se ejecutan al final de la canalización:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Use(async (context, next) =>
{
    // Do work that can write to the Response.
    await next.Invoke();
    // Do logging or other work that doesn't write to the Response.
});

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from 2nd delegate.");
});

app.Run();

Si quiere que los comentarios de código se traduzcan en más idiomas además del inglés, háganoslo saber en este problema de debate de GitHub.

En el ejemplo anterior, el delegado Run escribe "Hello from 2nd delegate." en la respuesta y, después, termina la canalización. Si se agrega otro delegado Use o Run después de Run, no se le llama.

Priorice la aplicación. Use una sobrecarga que requiera pasar el contexto al siguiente.

La aplicación de no asignación. Use el método de extensión :

  • Requiere pasar el contexto a next.
  • Guarda dos asignaciones internas por solicitud que son necesarias cuando se usa la otra sobrecarga.

Para más información, consulte este problema de GitHub.

Orden del middleware

En el diagrama siguiente se muestra la canalización de procesamiento de solicitudes completa para las aplicaciones de ASP.NET Core MVC y de Razor Pages. Puede ver cómo, en una aplicación típica, se ordenan los middleware existentes y dónde se agregan los middleware personalizados. Tiene control total sobre cómo reordenar los middleware existentes o insertar nuevos middleware personalizados según sea necesario para sus escenarios.

Canalización de middleware de ASP.NET Core

El middleware Punto de conexión del diagrama anterior ejecuta la canalización de filtro para el tipo de aplicación correspondiente, MVC o Razor Pages.

El middleware de Enrutamiento en el diagrama anterior se muestra siguiendo Archivos estáticos. Este es el orden que implementan las plantillas de proyecto mediante una llamada explícita a app.UseRouting. Si no llama a app.UseRouting, el middleware de Enrutamiento se ejecuta al principio de la canalización de forma predeterminada. Para más información, vea Enrutamiento.

Canalización de filtro de ASP.NET Core

El orden en el que se agregan los componentes de software intermedio en el método Program.cs define el orden en el que se invocarán los componentes de middleware en las solicitudes y el orden inverso de la respuesta. Por motivos de seguridad, rendimiento y funcionalidad, el orden es crítico.

El código destacado a continuación en Program.cs agrega componentes de middleware relacionados con la seguridad en el orden recomendado típico:

using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebMiddleware.Data;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
    ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
// app.UseCookiePolicy();

app.UseRouting();
// app.UseRateLimiter();
// app.UseRequestLocalization();
// app.UseCors();

app.UseAuthentication();
app.UseAuthorization();
// app.UseSession();
// app.UseResponseCompression();
// app.UseResponseCaching();

app.MapRazorPages();
app.MapDefaultControllerRoute();

app.Run();

En el código anterior:

  • El middleware que no se agrega al crear una aplicación web con cuentas de usuario individuales se convierte en comentario.
  • No todo el middleware aparece en este orden exacto, pero gran parte sí lo hace. Por ejemplo:
    • UseCors, UseAuthentication y UseAuthorization deben aparecer en el orden mostrado.
    • UseCors actualmente debe aparecer antes de UseResponseCaching. Este requisito se explica en la incidencia dotnet/aspnetcore #23218 de GitHub.
    • UseRequestLocalization debe aparecer antes que cualquier middleware que pueda comprobar la referencia cultural de la solicitud (por ejemplo, app.UseStaticFiles()).
    • Se debe llamar a UseRateLimiter después de UseRouting cuando se usan las API específicas del punto de conexión de limitación de velocidad. Por ejemplo, si se usa el atributo [EnableRateLimiting], se debe llamar a UseRateLimiter después de UseRouting. Al llamar solo a los limitadores globales, se puede llamar a UseRateLimiter antes de UseRouting.

En algunos escenarios, el middleware tiene un orden diferente. Por ejemplo, el almacenamiento en caché y la ordenación de compresión es específico del escenario y hay varias ordenaciones válidas. Por ejemplo:

app.UseResponseCaching();
app.UseResponseCompression();

Con el código anterior, el uso de la CPU podría reducirse guardando en caché la respuesta comprimida, pero es posible que termine el almacenamiento en caché de varias representaciones de un recurso con distintos algoritmos de compresión, como Gzip o Brotli.

La ordenación siguiente combina archivos estáticos para permitir el almacenamiento en caché de archivos estáticos comprimidos:

app.UseResponseCaching();
app.UseResponseCompression();
app.UseStaticFiles();

El siguiente código de Program.cs agrega los componentes de middleware para escenarios de aplicaciones comunes:

  1. Control de errores y excepciones
    • Cuando la aplicación se ejecuta en el entorno de desarrollo:
      • El middleware de la página de excepciones para el desarrollador (UseDeveloperExceptionPage) informa los errores en tiempo de ejecución de la aplicación.
      • El middleware de la página de errores de la base de datos (UseDatabaseErrorPage) informa los errores en tiempo de ejecución de la base de datos.
    • Cuando la aplicación se ejecuta en el entorno de producción:
      • El middleware del controlador de excepciones (UseExceptionHandler) detecta las excepciones generadas en los middlewares siguientes.
      • El middleware del protocolo de seguridad de transporte estricta de HTTP (HSTS) (UseHsts) agrega el encabezado Strict-Transport-Security.
  2. El middleware de redireccionamiento de HTTPS (UseHttpsRedirection) redirige las solicitudes HTTP a HTTPS.
  3. El middleware de archivos estáticos (UseStaticFiles) devuelve archivos estáticos y genera un cortocircuito más allá del procesamiento de la solicitud.
  4. El middleware de directivas de Cookie (UseCookiePolicy) permite que la aplicación cumpla con las normas del Reglamento general de protección de datos (RGPD) de la Unión Europea.
  5. Middleware de enrutamiento (UseRouting) para enrutar las solicitudes.
  6. El middleware de autenticación (UseAuthentication) intenta autenticar al usuarios antes de que se le permita acceder a los recursos protegidos.
  7. El middleware de autorización (UseAuthorization) autoriza a los usuarios a acceder a los recursos seguros.
  8. El middleware de sesiones (UseSession) establece y mantiene el estado de sesión. Si la aplicación usa el estado de sesión, llame al middleware de sesiones después del middleware de directivas de Cookie y antes del middleware de MVC.
  9. Middleware de enrutamiento de punto de conexión (UseEndpoints con MapRazorPages) para agregar puntos de conexión de Razor Pages a la canalización de solicitudes.
if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
    app.UseDatabaseErrorPage();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();
app.MapRazorPages();

En el código de ejemplo anterior, cada método de extensión de software intermedio se expone en WebApplicationBuilder a través del espacio de nombres de Microsoft.AspNetCore.Builder.

UseExceptionHandler es el primer componente de software intermedio que se agrega a la canalización. Por lo tanto, el software intermedio del controlador de excepciones detectará las excepciones que se produzcan en las llamadas posteriores.

El software intermedio de archivos estáticos se llama al principio de la canalización para que pueda controlar solicitudes y realizar cortocircuitos sin pasar por los componentes restantes. Este middleware no proporciona comprobaciones de autorización. Los archivos que proporciona el middleware de archivos estáticos, incluidos los de wwwroot, están disponibles de forma pública. Para consultar un enfoque sobre cómo proteger los archivos estáticos, vea Archivos estáticos en ASP.NET Core.

Si el software intermedio de archivos estáticos no controla la solicitud, se pasará al software intermedio de autenticación (UseAuthentication), que realizará la autenticación. Este software intermedio no cortocircuita las solicitudes sin autenticación. Aunque autentique solicitudes, la autorización (y también el rechazo) se producirán después de que MVC seleccione una página de Razor o un control y una acción de MVC concretos.

En el ejemplo siguiente se muestra un orden de software intermedio en el que el software intermedio de archivos estáticos controla las solicitudes de archivos estáticos antes del software intermedio de compresión de respuestas. Los archivos estáticos no se comprimen en este orden de software intermedio. Las respuestas de Razor Pages se pueden comprimir.

// Static files aren't compressed by Static File Middleware.
app.UseStaticFiles();

app.UseRouting();

app.UseResponseCompression();

app.MapRazorPages();

Para obtener información sobre las aplicaciones de página única, consulte Información general sobre aplicaciones de página única (SPA) en ASP.NET Core.

Orden de UseCors y UseStaticFiles

El orden para llamar a UseCors y UseStaticFiles depende de la aplicación. Para obtener más información, consulte la sección sobre el orden de UseCors y UseStaticFiles

Orden del middleware de encabezados reenviados

El Middleware de encabezados reenviados debe ejecutarse antes de otro middleware. Hacerlo en ese orden garantiza que el middleware que se basa en la información de encabezados reenviados pueda usar los valores de encabezado para procesarlos. Para ejecutar el middleware de encabezados reenviados después del middleware de diagnóstico y control de errores, vea Orden del middleware de encabezados reenviados.

Creación de una rama de la canalización de middleware

Las extensiones Map se usan como convenciones para la creación de ramas en la canalización. Map crea una rama de la canalización de solicitudes según las coincidencias de la ruta de solicitud proporcionada. Si la ruta de solicitud comienza con la ruta proporcionada, se ejecuta la creación de la rama.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Map("/map1", HandleMapTest1);

app.Map("/map2", HandleMapTest2);

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from non-Map delegate.");
});

app.Run();

static void HandleMapTest1(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map Test 1");
    });
}

static void HandleMapTest2(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map Test 2");
    });
}

En la siguiente tabla se muestran las solicitudes y las respuestas de http://localhost:1234 con el código anterior.

Solicitud Respuesta
localhost:1234 Saludos del delegado sin Map.
localhost:1234/map1 Prueba 1 de Map
localhost:1234/map2 Prueba 2 de Map
localhost:1234/map3 Saludos del delegado sin Map.

Cuando se usa Map, los segmentos de ruta que coincidan se eliminan de HttpRequest.Path y se anexan a HttpRequest.PathBase para cada solicitud.

Map admite la anidación, por ejemplo:

app.Map("/level1", level1App => {
    level1App.Map("/level2a", level2AApp => {
        // "/level1/level2a" processing
    });
    level1App.Map("/level2b", level2BApp => {
        // "/level1/level2b" processing
    });
});

Map puede hacer coincidir varios segmentos a la vez:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Map("/map1/seg1", HandleMultiSeg);

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from non-Map delegate.");
});

app.Run();

static void HandleMultiSeg(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map Test 1");
    });
}

MapWhen crea una rama de la canalización de solicitudes según el resultado del predicado proporcionado. Se puede usar cualquier predicado de tipo Func<HttpContext, bool> para asignar solicitudes a nuevas ramas de la canalización. En el ejemplo siguiente se usa un predicado para detectar la presencia de una branch variable de cadena de consulta:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapWhen(context => context.Request.Query.ContainsKey("branch"), HandleBranch);

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from non-Map delegate.");
});

app.Run();

static void HandleBranch(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        var branchVer = context.Request.Query["branch"];
        await context.Response.WriteAsync($"Branch used = {branchVer}");
    });
}

En la siguiente tabla se muestran las solicitudes y las respuestas de http://localhost:1234 con el código anterior:

Request Response
localhost:1234 Hello from non-Map delegate.
localhost:1234/?branch=main Branch used = main

UseWhen también crea una rama de la canalización de solicitudes según el resultado del predicado proporcionado. A diferencia de lo que sucede con MapWhen, esta rama se vuelve a unir a la canalización principal si no realiza un cortocircuito ni contiene un middleware de terminal:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.UseWhen(context => context.Request.Query.ContainsKey("branch"),
    appBuilder => HandleBranchAndRejoin(appBuilder));

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from non-Map delegate.");
});

app.Run();

void HandleBranchAndRejoin(IApplicationBuilder app)
{
    var logger = app.ApplicationServices.GetRequiredService<ILogger<Program>>(); 

    app.Use(async (context, next) =>
    {
        var branchVer = context.Request.Query["branch"];
        logger.LogInformation("Branch used = {branchVer}", branchVer);

        // Do work that doesn't write to the Response.
        await next();
        // Do other work that doesn't write to the Response.
    });
}

En el ejemplo anterior, se escribe una respuesta de Hello from non-Map delegate. para todas las solicitudes. Si la solicitud incluye una variable de cadena de consulta branch, su valor se registra antes de que se vuelva a unir la canalización principal.

Middleware integrado

ASP.NET Core incluye los componentes de software intermedio siguientes. En la columna Orden se proporcionan notas sobre la ubicación del middleware en la canalización de procesamiento de solicitudes, así como las condiciones con las que podría finalizar el procesamiento de solicitudes. Cuando un middleware cortocircuita la canalización de procesamiento de solicitudes e impide el procesamiento de una solicitud por parte de middleware descendente adicional, se llama middleware de terminal. Para más información sobre cómo cortocircuitar, consulte la sección Creación de una canalización de middleware con WebApplication.

Software intermedio Descripción Ordenar
Autenticación Proporciona compatibilidad con autenticación. Antes de que se necesite HttpContext.User. Terminal para devoluciones de llamadas OAuth.
Autorización Proporciona compatibilidad con la autorización. Inmediatamente después del middleware de autenticación.
Directiva de Cookie Realiza un seguimiento del consentimiento de los usuarios para almacenar información personal y aplica los estándares mínimos para los campos de las cookie, como secure y SameSite. Antes del middleware que emite las cookies. Ejemplos: autenticación, sesión y MVC (TempData).
CORS Configura el uso compartido de recursos entre orígenes. Antes de los componentes que usan CORS. UseCors actualmente debe ir antes de UseResponseCaching debido a este error.
DeveloperExceptionPage Genera una página con información de errores que está pensada para su uso solo en el entorno de desarrollo. Antes de los componentes que generan errores. Las plantillas de proyecto registran automáticamente este middleware como el primer middleware de la canalización cuando el entorno es de desarrollo.
Diagnóstico Varios middleware independientes que proporcionan una página de excepciones para el desarrollador, control de excepciones, páginas de códigos de estado y la página web predeterminada para las nuevas aplicaciones. Antes de los componentes que generan errores. Terminal para excepciones o con el fin de proporcionar la página web predeterminada para las nuevas aplicaciones.
Encabezados reenviados Reenvía encabezados con proxy a la solicitud actual. Antes de los componentes que consumen los campos actualizados. Ejemplos: esquema, host, IP de cliente y método.
Comprobación de estado Comprueba el estado de una aplicación ASP.NET Core y sus dependencias, como la comprobación de disponibilidad de base de datos. Terminal si una solicitud coincide con un punto de conexión de comprobación de estado.
Propagación de encabezados Permite propagar los encabezados HTTP de la solicitud entrante a las solicitudes del cliente HTTP salientes.
Registro HTTP Registra respuestas y solicitudes HTTP. Al principio de la canalización del middleware.
Invalidación del método HTTP Permite que una solicitud POST entrante invalide el método. Antes de los componentes que consumen el método actualizado.
Redireccionamiento de HTTPS Redirija todas las solicitudes HTTP a HTTPS. Antes de los componentes que consumen la dirección URL.
Seguridad de transporte estricta de HTTP (HSTS) Middleware de mejora de seguridad que agrega un encabezado de respuesta especial. Antes de que se envíen las respuestas y después de los componentes que modifican las solicitudes. Ejemplos: encabezados reenviados y reescritura de URL.
MVC Procesa las solicitudes con MVC/Razor Pages. Si hay una solicitud que coincida con una ruta, será final.
OWIN Puede interoperar con aplicaciones, servidores y software intermedio basados en OWIN. Si el software intermedio de OWIN procesa completamente la solicitud, será final.
Almacenamiento en caché de resultados Proporciona compatibilidad con el almacenamiento en caché de respuestas en función de la configuración. Antes de los componentes que requieren el almacenamiento en caché. UseRouting debe ser anterior a UseOutputCaching. UseCORS debe ser anterior a UseOutputCaching.
Almacenamiento en caché de respuestas Proporciona compatibilidad con la captura de respuestas. Requiere la participación del cliente para que funcione. Use el almacenamiento en caché de resultados para un control completo del servidor. Antes de los componentes que requieren el almacenamiento en caché. UseCORS debe ser anterior a UseResponseCaching. Normalmente no se recomienda en aplicaciones de interfaz de usuario, como Razor Pages, porque los exploradores suelen establecer encabezados de solicitud que impiden el almacenamiento en caché. El almacenamiento en caché de resultados beneficia a las aplicaciones de interfaz de usuario.
Descompresión de solicitudes Proporciona compatibilidad para descomprimir solicitudes. Antes de los componentes que leen el cuerpo de la solicitud.
Compresión de respuesta Proporciona compatibilidad con la compresión de respuestas. Antes de los componentes que requieren compresión.
Localización de solicitudes Proporciona compatibilidad con ubicación. Antes de los componentes que dependen de la ubicación. Debe aparecer después del middleware de enrutamiento cuando se usa RouteDataRequestCultureProvider.
Tiempos de espera de la solicitud Proporciona asistencia para configurar los tiempos de espera de la solicitud, el global y por punto de conexión. UseRequestTimeouts debe venir después de UseExceptionHandler, UseDeveloperExceptionPage y UseRouting.
Enrutamiento de punto de conexión Define y restringe las rutas de la solicitud. Terminal para rutas que coincidan.
SPA Controla todas las solicitudes desde este punto de la cadena de middleware devolviendo la página predeterminada de la aplicación de página única (SPA). En un punto posterior de la cadena, de modo que otro middleware dedicado a proporcionar archivos estáticos, acciones de MVC y otros elementos tenga prioridad.
Sesión Proporciona compatibilidad con la administración de sesiones de usuario. Antes de los componentes que requieren Session.
Archivos estáticos Proporciona compatibilidad con la proporción de archivos estáticos y la exploración de directorios. Si hay una solicitud que coincida con un archivo, será final.
Reescritura de URL Proporciona compatibilidad con la reescritura de direcciones URL y la redirección de solicitudes. Antes de los componentes que consumen la dirección URL.
W3CLogging Genera registros de acceso al servidor con el formato de archivo de registro extendido del W3C. Al principio de la canalización del middleware.
WebSockets Habilita el protocolo WebSockets. Antes de los componentes necesarios para aceptar solicitudes de WebSocket.

Recursos adicionales

Por Rick Anderson y Steve Smith

El software intermedio es un software que se ensambla en una canalización de una aplicación para controlar las solicitudes y las respuestas. Cada componente puede hacer lo siguiente:

  • Elegir si se pasa la solicitud al siguiente componente de la canalización.
  • Realizar trabajos antes y después del siguiente componente de la canalización.

Los delegados de solicitudes se usan para crear la canalización de solicitudes. Estos también controlan las solicitudes HTTP.

Los delegados de solicitudes se configuran con los métodos de extensión Run, Map y Use. Un delegado de solicitudes se puede especificar en línea como un método anónimo (denominado middleware en línea) o se puede definir en una clase reutilizable. Estas clases reutilizables y métodos anónimos en línea se conocen como software intermedio o componentes de software intermedio. Cada componente de software intermedio de la canalización de solicitudes es responsable de invocar el siguiente componente de la canalización o de cortocircuitar la canalización, en caso de ser necesario. Cuando un middleware se cortocircuita, se llama middleware de terminal porque impide el procesamiento de la solicitud por parte de middleware adicional.

En Migración de controladores y módulos HTTP a middleware de ASP.NET Core se explica la diferencia entre las canalizaciones de solicitudes en ASP.NET Core y ASP.NET 4.x y se proporcionan más ejemplos de middleware.

Análisis de código de middleware

ASP.NET Core incluye muchos analizadores de plataforma del compilador que inspeccionan el código de la aplicación para comprobar la calidad. Para más información, vea Análisis de código en aplicaciones ASP.NET Core.

Creación de una canalización de middleware con WebApplication

La canalización de solicitudes de ASP.NET Core consiste en una secuencia de delegados de solicitud a los que se llama de uno en uno. En el siguiente diagrama se muestra este concepto. El subproceso de ejecución sigue las flechas negras.

En el patrón de procesamiento de solicitudes se muestra una solicitud entrante, el procesamiento a través de tres middleware y la respuesta que sale de la aplicación. Cada middleware ejecuta su lógica y pasa la solicitud al middleware siguiente con la instrucción next(). Después de que el tercer middleware procese la solicitud, esta vuelve a pasar por los dos middleware anteriores en orden inverso. Esto se hace a modo de procesamiento adicional después de las instrucciones next() y antes de salir de la aplicación como respuesta al cliente.

Cada delegado puede realizar operaciones antes y después del siguiente. Los delegados que controlan excepciones deben llamarse al principio de la canalización para que puedan capturar las excepciones que se producen en las fases siguientes de la canalización.

La aplicación ASP.NET Core más sencilla posible configura un solo delegado de solicitudes que controla todas las solicitudes. En este caso no se incluye una canalización de solicitudes real. En su lugar, solo se llama a una única función anónima en respuesta a todas las solicitudes HTTP.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello world!");
});

app.Run();

Encadene varios delegados de solicitudes con Use. El parámetro next representa el siguiente delegado de la canalización. Si no llama al next parámetro, puede cortocircuitar la canalización. Normalmente, puede realizar acciones antes y después del delegado next, tal como se muestra en el ejemplo siguiente:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Use(async (context, next) =>
{
    // Do work that can write to the Response.
    await next.Invoke();
    // Do logging or other work that doesn't write to the Response.
});

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from 2nd delegate.");
});

app.Run();

Cuando un delegado no pasa una solicitud al siguiente delegado, se denomina cortocircuitar la canalización de solicitudes. Este proceso es necesario muchas veces, ya que previene la realización de trabajo innecesario. Por ejemplo, el middleware de archivos estáticos puede actuar como middleware de terminal procesando una solicitud para un archivo estático y cortocircuitando el resto de la canalización. El middleware agregado a la canalización antes del middleware que finaliza el procesamiento sigue procesando código después de sus instrucciones next.Invoke. Sin embargo, consulte la siguiente advertencia sobre intentar escribir en una respuesta que ya se ha enviado.

Advertencia

No llame a next.Invoke después de haber enviado la respuesta al cliente. Si se modifica HttpResponse después de haber iniciado la respuesta, se producirá una excepción. Por ejemplo, se inicia una excepción al establecer encabezados y un código de estado. Si escribe en el cuerpo de la respuesta después de llamar a next:

  • Puede provocar una infracción del protocolo. Por ejemplo, si escribe más de la longitud Content-Length establecida.
  • Puede dañar el formato del cuerpo. Por ejemplo, si escribe un pie de página en HTML en un archivo CSS.

HasStarted es una sugerencia útil para indicar si se han enviado los encabezados o se han realizado escrituras en el cuerpo.

Los delegados de Run no reciben un parámetro next. El primer delegado de Run siempre es terminal y finaliza la canalización. Run es una convención. Es posible que algunos componentes de middleware expongan métodos Run[Middleware] que se ejecutan al final de la canalización:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Use(async (context, next) =>
{
    // Do work that can write to the Response.
    await next.Invoke();
    // Do logging or other work that doesn't write to the Response.
});

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from 2nd delegate.");
});

app.Run();

Si quiere que los comentarios de código se traduzcan en más idiomas además del inglés, háganoslo saber en este problema de debate de GitHub.

En el ejemplo anterior, el delegado Run escribe "Hello from 2nd delegate." en la respuesta y, después, termina la canalización. Si se agrega otro delegado Use o Run después de Run, no se le llama.

Priorice la aplicación. Use una sobrecarga que requiera pasar el contexto al siguiente.

La aplicación de no asignación. Use el método de extensión :

  • Requiere pasar el contexto a next.
  • Guarda dos asignaciones internas por solicitud que son necesarias cuando se usa la otra sobrecarga.

Para más información, consulte este problema de GitHub.

Orden del middleware

En el diagrama siguiente se muestra la canalización de procesamiento de solicitudes completa para las aplicaciones de ASP.NET Core MVC y de Razor Pages. Puede ver cómo, en una aplicación típica, se ordenan los middleware existentes y dónde se agregan los middleware personalizados. Tiene control total sobre cómo reordenar los middleware existentes o insertar nuevos middleware personalizados según sea necesario para sus escenarios.

Canalización de middleware de ASP.NET Core

El middleware Punto de conexión del diagrama anterior ejecuta la canalización de filtro para el tipo de aplicación correspondiente, MVC o Razor Pages.

El middleware de Enrutamiento en el diagrama anterior se muestra siguiendo Archivos estáticos. Este es el orden que implementan las plantillas de proyecto mediante una llamada explícita a app.UseRouting. Si no llama a app.UseRouting, el middleware de Enrutamiento se ejecuta al principio de la canalización de forma predeterminada. Para más información, vea Enrutamiento.

Canalización de filtro de ASP.NET Core

El orden en el que se agregan los componentes de software intermedio en el método Program.cs define el orden en el que se invocarán los componentes de middleware en las solicitudes y el orden inverso de la respuesta. Por motivos de seguridad, rendimiento y funcionalidad, el orden es crítico.

El código destacado a continuación en Program.cs agrega componentes de middleware relacionados con la seguridad en el orden recomendado típico:

using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebMiddleware.Data;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
    ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
// app.UseCookiePolicy();

app.UseRouting();
// app.UseRateLimiter();
// app.UseRequestLocalization();
// app.UseCors();

app.UseAuthentication();
app.UseAuthorization();
// app.UseSession();
// app.UseResponseCompression();
// app.UseResponseCaching();

app.MapRazorPages();
app.MapDefaultControllerRoute();

app.Run();

En el código anterior:

  • El middleware que no se agrega al crear una aplicación web con cuentas de usuario individuales se convierte en comentario.
  • No todo el middleware aparece en este orden exacto, pero gran parte sí lo hace. Por ejemplo:
    • UseCors, UseAuthentication y UseAuthorization deben aparecer en el orden mostrado.
    • UseCors actualmente debe aparecer antes de UseResponseCaching. Este requisito se explica en la incidencia dotnet/aspnetcore #23218 de GitHub.
    • UseRequestLocalization debe aparecer antes que cualquier middleware que pueda comprobar la referencia cultural de la solicitud (por ejemplo, app.UseStaticFiles()).
    • Se debe llamar a UseRateLimiter después de UseRouting cuando se usan las API específicas del punto de conexión de limitación de velocidad. Por ejemplo, si se usa el atributo [EnableRateLimiting], se debe llamar a UseRateLimiter después de UseRouting. Al llamar solo a los limitadores globales, se puede llamar a UseRateLimiter antes de UseRouting.

En algunos escenarios, el middleware tiene un orden diferente. Por ejemplo, el almacenamiento en caché y la ordenación de compresión es específico del escenario y hay varias ordenaciones válidas. Por ejemplo:

app.UseResponseCaching();
app.UseResponseCompression();

Con el código anterior, el uso de la CPU podría reducirse guardando en caché la respuesta comprimida, pero es posible que termine el almacenamiento en caché de varias representaciones de un recurso con distintos algoritmos de compresión, como Gzip o Brotli.

La ordenación siguiente combina archivos estáticos para permitir el almacenamiento en caché de archivos estáticos comprimidos:

app.UseResponseCaching();
app.UseResponseCompression();
app.UseStaticFiles();

El siguiente código de Program.cs agrega los componentes de middleware para escenarios de aplicaciones comunes:

  1. Control de errores y excepciones
    • Cuando la aplicación se ejecuta en el entorno de desarrollo:
      • El middleware de la página de excepciones para el desarrollador (UseDeveloperExceptionPage) informa los errores en tiempo de ejecución de la aplicación.
      • El middleware de la página de errores de la base de datos (UseDatabaseErrorPage) informa los errores en tiempo de ejecución de la base de datos.
    • Cuando la aplicación se ejecuta en el entorno de producción:
      • El middleware del controlador de excepciones (UseExceptionHandler) detecta las excepciones generadas en los middlewares siguientes.
      • El middleware del protocolo de seguridad de transporte estricta de HTTP (HSTS) (UseHsts) agrega el encabezado Strict-Transport-Security.
  2. El middleware de redireccionamiento de HTTPS (UseHttpsRedirection) redirige las solicitudes HTTP a HTTPS.
  3. El middleware de archivos estáticos (UseStaticFiles) devuelve archivos estáticos y genera un cortocircuito más allá del procesamiento de la solicitud.
  4. El middleware de directivas de Cookie (UseCookiePolicy) permite que la aplicación cumpla con las normas del Reglamento general de protección de datos (RGPD) de la Unión Europea.
  5. Middleware de enrutamiento (UseRouting) para enrutar las solicitudes.
  6. El middleware de autenticación (UseAuthentication) intenta autenticar al usuarios antes de que se le permita acceder a los recursos protegidos.
  7. El middleware de autorización (UseAuthorization) autoriza a los usuarios a acceder a los recursos seguros.
  8. El middleware de sesiones (UseSession) establece y mantiene el estado de sesión. Si la aplicación usa el estado de sesión, llame al middleware de sesiones después del middleware de directivas de Cookie y antes del middleware de MVC.
  9. Middleware de enrutamiento de punto de conexión (UseEndpoints con MapRazorPages) para agregar puntos de conexión de Razor Pages a la canalización de solicitudes.
if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
    app.UseDatabaseErrorPage();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();
app.MapRazorPages();

En el código de ejemplo anterior, cada método de extensión de software intermedio se expone en WebApplicationBuilder a través del espacio de nombres de Microsoft.AspNetCore.Builder.

UseExceptionHandler es el primer componente de software intermedio que se agrega a la canalización. Por lo tanto, el software intermedio del controlador de excepciones detectará las excepciones que se produzcan en las llamadas posteriores.

El software intermedio de archivos estáticos se llama al principio de la canalización para que pueda controlar solicitudes y realizar cortocircuitos sin pasar por los componentes restantes. Este middleware no proporciona comprobaciones de autorización. Los archivos que proporciona el middleware de archivos estáticos, incluidos los de wwwroot, están disponibles de forma pública. Para consultar un enfoque sobre cómo proteger los archivos estáticos, vea Archivos estáticos en ASP.NET Core.

Si el software intermedio de archivos estáticos no controla la solicitud, se pasará al software intermedio de autenticación (UseAuthentication), que realizará la autenticación. Este software intermedio no cortocircuita las solicitudes sin autenticación. Aunque autentique solicitudes, la autorización (y también el rechazo) se producirán después de que MVC seleccione una página de Razor o un control y una acción de MVC concretos.

En el ejemplo siguiente se muestra un orden de software intermedio en el que el software intermedio de archivos estáticos controla las solicitudes de archivos estáticos antes del software intermedio de compresión de respuestas. Los archivos estáticos no se comprimen en este orden de software intermedio. Las respuestas de Razor Pages se pueden comprimir.

// Static files aren't compressed by Static File Middleware.
app.UseStaticFiles();

app.UseRouting();

app.UseResponseCompression();

app.MapRazorPages();

Para obtener más información sobre las aplicaciones de página única, consulte las guías de las plantillas de proyecto de React y Angular.

Orden de UseCors y UseStaticFiles

El orden para llamar a UseCors y UseStaticFiles depende de la aplicación. Para obtener más información, consulte la sección sobre el orden de UseCors y UseStaticFiles

Orden del middleware de encabezados reenviados

El Middleware de encabezados reenviados debe ejecutarse antes de otro middleware. Hacerlo en ese orden garantiza que el middleware que se basa en la información de encabezados reenviados pueda usar los valores de encabezado para procesarlos. Para ejecutar el middleware de encabezados reenviados después del middleware de diagnóstico y control de errores, vea Orden del middleware de encabezados reenviados.

Creación de una rama de la canalización de middleware

Las extensiones Map se usan como convenciones para la creación de ramas en la canalización. Map crea una rama de la canalización de solicitudes según las coincidencias de la ruta de solicitud proporcionada. Si la ruta de solicitud comienza con la ruta proporcionada, se ejecuta la creación de la rama.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Map("/map1", HandleMapTest1);

app.Map("/map2", HandleMapTest2);

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from non-Map delegate.");
});

app.Run();

static void HandleMapTest1(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map Test 1");
    });
}

static void HandleMapTest2(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map Test 2");
    });
}

En la siguiente tabla se muestran las solicitudes y las respuestas de http://localhost:1234 con el código anterior.

Solicitud Respuesta
localhost:1234 Saludos del delegado sin Map.
localhost:1234/map1 Prueba 1 de Map
localhost:1234/map2 Prueba 2 de Map
localhost:1234/map3 Saludos del delegado sin Map.

Cuando se usa Map, los segmentos de ruta que coincidan se eliminan de HttpRequest.Path y se anexan a HttpRequest.PathBase para cada solicitud.

Map admite la anidación, por ejemplo:

app.Map("/level1", level1App => {
    level1App.Map("/level2a", level2AApp => {
        // "/level1/level2a" processing
    });
    level1App.Map("/level2b", level2BApp => {
        // "/level1/level2b" processing
    });
});

Map puede hacer coincidir varios segmentos a la vez:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Map("/map1/seg1", HandleMultiSeg);

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from non-Map delegate.");
});

app.Run();

static void HandleMultiSeg(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map Test 1");
    });
}

MapWhen crea una rama de la canalización de solicitudes según el resultado del predicado proporcionado. Se puede usar cualquier predicado de tipo Func<HttpContext, bool> para asignar solicitudes a nuevas ramas de la canalización. En el ejemplo siguiente se usa un predicado para detectar la presencia de una branch variable de cadena de consulta:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapWhen(context => context.Request.Query.ContainsKey("branch"), HandleBranch);

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from non-Map delegate.");
});

app.Run();

static void HandleBranch(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        var branchVer = context.Request.Query["branch"];
        await context.Response.WriteAsync($"Branch used = {branchVer}");
    });
}

En la siguiente tabla se muestran las solicitudes y las respuestas de http://localhost:1234 con el código anterior:

Request Response
localhost:1234 Hello from non-Map delegate.
localhost:1234/?branch=main Branch used = main

UseWhen también crea una rama de la canalización de solicitudes según el resultado del predicado proporcionado. A diferencia de lo que sucede con MapWhen, esta rama se vuelve a unir a la canalización principal si no realiza un cortocircuito ni contiene un middleware de terminal:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.UseWhen(context => context.Request.Query.ContainsKey("branch"),
    appBuilder => HandleBranchAndRejoin(appBuilder));

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from non-Map delegate.");
});

app.Run();

void HandleBranchAndRejoin(IApplicationBuilder app)
{
    var logger = app.ApplicationServices.GetRequiredService<ILogger<Program>>(); 

    app.Use(async (context, next) =>
    {
        var branchVer = context.Request.Query["branch"];
        logger.LogInformation("Branch used = {branchVer}", branchVer);

        // Do work that doesn't write to the Response.
        await next();
        // Do other work that doesn't write to the Response.
    });
}

En el ejemplo anterior, se escribe una respuesta de Hello from non-Map delegate. para todas las solicitudes. Si la solicitud incluye una variable de cadena de consulta branch, su valor se registra antes de que se vuelva a unir la canalización principal.

Middleware integrado

ASP.NET Core incluye los componentes de software intermedio siguientes. En la columna Orden se proporcionan notas sobre la ubicación del middleware en la canalización de procesamiento de solicitudes, así como las condiciones con las que podría finalizar el procesamiento de solicitudes. Cuando un middleware cortocircuita la canalización de procesamiento de solicitudes e impide el procesamiento de una solicitud por parte de middleware descendente adicional, se llama middleware de terminal. Para más información sobre cómo cortocircuitar, consulte la sección Creación de una canalización de middleware con WebApplication.

Software intermedio Descripción Ordenar
Autenticación Proporciona compatibilidad con autenticación. Antes de que se necesite HttpContext.User. Terminal para devoluciones de llamadas OAuth.
Autorización Proporciona compatibilidad con la autorización. Inmediatamente después del middleware de autenticación.
Directiva de Cookie Realiza un seguimiento del consentimiento de los usuarios para almacenar información personal y aplica los estándares mínimos para los campos de las cookie, como secure y SameSite. Antes del middleware que emite las cookies. Ejemplos: autenticación, sesión y MVC (TempData).
CORS Configura el uso compartido de recursos entre orígenes. Antes de los componentes que usan CORS. UseCors actualmente debe ir antes de UseResponseCaching debido a este error.
DeveloperExceptionPage Genera una página con información de errores que está pensada para su uso solo en el entorno de desarrollo. Antes de los componentes que generan errores. Las plantillas de proyecto registran automáticamente este middleware como el primer middleware de la canalización cuando el entorno es de desarrollo.
Diagnóstico Varios middleware independientes que proporcionan una página de excepciones para el desarrollador, control de excepciones, páginas de códigos de estado y la página web predeterminada para las nuevas aplicaciones. Antes de los componentes que generan errores. Terminal para excepciones o con el fin de proporcionar la página web predeterminada para las nuevas aplicaciones.
Encabezados reenviados Reenvía encabezados con proxy a la solicitud actual. Antes de los componentes que consumen los campos actualizados. Ejemplos: esquema, host, IP de cliente y método.
Comprobación de estado Comprueba el estado de una aplicación ASP.NET Core y sus dependencias, como la comprobación de disponibilidad de base de datos. Terminal si una solicitud coincide con un punto de conexión de comprobación de estado.
Propagación de encabezados Permite propagar los encabezados HTTP de la solicitud entrante a las solicitudes del cliente HTTP salientes.
Registro HTTP Registra respuestas y solicitudes HTTP. Al principio de la canalización del middleware.
Invalidación del método HTTP Permite que una solicitud POST entrante invalide el método. Antes de los componentes que consumen el método actualizado.
Redireccionamiento de HTTPS Redirija todas las solicitudes HTTP a HTTPS. Antes de los componentes que consumen la dirección URL.
Seguridad de transporte estricta de HTTP (HSTS) Middleware de mejora de seguridad que agrega un encabezado de respuesta especial. Antes de que se envíen las respuestas y después de los componentes que modifican las solicitudes. Ejemplos: encabezados reenviados y reescritura de URL.
MVC Procesa las solicitudes con MVC/Razor Pages. Si hay una solicitud que coincida con una ruta, será final.
OWIN Puede interoperar con aplicaciones, servidores y software intermedio basados en OWIN. Si el software intermedio de OWIN procesa completamente la solicitud, será final.
Almacenamiento en caché de resultados Proporciona compatibilidad con el almacenamiento en caché de respuestas en función de la configuración. Antes de los componentes que requieren el almacenamiento en caché. UseRouting debe ser anterior a UseOutputCaching. UseCORS debe ser anterior a UseOutputCaching.
Almacenamiento en caché de respuestas Proporciona compatibilidad con la captura de respuestas. Requiere la participación del cliente para que funcione. Use el almacenamiento en caché de resultados para un control completo del servidor. Antes de los componentes que requieren el almacenamiento en caché. UseCORS debe ser anterior a UseResponseCaching. Normalmente no se recomienda en aplicaciones de interfaz de usuario, como Razor Pages, porque los exploradores suelen establecer encabezados de solicitud que impiden el almacenamiento en caché. El almacenamiento en caché de resultados beneficia a las aplicaciones de interfaz de usuario.
Descompresión de solicitudes Proporciona compatibilidad para descomprimir solicitudes. Antes de los componentes que leen el cuerpo de la solicitud.
Compresión de respuesta Proporciona compatibilidad con la compresión de respuestas. Antes de los componentes que requieren compresión.
Localización de solicitudes Proporciona compatibilidad con ubicación. Antes de los componentes que dependen de la ubicación. Debe aparecer después del middleware de enrutamiento cuando se usa RouteDataRequestCultureProvider.
Enrutamiento de punto de conexión Define y restringe las rutas de la solicitud. Terminal para rutas que coincidan.
SPA Controla todas las solicitudes desde este punto de la cadena de middleware devolviendo la página predeterminada de la aplicación de página única (SPA). En un punto posterior de la cadena, de modo que otro middleware dedicado a proporcionar archivos estáticos, acciones de MVC y otros elementos tenga prioridad.
Sesión Proporciona compatibilidad con la administración de sesiones de usuario. Antes de los componentes que requieren Session.
Archivos estáticos Proporciona compatibilidad con la proporción de archivos estáticos y la exploración de directorios. Si hay una solicitud que coincida con un archivo, será final.
Reescritura de URL Proporciona compatibilidad con la reescritura de direcciones URL y la redirección de solicitudes. Antes de los componentes que consumen la dirección URL.
W3CLogging Genera registros de acceso al servidor con el formato de archivo de registro extendido del W3C. Al principio de la canalización del middleware.
WebSockets Habilita el protocolo WebSockets. Antes de los componentes necesarios para aceptar solicitudes de WebSocket.

Recursos adicionales

Por Rick Anderson y Steve Smith

El software intermedio es un software que se ensambla en una canalización de una aplicación para controlar las solicitudes y las respuestas. Cada componente puede hacer lo siguiente:

  • Elegir si se pasa la solicitud al siguiente componente de la canalización.
  • Realizar trabajos antes y después del siguiente componente de la canalización.

Los delegados de solicitudes se usan para crear la canalización de solicitudes. Estos también controlan las solicitudes HTTP.

Los delegados de solicitudes se configuran con los métodos de extensión Run, Map y Use. Un delegado de solicitudes se puede especificar en línea como un método anónimo (denominado middleware en línea) o se puede definir en una clase reutilizable. Estas clases reutilizables y métodos anónimos en línea se conocen como software intermedio o componentes de software intermedio. Cada componente de software intermedio de la canalización de solicitudes es responsable de invocar el siguiente componente de la canalización o de cortocircuitar la canalización, en caso de ser necesario. Cuando un middleware se cortocircuita, se llama middleware de terminal porque impide el procesamiento de la solicitud por parte de middleware adicional.

En Migración de controladores y módulos HTTP a middleware de ASP.NET Core se explica la diferencia entre las canalizaciones de solicitudes en ASP.NET Core y ASP.NET 4.x y se proporcionan más ejemplos de middleware.

Análisis de código de middleware

ASP.NET Core incluye muchos analizadores de plataforma del compilador que inspeccionan el código de la aplicación para comprobar la calidad. Para más información, vea Análisis de código en aplicaciones ASP.NET Core.

Creación de una canalización de middleware con WebApplication

La canalización de solicitudes de ASP.NET Core consiste en una secuencia de delegados de solicitud a los que se llama de uno en uno. En el siguiente diagrama se muestra este concepto. El subproceso de ejecución sigue las flechas negras.

En el patrón de procesamiento de solicitudes se muestra una solicitud entrante, el procesamiento a través de tres middleware y la respuesta que sale de la aplicación. Cada middleware ejecuta su lógica y pasa la solicitud al middleware siguiente con la instrucción next(). Después de que el tercer middleware procese la solicitud, esta vuelve a pasar por los dos middleware anteriores en orden inverso. Esto se hace a modo de procesamiento adicional después de las instrucciones next() y antes de salir de la aplicación como respuesta al cliente.

Cada delegado puede realizar operaciones antes y después del siguiente. Los delegados que controlan excepciones deben llamarse al principio de la canalización para que puedan capturar las excepciones que se producen en las fases siguientes de la canalización.

La aplicación ASP.NET Core más sencilla posible configura un solo delegado de solicitudes que controla todas las solicitudes. En este caso no se incluye una canalización de solicitudes real. En su lugar, solo se llama a una única función anónima en respuesta a todas las solicitudes HTTP.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello world!");
});

app.Run();

Encadene varios delegados de solicitudes con Use. El parámetro next representa el siguiente delegado de la canalización. Si no llama al next parámetro, puede cortocircuitar la canalización. Normalmente, puede realizar acciones antes y después del delegado next, tal como se muestra en el ejemplo siguiente:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Use(async (context, next) =>
{
    // Do work that can write to the Response.
    await next.Invoke();
    // Do logging or other work that doesn't write to the Response.
});

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from 2nd delegate.");
});

app.Run();

Cuando un delegado no pasa una solicitud al siguiente delegado, se denomina cortocircuitar la canalización de solicitudes. Este proceso es necesario muchas veces, ya que previene la realización de trabajo innecesario. Por ejemplo, el middleware de archivos estáticos puede actuar como middleware de terminal procesando una solicitud para un archivo estático y cortocircuitando el resto de la canalización. El middleware agregado a la canalización antes del middleware que finaliza el procesamiento sigue procesando código después de sus instrucciones next.Invoke. Sin embargo, consulte la siguiente advertencia sobre intentar escribir en una respuesta que ya se ha enviado.

Advertencia

No llame a next.Invoke después de haber enviado la respuesta al cliente. Si se modifica HttpResponse después de haber iniciado la respuesta, se producirá una excepción. Por ejemplo, se inicia una excepción al establecer encabezados y un código de estado. Si escribe en el cuerpo de la respuesta después de llamar a next:

  • Puede provocar una infracción del protocolo. Por ejemplo, si escribe más de la longitud Content-Length establecida.
  • Puede dañar el formato del cuerpo. Por ejemplo, si escribe un pie de página en HTML en un archivo CSS.

HasStarted es una sugerencia útil para indicar si se han enviado los encabezados o se han realizado escrituras en el cuerpo.

Los delegados de Run no reciben un parámetro next. El primer delegado de Run siempre es terminal y finaliza la canalización. Run es una convención. Es posible que algunos componentes de middleware expongan métodos Run[Middleware] que se ejecutan al final de la canalización:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Use(async (context, next) =>
{
    // Do work that can write to the Response.
    await next.Invoke();
    // Do logging or other work that doesn't write to the Response.
});

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from 2nd delegate.");
});

app.Run();

Si quiere que los comentarios de código se traduzcan en más idiomas además del inglés, háganoslo saber en este problema de debate de GitHub.

En el ejemplo anterior, el delegado Run escribe "Hello from 2nd delegate." en la respuesta y, después, termina la canalización. Si se agrega otro delegado Use o Run después de Run, no se le llama.

Priorice la aplicación. Use una sobrecarga que requiera pasar el contexto al siguiente.

La aplicación de no asignación. Use el método de extensión :

  • Requiere pasar el contexto a next.
  • Guarda dos asignaciones internas por solicitud que son necesarias cuando se usa la otra sobrecarga.

Para más información, consulte este problema de GitHub.

Orden del middleware

En el diagrama siguiente se muestra la canalización de procesamiento de solicitudes completa para las aplicaciones de ASP.NET Core MVC y de Razor Pages. Puede ver cómo, en una aplicación típica, se ordenan los middleware existentes y dónde se agregan los middleware personalizados. Tiene control total sobre cómo reordenar los middleware existentes o insertar nuevos middleware personalizados según sea necesario para sus escenarios.

Canalización de middleware de ASP.NET Core

El middleware Punto de conexión del diagrama anterior ejecuta la canalización de filtro para el tipo de aplicación correspondiente, MVC o Razor Pages.

El middleware de Enrutamiento en el diagrama anterior se muestra siguiendo Archivos estáticos. Este es el orden que implementan las plantillas de proyecto mediante una llamada explícita a app.UseRouting. Si no llama a app.UseRouting, el middleware de Enrutamiento se ejecuta al principio de la canalización de forma predeterminada. Para más información, vea Enrutamiento.

Canalización de filtro de ASP.NET Core

El orden en el que se agregan los componentes de software intermedio en el método Program.cs define el orden en el que se invocarán los componentes de middleware en las solicitudes y el orden inverso de la respuesta. Por motivos de seguridad, rendimiento y funcionalidad, el orden es crítico.

El código destacado a continuación en Program.cs agrega componentes de middleware relacionados con la seguridad en el orden recomendado típico:

using IndividualAccountsExample.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
// app.UseCookiePolicy();

app.UseRouting();
// app.UseRequestLocalization();
// app.UseCors();

app.UseAuthentication();
app.UseAuthorization();
// app.UseSession();
// app.UseResponseCompression();
// app.UseResponseCaching();

app.MapRazorPages();
app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

En el código anterior:

  • El middleware que no se agrega al crear una aplicación web con cuentas de usuario individuales se convierte en comentario.
  • No todo el middleware aparece en este orden exacto, pero gran parte sí lo hace. Por ejemplo:
    • UseCors, UseAuthentication y UseAuthorization deben aparecer en el orden mostrado.
    • UseCors actualmente debe aparecer antes de UseResponseCaching. Este requisito se explica en la incidencia dotnet/aspnetcore #23218 de GitHub.
    • UseRequestLocalization debe aparecer antes que cualquier middleware que pueda comprobar la referencia cultural de la solicitud (por ejemplo, app.UseMvcWithDefaultRoute()).

En algunos escenarios, el middleware tiene un orden diferente. Por ejemplo, el almacenamiento en caché y la ordenación de compresión es específico del escenario y hay varias ordenaciones válidas. Por ejemplo:

app.UseResponseCaching();
app.UseResponseCompression();

Con el código anterior, el uso de la CPU podría reducirse guardando en caché la respuesta comprimida, pero es posible que termine el almacenamiento en caché de varias representaciones de un recurso con distintos algoritmos de compresión, como Gzip o Brotli.

La ordenación siguiente combina archivos estáticos para permitir el almacenamiento en caché de archivos estáticos comprimidos:

app.UseResponseCaching();
app.UseResponseCompression();
app.UseStaticFiles();

El siguiente código de Program.cs agrega los componentes de middleware para escenarios de aplicaciones comunes:

  1. Control de errores y excepciones
    • Cuando la aplicación se ejecuta en el entorno de desarrollo:
      • El middleware de la página de excepciones para el desarrollador (UseDeveloperExceptionPage) informa los errores en tiempo de ejecución de la aplicación.
      • El middleware de la página de errores de la base de datos (UseDatabaseErrorPage) informa los errores en tiempo de ejecución de la base de datos.
    • Cuando la aplicación se ejecuta en el entorno de producción:
      • El middleware del controlador de excepciones (UseExceptionHandler) detecta las excepciones generadas en los middlewares siguientes.
      • El middleware del protocolo de seguridad de transporte estricta de HTTP (HSTS) (UseHsts) agrega el encabezado Strict-Transport-Security.
  2. El middleware de redireccionamiento de HTTPS (UseHttpsRedirection) redirige las solicitudes HTTP a HTTPS.
  3. El middleware de archivos estáticos (UseStaticFiles) devuelve archivos estáticos y genera un cortocircuito más allá del procesamiento de la solicitud.
  4. El middleware de directivas de Cookie (UseCookiePolicy) permite que la aplicación cumpla con las normas del Reglamento general de protección de datos (RGPD) de la Unión Europea.
  5. Middleware de enrutamiento (UseRouting) para enrutar las solicitudes.
  6. El middleware de autenticación (UseAuthentication) intenta autenticar al usuarios antes de que se le permita acceder a los recursos protegidos.
  7. El middleware de autorización (UseAuthorization) autoriza a los usuarios a acceder a los recursos seguros.
  8. El middleware de sesiones (UseSession) establece y mantiene el estado de sesión. Si la aplicación usa el estado de sesión, llame al middleware de sesiones después del middleware de directivas de Cookie y antes del middleware de MVC.
  9. Middleware de enrutamiento de punto de conexión (UseEndpoints con MapRazorPages) para agregar puntos de conexión de Razor Pages a la canalización de solicitudes.
if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
    app.UseDatabaseErrorPage();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();
app.MapRazorPages();

En el código de ejemplo anterior, cada método de extensión de software intermedio se expone en WebApplicationBuilder a través del espacio de nombres de Microsoft.AspNetCore.Builder.

UseExceptionHandler es el primer componente de software intermedio que se agrega a la canalización. Por lo tanto, el software intermedio del controlador de excepciones detectará las excepciones que se produzcan en las llamadas posteriores.

El software intermedio de archivos estáticos se llama al principio de la canalización para que pueda controlar solicitudes y realizar cortocircuitos sin pasar por los componentes restantes. Este middleware no proporciona comprobaciones de autorización. Los archivos que proporciona el middleware de archivos estáticos, incluidos los de wwwroot, están disponibles de forma pública. Para consultar un enfoque sobre cómo proteger los archivos estáticos, vea Archivos estáticos en ASP.NET Core.

Si el software intermedio de archivos estáticos no controla la solicitud, se pasará al software intermedio de autenticación (UseAuthentication), que realizará la autenticación. Este software intermedio no cortocircuita las solicitudes sin autenticación. Aunque autentique solicitudes, la autorización (y también el rechazo) se producirán después de que MVC seleccione una página de Razor o un control y una acción de MVC concretos.

En el ejemplo siguiente se muestra un orden de software intermedio en el que el software intermedio de archivos estáticos controla las solicitudes de archivos estáticos antes del software intermedio de compresión de respuestas. Los archivos estáticos no se comprimen en este orden de software intermedio. Las respuestas de Razor Pages se pueden comprimir.

// Static files aren't compressed by Static File Middleware.
app.UseStaticFiles();

app.UseRouting();

app.UseResponseCompression();

app.MapRazorPages();

Para obtener más información sobre las aplicaciones de página única, consulte las guías de las plantillas de proyecto de React y Angular.

Orden de UseCors y UseStaticFiles

El orden para llamar a UseCors y UseStaticFiles depende de la aplicación. Para obtener más información, consulte la sección sobre el orden de UseCors y UseStaticFiles

Orden del middleware de encabezados reenviados

El Middleware de encabezados reenviados debe ejecutarse antes de otro middleware. Hacerlo en ese orden garantiza que el middleware que se basa en la información de encabezados reenviados pueda usar los valores de encabezado para procesarlos. Para ejecutar el middleware de encabezados reenviados después del middleware de diagnóstico y control de errores, vea Orden del middleware de encabezados reenviados.

Creación de una rama de la canalización de middleware

Las extensiones Map se usan como convenciones para la creación de ramas en la canalización. Map crea una rama de la canalización de solicitudes según las coincidencias de la ruta de solicitud proporcionada. Si la ruta de solicitud comienza con la ruta proporcionada, se ejecuta la creación de la rama.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Map("/map1", HandleMapTest1);

app.Map("/map2", HandleMapTest2);

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from non-Map delegate.");
});

app.Run();

static void HandleMapTest1(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map Test 1");
    });
}

static void HandleMapTest2(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map Test 2");
    });
}

En la siguiente tabla se muestran las solicitudes y las respuestas de http://localhost:1234 con el código anterior.

Solicitud Respuesta
localhost:1234 Saludos del delegado sin Map.
localhost:1234/map1 Prueba 1 de Map
localhost:1234/map2 Prueba 2 de Map
localhost:1234/map3 Saludos del delegado sin Map.

Cuando se usa Map, los segmentos de ruta que coincidan se eliminan de HttpRequest.Path y se anexan a HttpRequest.PathBase para cada solicitud.

Map admite la anidación, por ejemplo:

app.Map("/level1", level1App => {
    level1App.Map("/level2a", level2AApp => {
        // "/level1/level2a" processing
    });
    level1App.Map("/level2b", level2BApp => {
        // "/level1/level2b" processing
    });
});

Map puede hacer coincidir varios segmentos a la vez:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Map("/map1/seg1", HandleMultiSeg);

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from non-Map delegate.");
});

app.Run();

static void HandleMultiSeg(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map Test 1");
    });
}

MapWhen crea una rama de la canalización de solicitudes según el resultado del predicado proporcionado. Se puede usar cualquier predicado de tipo Func<HttpContext, bool> para asignar solicitudes a nuevas ramas de la canalización. En el ejemplo siguiente se usa un predicado para detectar la presencia de una branch variable de cadena de consulta:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapWhen(context => context.Request.Query.ContainsKey("branch"), HandleBranch);

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from non-Map delegate.");
});

app.Run();

static void HandleBranch(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        var branchVer = context.Request.Query["branch"];
        await context.Response.WriteAsync($"Branch used = {branchVer}");
    });
}

En la siguiente tabla se muestran las solicitudes y las respuestas de http://localhost:1234 con el código anterior:

Request Response
localhost:1234 Hello from non-Map delegate.
localhost:1234/?branch=main Branch used = main

UseWhen también crea una rama de la canalización de solicitudes según el resultado del predicado proporcionado. A diferencia de lo que sucede con MapWhen, esta rama se vuelve a unir a la canalización principal si no realiza un cortocircuito ni contiene un middleware de terminal:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.UseWhen(context => context.Request.Query.ContainsKey("branch"),
    appBuilder => HandleBranchAndRejoin(appBuilder));

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from non-Map delegate.");
});

app.Run();

void HandleBranchAndRejoin(IApplicationBuilder app)
{
    var logger = app.ApplicationServices.GetRequiredService<ILogger<Program>>(); 

    app.Use(async (context, next) =>
    {
        var branchVer = context.Request.Query["branch"];
        logger.LogInformation("Branch used = {branchVer}", branchVer);

        // Do work that doesn't write to the Response.
        await next();
        // Do other work that doesn't write to the Response.
    });
}

En el ejemplo anterior, se escribe una respuesta de Hello from non-Map delegate. para todas las solicitudes. Si la solicitud incluye una variable de cadena de consulta branch, su valor se registra antes de que se vuelva a unir la canalización principal.

Middleware integrado

ASP.NET Core incluye los componentes de software intermedio siguientes. En la columna Orden se proporcionan notas sobre la ubicación del middleware en la canalización de procesamiento de solicitudes, así como las condiciones con las que podría finalizar el procesamiento de solicitudes. Cuando un middleware cortocircuita la canalización de procesamiento de solicitudes e impide el procesamiento de una solicitud por parte de middleware descendente adicional, se llama middleware de terminal. Para más información sobre cómo cortocircuitar, consulte la sección Creación de una canalización de middleware con WebApplication.

Software intermedio Descripción Ordenar
Autenticación Proporciona compatibilidad con autenticación. Antes de que se necesite HttpContext.User. Terminal para devoluciones de llamadas OAuth.
Autorización Proporciona compatibilidad con la autorización. Inmediatamente después del middleware de autenticación.
Directiva de Cookie Realiza un seguimiento del consentimiento de los usuarios para almacenar información personal y aplica los estándares mínimos para los campos de las cookie, como secure y SameSite. Antes del middleware que emite las cookies. Ejemplos: autenticación, sesión y MVC (TempData).
CORS Configura el uso compartido de recursos entre orígenes. Antes de los componentes que usan CORS. UseCors actualmente debe ir antes de UseResponseCaching debido a este error.
DeveloperExceptionPage Genera una página con información de errores que está pensada para su uso solo en el entorno de desarrollo. Antes de los componentes que generan errores. Las plantillas de proyecto registran automáticamente este middleware como el primer middleware de la canalización cuando el entorno es de desarrollo.
Diagnóstico Varios middleware independientes que proporcionan una página de excepciones para el desarrollador, control de excepciones, páginas de códigos de estado y la página web predeterminada para las nuevas aplicaciones. Antes de los componentes que generan errores. Terminal para excepciones o con el fin de proporcionar la página web predeterminada para las nuevas aplicaciones.
Encabezados reenviados Reenvía encabezados con proxy a la solicitud actual. Antes de los componentes que consumen los campos actualizados. Ejemplos: esquema, host, IP de cliente y método.
Comprobación de estado Comprueba el estado de una aplicación ASP.NET Core y sus dependencias, como la comprobación de disponibilidad de base de datos. Terminal si una solicitud coincide con un punto de conexión de comprobación de estado.
Propagación de encabezados Permite propagar los encabezados HTTP de la solicitud entrante a las solicitudes del cliente HTTP salientes.
Registro HTTP Registra respuestas y solicitudes HTTP. Al principio de la canalización del middleware.
Invalidación del método HTTP Permite que una solicitud POST entrante invalide el método. Antes de los componentes que consumen el método actualizado.
Redireccionamiento de HTTPS Redirija todas las solicitudes HTTP a HTTPS. Antes de los componentes que consumen la dirección URL.
Seguridad de transporte estricta de HTTP (HSTS) Middleware de mejora de seguridad que agrega un encabezado de respuesta especial. Antes de que se envíen las respuestas y después de los componentes que modifican las solicitudes. Ejemplos: encabezados reenviados y reescritura de URL.
MVC Procesa las solicitudes con MVC/Razor Pages. Si hay una solicitud que coincida con una ruta, será final.
OWIN Puede interoperar con aplicaciones, servidores y software intermedio basados en OWIN. Si el software intermedio de OWIN procesa completamente la solicitud, será final.
Descompresión de solicitudes Proporciona compatibilidad para descomprimir solicitudes. Antes de los componentes que leen el cuerpo de la solicitud.
Almacenamiento en caché de respuestas Proporciona compatibilidad con la captura de respuestas. Antes de los componentes que requieren el almacenamiento en caché. UseCORS debe ser anterior a UseResponseCaching.
Compresión de respuesta Proporciona compatibilidad con la compresión de respuestas. Antes de los componentes que requieren compresión.
Localización de solicitudes Proporciona compatibilidad con ubicación. Antes de los componentes que dependen de la ubicación. Debe aparecer después del middleware de enrutamiento cuando se usa RouteDataRequestCultureProvider.
Enrutamiento de punto de conexión Define y restringe las rutas de la solicitud. Terminal para rutas que coincidan.
SPA Controla todas las solicitudes desde este punto de la cadena de middleware devolviendo la página predeterminada de la aplicación de página única (SPA). En un punto posterior de la cadena, de modo que otro middleware dedicado a proporcionar archivos estáticos, acciones de MVC y otros elementos tenga prioridad.
Sesión Proporciona compatibilidad con la administración de sesiones de usuario. Antes de los componentes que requieren Session.
Archivos estáticos Proporciona compatibilidad con la proporción de archivos estáticos y la exploración de directorios. Si hay una solicitud que coincida con un archivo, será final.
Reescritura de URL Proporciona compatibilidad con la reescritura de direcciones URL y la redirección de solicitudes. Antes de los componentes que consumen la dirección URL.
W3CLogging Genera registros de acceso al servidor con el formato de archivo de registro extendido del W3C. Al principio de la canalización del middleware.
WebSockets Habilita el protocolo WebSockets. Antes de los componentes necesarios para aceptar solicitudes de WebSocket.

Recursos adicionales

Por Rick Anderson y Steve Smith

El software intermedio es un software que se ensambla en una canalización de una aplicación para controlar las solicitudes y las respuestas. Cada componente puede hacer lo siguiente:

  • Elegir si se pasa la solicitud al siguiente componente de la canalización.
  • Realizar trabajos antes y después del siguiente componente de la canalización.

Los delegados de solicitudes se usan para crear la canalización de solicitudes. Estos también controlan las solicitudes HTTP.

Los delegados de solicitudes se configuran con los métodos de extensión Run, Map y Use. Un delegado de solicitudes se puede especificar en línea como un método anónimo (denominado middleware en línea) o se puede definir en una clase reutilizable. Estas clases reutilizables y métodos anónimos en línea se conocen como software intermedio o componentes de software intermedio. Cada componente de software intermedio de la canalización de solicitudes es responsable de invocar el siguiente componente de la canalización o de cortocircuitar la canalización, en caso de ser necesario. Cuando un middleware se cortocircuita, se llama middleware de terminal porque impide el procesamiento de la solicitud por parte de middleware adicional.

En Migración de controladores y módulos HTTP a middleware de ASP.NET Core se explica la diferencia entre las canalizaciones de solicitudes en ASP.NET Core y ASP.NET 4.x y se proporcionan más ejemplos de middleware.

Creación de una canalización de software intermedio con IApplicationBuilder

La canalización de solicitudes de ASP.NET Core consiste en una secuencia de delegados de solicitud a los que se llama de uno en uno. En el siguiente diagrama se muestra este concepto. El subproceso de ejecución sigue las flechas negras.

En el patrón de procesamiento de solicitudes se muestra una solicitud entrante, el procesamiento a través de tres middleware y la respuesta que sale de la aplicación. Cada middleware ejecuta su lógica y pasa la solicitud al middleware siguiente con la instrucción next(). Después de que el tercer middleware procese la solicitud, esta vuelve a pasar por los dos middleware anteriores en orden inverso. Esto se hace a modo de procesamiento adicional después de las instrucciones next() y antes de salir de la aplicación como respuesta al cliente.

Cada delegado puede realizar operaciones antes y después del siguiente. Los delegados que controlan excepciones deben llamarse al principio de la canalización para que puedan capturar las excepciones que se producen en las fases siguientes de la canalización.

La aplicación ASP.NET Core más sencilla posible configura un solo delegado de solicitudes que controla todas las solicitudes. En este caso no se incluye una canalización de solicitudes real. En su lugar, solo se llama a una única función anónima en respuesta a todas las solicitudes HTTP.

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello, World!");
        });
    }
}

Encadene varios delegados de solicitudes con Use. El parámetro next representa el siguiente delegado de la canalización. Si no llama al siguiente parámetro, puede cortocircuitar la canalización. Normalmente, puede realizar acciones antes y después del siguiente delegado, tal como se muestra en el ejemplo siguiente:

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) =>
        {
            // Do work that doesn't write to the Response.
            await next.Invoke();
            // Do logging or other work that doesn't write to the Response.
        });

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from 2nd delegate.");
        });
    }
}

Cuando un delegado no pasa una solicitud al siguiente delegado, se denomina cortocircuitar la canalización de solicitudes. Este proceso es necesario muchas veces, ya que previene la realización de trabajo innecesario. Por ejemplo, el middleware de archivos estáticos puede actuar como middleware de terminal procesando una solicitud para un archivo estático y cortocircuitando el resto de la canalización. El middleware agregado a la canalización antes del middleware que finaliza el procesamiento sigue procesando código después de sus instrucciones next.Invoke. Sin embargo, consulte la siguiente advertencia sobre intentar escribir en una respuesta que ya se ha enviado.

Advertencia

No llame a next.Invoke después de haber enviado la respuesta al cliente. Si se modifica HttpResponse después de haber iniciado la respuesta, se producirá una excepción. Por ejemplo, se inicia una excepción al establecer encabezados y un código de estado. Si escribe en el cuerpo de la respuesta después de llamar a next:

  • Puede provocar una infracción del protocolo. Por ejemplo, si escribe más de la longitud Content-Length establecida.
  • Puede dañar el formato del cuerpo. Por ejemplo, si escribe un pie de página en HTML en un archivo CSS.

HasStarted es una sugerencia útil para indicar si se han enviado los encabezados o se han realizado escrituras en el cuerpo.

Los delegados de Run no reciben un parámetro next. El primer delegado de Run siempre es terminal y finaliza la canalización. Run es una convención. Es posible que algunos componentes de middleware expongan métodos Run[Middleware] que se ejecutan al final de la canalización:

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) =>
        {
            // Do work that doesn't write to the Response.
            await next.Invoke();
            // Do logging or other work that doesn't write to the Response.
        });

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from 2nd delegate.");
        });
    }
}

Si quiere que los comentarios de código se traduzcan en más idiomas además del inglés, háganoslo saber en este problema de debate de GitHub.

En el ejemplo anterior, el delegado Run escribe "Hello from 2nd delegate." en la respuesta y, después, termina la canalización. Si se agrega otro delegado Use o Run después de Run, no se le llama.

Orden del middleware

En el diagrama siguiente se muestra la canalización de procesamiento de solicitudes completa para las aplicaciones de ASP.NET Core MVC y de Razor Pages. Puede ver cómo, en una aplicación típica, se ordenan los middleware existentes y dónde se agregan los middleware personalizados. Tiene control total sobre cómo reordenar los middleware existentes o insertar nuevos middleware personalizados según sea necesario para sus escenarios.

Canalización de middleware de ASP.NET Core

El middleware Punto de conexión del diagrama anterior ejecuta la canalización de filtro para el tipo de aplicación correspondiente, MVC o Razor Pages.

Canalización de filtro de ASP.NET Core

El orden en el que se agregan los componentes de software intermedio en el método Startup.Configure define el orden en el que se invocarán los componentes de software intermedio en las solicitudes y el orden inverso de la respuesta. Por motivos de seguridad, rendimiento y funcionalidad, el orden es crítico.

El método Startup.Configure siguiente agrega componentes de middleware relacionados con la seguridad en el orden recomendado típico:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();
    // app.UseCookiePolicy();

    app.UseRouting();
    // app.UseRequestLocalization();
    // app.UseCors();

    app.UseAuthentication();
    app.UseAuthorization();
    // app.UseSession();
    // app.UseResponseCompression();
    // app.UseResponseCaching();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });
}

En el código anterior:

  • El middleware que no se agrega al crear una aplicación web con cuentas de usuario individuales se convierte en comentario.
  • No todo el middleware aparece en este orden exacto, pero gran parte sí lo hace. Por ejemplo:
    • UseCors, UseAuthentication y UseAuthorization deben aparecer en el orden mostrado.
    • UseCors actualmente debe ir antes de UseResponseCaching debido a este error.
    • UseRequestLocalization debe aparecer antes que cualquier middleware que pueda comprobar la referencia cultural de la solicitud (por ejemplo, app.UseMvcWithDefaultRoute()).

En algunos escenarios, el middleware tiene un orden diferente. Por ejemplo, el almacenamiento en caché y la ordenación de compresión es específico del escenario y hay varias ordenaciones válidas. Por ejemplo:

app.UseResponseCaching();
app.UseResponseCompression();

Con el código anterior, la CPU podría guardarse almacenando en caché la respuesta comprimida, pero es posible que termine el almacenamiento en caché de varias representaciones de un recurso con distintos algoritmos de compresión, como Gzip o Brotli.

La ordenación siguiente combina archivos estáticos para permitir el almacenamiento en caché de archivos estáticos comprimidos:

app.UseResponseCaching();
app.UseResponseCompression();
app.UseStaticFiles();

El siguiente método Startup.Configure agrega los componentes de middleware para escenarios de aplicaciones comunes:

  1. Control de errores y excepciones
    • Cuando la aplicación se ejecuta en el entorno de desarrollo:
      • El middleware de la página de excepciones para el desarrollador (UseDeveloperExceptionPage) informa los errores en tiempo de ejecución de la aplicación.
      • El middleware de la página de errores de la base de datos informa de los errores en tiempo de ejecución de la base de datos.
    • Cuando la aplicación se ejecuta en el entorno de producción:
      • El middleware del controlador de excepciones (UseExceptionHandler) detecta las excepciones generadas en los middlewares siguientes.
      • El middleware del protocolo de seguridad de transporte estricta de HTTP (HSTS) (UseHsts) agrega el encabezado Strict-Transport-Security.
  2. El middleware de redireccionamiento de HTTPS (UseHttpsRedirection) redirige las solicitudes HTTP a HTTPS.
  3. El middleware de archivos estáticos (UseStaticFiles) devuelve archivos estáticos y genera un cortocircuito más allá del procesamiento de la solicitud.
  4. El middleware de directivas de Cookie (UseCookiePolicy) permite que la aplicación cumpla con las normas del Reglamento general de protección de datos (RGPD) de la Unión Europea.
  5. Middleware de enrutamiento (UseRouting) para enrutar las solicitudes.
  6. El middleware de autenticación (UseAuthentication) intenta autenticar al usuarios antes de que se le permita acceder a los recursos protegidos.
  7. El middleware de autorización (UseAuthorization) autoriza a los usuarios a acceder a los recursos seguros.
  8. El middleware de sesiones (UseSession) establece y mantiene el estado de sesión. Si la aplicación usa el estado de sesión, llame al middleware de sesiones después del middleware de directivas de Cookie y antes del middleware de MVC.
  9. Middleware de enrutamiento de punto de conexión (UseEndpoints con MapRazorPages) para agregar puntos de conexión de Razor Pages a la canalización de solicitudes.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();
    app.UseCookiePolicy();
    app.UseRouting();
    app.UseAuthentication();
    app.UseAuthorization();
    app.UseSession();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

En el código de ejemplo anterior, cada método de extensión de software intermedio se expone en IApplicationBuilder a través del espacio de nombres de Microsoft.AspNetCore.Builder.

UseExceptionHandler es el primer componente de software intermedio que se agrega a la canalización. Por lo tanto, el software intermedio del controlador de excepciones detectará las excepciones que se produzcan en las llamadas posteriores.

El software intermedio de archivos estáticos se llama al principio de la canalización para que pueda controlar solicitudes y realizar cortocircuitos sin pasar por los componentes restantes. Este middleware no proporciona comprobaciones de autorización. Los archivos que proporciona el middleware de archivos estáticos, incluidos los de wwwroot, están disponibles de forma pública. Para consultar un enfoque sobre cómo proteger los archivos estáticos, vea Archivos estáticos en ASP.NET Core.

Si el software intermedio de archivos estáticos no controla la solicitud, se pasará al software intermedio de autenticación (UseAuthentication), que realizará la autenticación. Este software intermedio no cortocircuita las solicitudes sin autenticación. Aunque autentique solicitudes, la autorización (y también el rechazo) se producirán después de que MVC seleccione una página de Razor o un control y una acción de MVC concretos.

En el ejemplo siguiente se muestra un orden de software intermedio en el que el software intermedio de archivos estáticos controla las solicitudes de archivos estáticos antes del software intermedio de compresión de respuestas. Los archivos estáticos no se comprimen en este orden de software intermedio. Las respuestas de Razor Pages se pueden comprimir.

public void Configure(IApplicationBuilder app)
{
    // Static files aren't compressed by Static File Middleware.
    app.UseStaticFiles();

    app.UseRouting();

    app.UseResponseCompression();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

En el caso de las aplicaciones de página única (SPA), el middleware de SPA UseSpaStaticFiles normalmente se incluye en último lugar en la canalización de middleware. El middleware de SPA se incluye en último lugar:

  • Para permitir que todos los demás middleware respondan a las solicitudes coincidentes primero.
  • Para permitir que SPA con enrutamiento del lado cliente se ejecuten para todas las rutas que no reconoce la aplicación de servidor.

Para obtener más información sobre las SPA, consulte las guías de las plantillas de proyecto React y Angular.

Orden del middleware de encabezados reenviados

El Middleware de encabezados reenviados debe ejecutarse antes de otro middleware. Hacerlo en ese orden garantiza que el middleware que se basa en la información de encabezados reenviados pueda usar los valores de encabezado para procesarlos. Para ejecutar el middleware de encabezados reenviados después del middleware de diagnóstico y control de errores, vea Orden del middleware de encabezados reenviados.

Creación de una rama de la canalización de middleware

Las extensiones Map se usan como convenciones para la creación de ramas en la canalización. Map crea una rama de la canalización de solicitudes según las coincidencias de la ruta de solicitud proporcionada. Si la ruta de solicitud comienza con la ruta proporcionada, se ejecuta la creación de la rama.

public class Startup
{
    private static void HandleMapTest1(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Map Test 1");
        });
    }

    private static void HandleMapTest2(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Map Test 2");
        });
    }

    public void Configure(IApplicationBuilder app)
    {
        app.Map("/map1", HandleMapTest1);

        app.Map("/map2", HandleMapTest2);

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from non-Map delegate.");
        });
    }
}

En la siguiente tabla se muestran las solicitudes y las respuestas de http://localhost:1234 con el código anterior.

Request Respuesta
localhost:1234 Saludos del delegado sin Map.
localhost:1234/map1 Prueba 1 de Map
localhost:1234/map2 Prueba 2 de Map
localhost:1234/map3 Saludos del delegado sin Map.

Cuando se usa Map, los segmentos de ruta que coincidan se eliminan de HttpRequest.Path y se anexan a HttpRequest.PathBase para cada solicitud.

Map admite la anidación, por ejemplo:

app.Map("/level1", level1App => {
    level1App.Map("/level2a", level2AApp => {
        // "/level1/level2a" processing
    });
    level1App.Map("/level2b", level2BApp => {
        // "/level1/level2b" processing
    });
});

Map puede hacer coincidir varios segmentos a la vez:

public class Startup
{
    private static void HandleMultiSeg(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Map multiple segments.");
        });
    }

    public void Configure(IApplicationBuilder app)
    {
        app.Map("/map1/seg1", HandleMultiSeg);

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from non-Map delegate.");
        });
    }
}

MapWhen crea una rama de la canalización de solicitudes según el resultado del predicado proporcionado. Se puede usar cualquier predicado de tipo Func<HttpContext, bool> para asignar solicitudes a nuevas ramas de la canalización. En el ejemplo siguiente se usa un predicado para detectar la presencia de una branch variable de cadena de consulta:

public class Startup
{
    private static void HandleBranch(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            var branchVer = context.Request.Query["branch"];
            await context.Response.WriteAsync($"Branch used = {branchVer}");
        });
    }

    public void Configure(IApplicationBuilder app)
    {
        app.MapWhen(context => context.Request.Query.ContainsKey("branch"),
                               HandleBranch);

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from non-Map delegate.");
        });
    }
}

En la siguiente tabla se muestran las solicitudes y las respuestas de http://localhost:1234 con el código anterior:

Request Respuesta
localhost:1234 Saludos del delegado sin Map.
localhost:1234/?branch=main Rama usada = principal

UseWhen también crea una rama de la canalización de solicitudes según el resultado del predicado proporcionado. A diferencia de lo que sucede con MapWhen, esta rama se vuelve a unir a la canalización principal si no realiza un cortocircuito ni contiene un middleware de terminal:

public class Startup
{
    private void HandleBranchAndRejoin(IApplicationBuilder app, ILogger<Startup> logger)
    {
        app.Use(async (context, next) =>
        {
            var branchVer = context.Request.Query["branch"];
            logger.LogInformation("Branch used = {branchVer}", branchVer);

            // Do work that doesn't write to the Response.
            await next();
            // Do other work that doesn't write to the Response.
        });
    }

    public void Configure(IApplicationBuilder app, ILogger<Startup> logger)
    {
        app.UseWhen(context => context.Request.Query.ContainsKey("branch"),
                               appBuilder => HandleBranchAndRejoin(appBuilder, logger));

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from main pipeline.");
        });
    }
}

En el ejemplo anterior, se escribe una respuesta de "Hola desde la canalización principal" para todas las solicitudes. Si la solicitud incluye una variable de cadena de consulta branch, su valor se registra antes de que se vuelva a unir la canalización principal.

Middleware integrado

ASP.NET Core incluye los componentes de software intermedio siguientes. En la columna Orden se proporcionan notas sobre la ubicación del middleware en la canalización de procesamiento de solicitudes, así como las condiciones con las que podría finalizar el procesamiento de solicitudes. Cuando un middleware cortocircuita la canalización de procesamiento de solicitudes e impide el procesamiento de una solicitud por parte de middleware descendente adicional, se llama middleware de terminal. Para más información sobre cómo cortocircuitar, consulte la sección Creación de una canalización de middleware con IApplicationBuilder.

Software intermedio Descripción Ordenar
Autenticación Proporciona compatibilidad con autenticación. Antes de que se necesite HttpContext.User. Terminal para devoluciones de llamadas OAuth.
Autorización Proporciona compatibilidad con la autorización. Inmediatamente después del middleware de autenticación.
Directiva de Cookie Realiza un seguimiento del consentimiento de los usuarios para almacenar información personal y aplica los estándares mínimos para los campos de las cookie, como secure y SameSite. Antes del middleware que emite las cookies. Ejemplos: autenticación, sesión y MVC (TempData).
CORS Configura el uso compartido de recursos entre orígenes. Antes de los componentes que usan CORS. UseCors actualmente debe ir antes de UseResponseCaching debido a este error.
Diagnóstico Varios middleware independientes que proporcionan una página de excepciones para el desarrollador, control de excepciones, páginas de códigos de estado y la página web predeterminada para las nuevas aplicaciones. Antes de los componentes que generan errores. Terminal para excepciones o con el fin de proporcionar la página web predeterminada para las nuevas aplicaciones.
Encabezados reenviados Reenvía encabezados con proxy a la solicitud actual. Antes de los componentes que consumen los campos actualizados. Ejemplos: esquema, host, IP de cliente y método.
Comprobación de estado Comprueba el estado de una aplicación ASP.NET Core y sus dependencias, como la comprobación de disponibilidad de base de datos. Terminal si una solicitud coincide con un punto de conexión de comprobación de estado.
Propagación de encabezados Permite propagar los encabezados HTTP de la solicitud entrante a las solicitudes del cliente HTTP salientes.
Invalidación del método HTTP Permite que una solicitud POST entrante invalide el método. Antes de los componentes que consumen el método actualizado.
Redireccionamiento de HTTPS Redirija todas las solicitudes HTTP a HTTPS. Antes de los componentes que consumen la dirección URL.
Seguridad de transporte estricta de HTTP (HSTS) Middleware de mejora de seguridad que agrega un encabezado de respuesta especial. Antes de que se envíen las respuestas y después de los componentes que modifican las solicitudes. Ejemplos: encabezados reenviados y reescritura de URL.
MVC Procesa las solicitudes con MVC/Razor Pages. Si hay una solicitud que coincida con una ruta, será final.
OWIN Puede interoperar con aplicaciones, servidores y software intermedio basados en OWIN. Si el software intermedio de OWIN procesa completamente la solicitud, será final.
Almacenamiento en caché de respuestas Proporciona compatibilidad con la captura de respuestas. Antes de los componentes que requieren el almacenamiento en caché. UseCORS debe ser anterior a UseResponseCaching.
Compresión de respuesta Proporciona compatibilidad con la compresión de respuestas. Antes de los componentes que requieren compresión.
Localización de solicitudes Proporciona compatibilidad con ubicación. Antes de los componentes que dependen de la ubicación. Debe aparecer después del middleware de enrutamiento cuando se usa RouteDataRequestCultureProvider.
Enrutamiento de punto de conexión Define y restringe las rutas de la solicitud. Terminal para rutas que coincidan.
SPA Controla todas las solicitudes desde este punto de la cadena de middleware devolviendo la página predeterminada de la aplicación de página única (SPA). En un punto posterior de la cadena, de modo que otro middleware dedicado a proporcionar archivos estáticos, acciones de MVC y otros elementos tenga prioridad.
Sesión Proporciona compatibilidad con la administración de sesiones de usuario. Antes de los componentes que requieren Session.
Archivos estáticos Proporciona compatibilidad con la proporción de archivos estáticos y la exploración de directorios. Si hay una solicitud que coincida con un archivo, será final.
Reescritura de URL Proporciona compatibilidad con la reescritura de direcciones URL y la redirección de solicitudes. Antes de los componentes que consumen la dirección URL.
WebSockets Habilita el protocolo WebSocket. Antes de los componentes necesarios para aceptar solicitudes de WebSocket.

Recursos adicionales