Escrever middleware do ASP.NET Core personalizado

Por Fiyaz Hasan, Rick Anderson e Steve Smith

O middleware é um software montado em um pipeline de aplicativo para manipular solicitações e respostas. O ASP.NET Core fornece um rico conjunto de componentes de middleware internos, mas em alguns cenários, talvez seja conveniente que você escreva um middleware personalizado.

Este tópico descreve como escrever middleware baseado em convenção. Para obter uma abordagem que usa tipagem forte e ativação por solicitação, confira Ativação de middleware baseada em fábrica em ASP.NET Core.

Classe de middleware

O middleware geralmente é encapsulado em uma classe e exposto com um método de extensão. Considere o middleware em linha a seguir, que define a cultura para a solicitação atual de uma cadeia de caracteres 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();

O middleware em linha anterior realçado é usado para demonstrar a criação de um componente de middleware chamando Microsoft.AspNetCore.Builder.UseExtensions.Use. O método de extensão anterior Use adiciona um delegado de middleware definido na linha ao pipeline de solicitação do aplicativo.

Há duas sobrecargas disponíveis para a extensão Use:

  • Uma usa um HttpContext e um Func<Task>. Invoque o Func<Task> sem parâmetros.
  • O outro método utiliza um HttpContext e um RequestDelegate. Invoque o RequestDelegate passando o HttpContext.

Prefira usar a última sobrecarga, pois ela salva duas alocações internas por solicitação que são necessárias ao usar a outra sobrecarga.

Teste o middleware ao transmitir a cultura. Por exemplo, solicite https://localhost:5001/?culture=es-es.

Para obter suporte a localização interna do ASP.NET Core, confira Globalização e localização no ASP.NET Core.

O código a seguir move o delegado de middleware para uma classe:

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);
    }
}

A classe de middleware deve incluir:

  • Um construtor público com um parâmetro de tipo RequestDelegate.
  • Um método público chamado Invoke ou InvokeAsync. Esse método precisa:
    • Retornar um Task.
    • Aceitar um primeiro parâmetro do tipo HttpContext.

Os parâmetros adicionais para o construtor e Invoke/InvokeAsync são preenchidos pela DI (injeção de dependência).

Normalmente, um método de extensão é criado para expor o middleware por meio de 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>();
    }
}

O código a seguir chama o middleware de 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();

Dependências de middleware

O middleware deve seguir o princípio de dependências explícitas ao expor suas dependências em seu construtor. O middleware é construído uma vez por tempo de vida do aplicativo.

Os componentes de middleware podem resolver suas dependências, utilizando a DI (injeção de dependência) por meio de parâmetros do construtor. UseMiddleware também pode aceitar parâmetros adicionais diretamente.

Dependências de middleware por solicitação

O middleware é construído na inicialização do aplicativo e, portanto, tem tempo de vida do aplicativo. Serviços de tempo de vida com escopo usados pelos construtores do middleware não são compartilhados com outros tipos de dependência inseridos durante cada solicitação. Para compartilhar um serviço com escopo entre seu serviço de middleware e serviços de outros tipos, adicione esses serviços à assinatura do método InvokeAsync. O método InvokeAsync pode aceitar parâmetros adicionais que são preenchidos pela injeção de dependência:

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>();
    }
}

As opções de tempo de vida e registro contêm um exemplo completo de middleware com serviços de tempo de vida com escopo.

O código a seguir é usado para testar o 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();

A interface IMessageWriter e a implementação:

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 adicionais

Por Rick Anderson e Steve Smith

O middleware é um software montado em um pipeline de aplicativo para manipular solicitações e respostas. O ASP.NET Core fornece um rico conjunto de componentes de middleware internos, mas em alguns cenários, talvez seja conveniente que você escreva um middleware personalizado.

Observação

Este tópico descreve como escrever middleware baseado em convenção. Para obter uma abordagem que usa tipagem forte e ativação por solicitação, confira Ativação de middleware baseada em fábrica em ASP.NET Core.

Classe de middleware

O middleware geralmente é encapsulado em uma classe e exposto com um método de extensão. Considere o middleware a seguir, que define a cultura para a solicitação atual de uma cadeia de caracteres de consulta:

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}");
        });

    }
}

O código de exemplo anterior é usado para demonstrar a criação de um componente de middleware. Para obter suporte a localização interna do ASP.NET Core, confira Globalização e localização no ASP.NET Core.

Teste o middleware ao transmitir a cultura. Por exemplo, solicite https://localhost:5001/?culture=no.

O código a seguir move o delegado de middleware para uma classe:

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);
        }
    }
}

A classe de middleware deve incluir:

  • Um construtor público com um parâmetro de tipo RequestDelegate.
  • Um método público chamado Invoke ou InvokeAsync. Esse método precisa:
    • Retornar um Task.
    • Aceitar um primeiro parâmetro do tipo HttpContext.

Os parâmetros adicionais para o construtor e Invoke/InvokeAsync são preenchidos pela DI (injeção de dependência).

Dependências de middleware

O middleware deve seguir o princípio de dependências explícitas ao expor suas dependências em seu construtor. O middleware é construído uma vez por tempo de vida do aplicativo. Confira a seção Dependências de middleware por solicitação se você precisar compartilhar serviços com middleware em uma solicitação.

Os componentes de middleware podem resolver suas dependências, utilizando a DI (injeção de dependência) por meio de parâmetros do construtor. UseMiddleware também pode aceitar parâmetros adicionais diretamente.

Dependências de middleware por solicitação

Uma vez que o middleware é construído durante a inicialização do aplicativo, e não por solicitação, os serviços de tempo de vida com escopo usados pelos construtores do middleware não são compartilhados com outros tipos de dependência inseridos durante cada solicitação. Se você tiver que compartilhar um serviço com escopo entre seu serviço de middleware e serviços de outros tipos, adicione esses serviços à assinatura do método InvokeAsync. O método InvokeAsync pode aceitar parâmetros adicionais que são preenchidos pela injeção de dependência:

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);
    }
}

As opções de tempo de vida e registro contêm um exemplo completo de middleware com serviços de tempo de vida com escopo.

Método de extensão de middleware

O seguinte método de extensão expõe o middleware por meio do IApplicationBuilder:

using Microsoft.AspNetCore.Builder;

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

O código a seguir chama o middleware de 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 adicionais