Escritura de middleware de ASP.NET Core personalizado

De Fiyaz Hasan, 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. ASP.NET Core proporciona un completo conjunto de componentes de middleware integrados, pero en algunos escenarios es posible que quiera escribir middleware personalizado.

En este tema se describe cómo escribir middleware basado en convenciones. Puede encontrar un enfoque donde se usa el establecimiento de tipos seguros y la activación por solicitud en Activación de middleware basada en fábrica en ASP.NET Core.

Clase de middleware

El middleware normalmente está encapsulado en una clase y se expone con un método de extensión. Considere el siguiente middleware en línea, que establece la referencia cultural de la solicitud actual desde una cadena de consulta:

using System.Globalization;

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

app.UseHttpsRedirection();

app.Use(async (context, next) =>
{
    var cultureQuery = context.Request.Query["culture"];
    if (!string.IsNullOrWhiteSpace(cultureQuery))
    {
        var culture = new CultureInfo(cultureQuery);

        CultureInfo.CurrentCulture = culture;
        CultureInfo.CurrentUICulture = culture;
    }

    // Call the next delegate/middleware in the pipeline.
    await next(context);
});

app.Run(async (context) =>
{
    await context.Response.WriteAsync(
        $"CurrentCulture.DisplayName: {CultureInfo.CurrentCulture.DisplayName}");
});

app.Run();

El middleware en línea resaltado anterior se usa para demostrar la creación de un componente de middleware mediante una llamada a Microsoft.AspNetCore.Builder.UseExtensions.Use. El método de extensión Use anterior agrega un delegado de middleware definido en línea a la canalización de solicitudes de la aplicación.

Hay dos sobrecargas disponibles para la extensión Use:

  • Una toma un elemento HttpContext y un elemento Func<Task>. Invoque el elemento Func<Task> sin ningún parámetro.
  • La otra toma un elemento HttpContext y un elemento RequestDelegate. Invoque el elemento RequestDelegate pasando el elemento HttpContext.

Es preferible usar la última sobrecarga, ya que guarda dos asignaciones por solicitud internas que son necesarias cuando se usa la otra sobrecarga.

Pruebe el middleware pasando la referencia cultural. Por ejemplo, solicite https://localhost:5001/?culture=es-es.

Para información sobre la compatibilidad integrada con la localización, consulte Globalización y localización en ASP.NET Core.

El código siguiente mueve el delegado de middleware a una clase:

using System.Globalization;

namespace Middleware.Example;

public class RequestCultureMiddleware
{
    private readonly RequestDelegate _next;

    public RequestCultureMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var cultureQuery = context.Request.Query["culture"];
        if (!string.IsNullOrWhiteSpace(cultureQuery))
        {
            var culture = new CultureInfo(cultureQuery);

            CultureInfo.CurrentCulture = culture;
            CultureInfo.CurrentUICulture = culture;
        }

        // Call the next delegate/middleware in the pipeline.
        await _next(context);
    }
}

La clase de middleware debe incluir:

  • Un constructor público con un parámetro de tipo RequestDelegate.
  • Un método público llamado Invoke o InvokeAsync. Este método debe:
    • Devolver Task.
    • Aceptar un primer parámetro de tipo HttpContext.

Los parámetros adicionales para el constructor y Invoke/InvokeAsync se rellenan mediante la inserción de dependencias (DI).

Normalmente, se crea un método de extensión para exponer el middleware a mediante IApplicationBuilder:

using System.Globalization;

namespace Middleware.Example;

public class RequestCultureMiddleware
{
    private readonly RequestDelegate _next;

    public RequestCultureMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var cultureQuery = context.Request.Query["culture"];
        if (!string.IsNullOrWhiteSpace(cultureQuery))
        {
            var culture = new CultureInfo(cultureQuery);

            CultureInfo.CurrentCulture = culture;
            CultureInfo.CurrentUICulture = culture;
        }

        // Call the next delegate/middleware in the pipeline.
        await _next(context);
    }
}

public static class RequestCultureMiddlewareExtensions
{
    public static IApplicationBuilder UseRequestCulture(
        this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<RequestCultureMiddleware>();
    }
}

El código siguiente llama al middleware desde Program.cs:

using Middleware.Example;
using System.Globalization;

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

app.UseHttpsRedirection();

app.UseRequestCulture();

app.Run(async (context) =>
{
    await context.Response.WriteAsync(
        $"CurrentCulture.DisplayName: {CultureInfo.CurrentCulture.DisplayName}");
});

app.Run();

Dependencias de middleware

El middleware debería seguir el principio de dependencias explicitas mediante la exposición de sus dependencias en el constructor. El middleware se construye una vez por duración de la aplicación.

Los componentes de software intermedio pueden resolver sus dependencias de una inserción de dependencias (DI) mediante parámetros del constructor. UseMiddleware también puede aceptar parámetros adicionales directamente.

Dependencias de middleware bajo solicitud

El middleware se construye al inicio de la aplicación y, por tanto, tiene la duración de la aplicación. Los servicios de duración limitada que usan los constructores de software intermedio no se comparten con otros tipos insertados mediante dependencias durante cada solicitud. Para compartir un servicio limitado entre el middleware y otros tipos, agregue esos servicios a la signatura del método InvokeAsync. El método InvokeAsync puede aceptar parámetros adicionales que la inserción de dependencias propaga:

namespace Middleware.Example;

public class MyCustomMiddleware
{
    private readonly RequestDelegate _next;

    public MyCustomMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    // IMessageWriter is injected into InvokeAsync
    public async Task InvokeAsync(HttpContext httpContext, IMessageWriter svc)
    {
        svc.Write(DateTime.Now.Ticks.ToString());
        await _next(httpContext);
    }
}

public static class MyCustomMiddlewareExtensions
{
    public static IApplicationBuilder UseMyCustomMiddleware(
        this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<MyCustomMiddleware>();
    }
}

Las opciones de registro y duración contienen un ejemplo completo de middleware con servicios de duración con ámbito.

El código siguiente se usa para probar el middleware anterior:

using Middleware.Example;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<IMessageWriter, LoggingMessageWriter>();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseMyCustomMiddleware();

app.MapGet("/", () => "Hello World!");

app.Run();

La interfaz y la implementación de IMessageWriter:

namespace Middleware.Example;

public interface IMessageWriter
{
    void Write(string message);
}

public class LoggingMessageWriter : IMessageWriter
{

    private readonly ILogger<LoggingMessageWriter> _logger;

    public LoggingMessageWriter(ILogger<LoggingMessageWriter> logger) =>
        _logger = logger;

    public void Write(string message) =>
        _logger.LogInformation(message);
}

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. ASP.NET Core proporciona un completo conjunto de componentes de middleware integrados, pero en algunos escenarios es posible que quiera escribir middleware personalizado.

Nota:

En este tema se describe cómo escribir middleware basado en convenciones. Puede encontrar un enfoque donde se usa el establecimiento de tipos seguros y la activación por solicitud en Activación de middleware basada en fábrica en ASP.NET Core.

Clase de middleware

El middleware normalmente está encapsulado en una clase y se expone con un método de extensión. Use el siguiente software intermedio a modo de ejemplo. En este se establece la referencia cultural de la solicitud actual a partir de la cadena de solicitud:

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) =>
        {
            var cultureQuery = context.Request.Query["culture"];
            if (!string.IsNullOrWhiteSpace(cultureQuery))
            {
                var culture = new CultureInfo(cultureQuery);

                CultureInfo.CurrentCulture = culture;
                CultureInfo.CurrentUICulture = culture;
            }

            // Call the next delegate/middleware in the pipeline
            await next();
        });

        app.Run(async (context) =>
        {
            await context.Response.WriteAsync(
                $"Hello {CultureInfo.CurrentCulture.DisplayName}");
        });

    }
}

El código de ejemplo anterior se usa para mostrar la creación de un componente de software intermedio. Para información sobre la compatibilidad integrada con la localización, consulte Globalización y localización en ASP.NET Core.

Pruebe el middleware pasando la referencia cultural. Por ejemplo, solicite https://localhost:5001/?culture=no.

El código siguiente mueve el delegado de middleware a una clase:

using Microsoft.AspNetCore.Http;
using System.Globalization;
using System.Threading.Tasks;

namespace Culture
{
    public class RequestCultureMiddleware
    {
        private readonly RequestDelegate _next;

        public RequestCultureMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task InvokeAsync(HttpContext context)
        {
            var cultureQuery = context.Request.Query["culture"];
            if (!string.IsNullOrWhiteSpace(cultureQuery))
            {
                var culture = new CultureInfo(cultureQuery);

                CultureInfo.CurrentCulture = culture;
                CultureInfo.CurrentUICulture = culture;

            }

            // Call the next delegate/middleware in the pipeline
            await _next(context);
        }
    }
}

La clase de middleware debe incluir:

  • Un constructor público con un parámetro de tipo RequestDelegate.
  • Un método público llamado Invoke o InvokeAsync. Este método debe:
    • Devolver Task.
    • Aceptar un primer parámetro de tipo HttpContext.

Los parámetros adicionales para el constructor y Invoke/InvokeAsync se rellenan mediante la inserción de dependencias (DI).

Dependencias de middleware

El middleware debería seguir el principio de dependencias explicitas mediante la exposición de sus dependencias en el constructor. El middleware se construye una vez por duración de la aplicación. Si necesita compartir servicios con software intermedio en una solicitud, vea la sección Dependencias de middleware bajo solicitud.

Los componentes de software intermedio pueden resolver sus dependencias de una inserción de dependencias (DI) mediante parámetros del constructor. UseMiddleware también puede aceptar parámetros adicionales directamente.

Dependencias de middleware bajo solicitud

Dado que el software intermedio se construye al inicio de la aplicación y no bajo solicitud, los servicios de duración con ámbito que usan los constructores de software intermedio no se comparten con otros tipos insertados mediante dependencias durante cada solicitud. Si debe compartir un servicio con ámbito entre su middleware y otros tipos, agregue esos servicios a la signatura del método InvokeAsync. El método InvokeAsync puede aceptar parámetros adicionales que la inserción de dependencias propaga:

public class CustomMiddleware
{
    private readonly RequestDelegate _next;

    public CustomMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    // IMyScopedService is injected into InvokeAsync
    public async Task InvokeAsync(HttpContext httpContext, IMyScopedService svc)
    {
        svc.MyProperty = 1000;
        await _next(httpContext);
    }
}

Las opciones de registro y duración contienen un ejemplo completo de middleware con servicios de duración con ámbito.

Método de extensión de middleware

El método de extensión siguiente expone el software intermedio mediante IApplicationBuilder:

using Microsoft.AspNetCore.Builder;

namespace Culture
{
    public static class RequestCultureMiddlewareExtensions
    {
        public static IApplicationBuilder UseRequestCulture(
            this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<RequestCultureMiddleware>();
        }
    }
}

El código siguiente llama al middleware desde Startup.Configure:

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.UseRequestCulture();

        app.Run(async (context) =>
        {
            await context.Response.WriteAsync(
                $"Hello {CultureInfo.CurrentCulture.DisplayName}");
        });
    }
}

Recursos adicionales