Scrivere middleware di ASP.NET Core personalizzato

Di Fiyaz Hasan, Rick Anderson e Steve Smith

Il middleware è un software che viene assemblato in una pipeline dell'app per gestire richieste e risposte. ASP.NET Core offre un ampio set di componenti middleware integrati, ma in alcuni scenari si potrebbe voler scrivere un middleware personalizzato.

In questo argomento viene descritto come scrivere middleware basato su convenzioni. Per un approccio che usa la digitazione avanzata e l'attivazione per richiesta, vedere Attivazione middleware basata su factory in ASP.NET Core.

Classe middleware

Il middleware è in genere incapsulato in una classe ed esposto con un metodo di estensione. Si consideri il middleware inline seguente, che imposta le impostazioni cultura per la richiesta corrente da una stringa di query:

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

Il middleware inline evidenziato precedente viene usato per illustrare la creazione di un componente middleware chiamando Microsoft.AspNetCore.Builder.UseExtensions.Use. Il metodo di estensione precedente Use aggiunge un delegato middleware definito inline alla pipeline di richiesta dell'applicazione.

Sono disponibili due overload per l'estensione Use :

  • Uno accetta un HttpContext e un .Func<Task> Func<Task> Richiamare senza parametri.
  • L'altro accetta un HttpContext oggetto e un oggetto RequestDelegate. Richiamare l'oggetto RequestDelegate passando l'oggetto HttpContext.

Preferire l'uso dell'overload successivo perché salva due allocazioni interne per richiesta necessarie quando si usa l'altro overload.

Testare il middleware passando le impostazioni cultura. Ad esempio, richiedere https://localhost:5001/?culture=es-es.

Per il supporto di localizzazione predefinito di ASP.NET Core, vedere Globalizzazione e localizzazione in ASP.NET Core.

Il codice seguente sposta il delegato middleware in una 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);
    }
}

La classe middleware deve includere:

  • Un costruttore pubblico con un parametro di tipo RequestDelegate.
  • Un metodo pubblico denominato Invoke o InvokeAsync. Questo metodo deve:
    • Restituire Task.
    • Accettare un primo parametro di tipo HttpContext.

I parametri aggiuntivi per il costruttore e Invoke/InvokeAsync vengono popolati dall'inserimento delle dipendenze.

In genere, viene creato un metodo di estensione per esporre il middleware tramite 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>();
    }
}

Il codice seguente chiama il middleware da 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();

Dipendenze del middleware

Il middleware deve seguire il principio delle dipendenze esplicite esponendo le dipendenze nel costruttore. Il middleware viene costruito una volta per ogni durata applicazione.

I componenti middleware possono risolvere le dipendenze dall'inserimento di dipendenze mediante i parametri del costruttore. UseMiddleware può anche accettare direttamente parametri aggiuntivi.

Dipendenze del middleware per richiesta

Il middleware viene costruito all'avvio dell'app e pertanto ha un tempo di vita dell'applicazione. I servizi di durata con ambito usati dai costruttori middleware non vengono condivisi con altri tipi inseriti da dipendenze durante ogni richiesta. Per condividere un servizio con ambito tra middleware e altri tipi, aggiungere questi servizi alla InvokeAsync firma del metodo. Il metodo InvokeAsync può accettare parametri aggiuntivi popolati dall'inserimento delle dipendenze:

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

Le opzioni di durata e registrazione contengono un campione completo di middleware con servizi di durata con ambito.

Il codice seguente viene usato per testare il middleware precedente:

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

Interfaccia IMessageWriter e implementazione:

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

Risorse aggiuntive

Rick Anderson e Steve Smith

Il middleware è un software che viene assemblato in una pipeline dell'app per gestire richieste e risposte. ASP.NET Core offre un ampio set di componenti middleware integrati, ma in alcuni scenari si potrebbe voler scrivere un middleware personalizzato.

Nota

In questo argomento viene descritto come scrivere middleware basato su convenzioni. Per un approccio che usa la digitazione avanzata e l'attivazione per richiesta, vedere Attivazione middleware basata su factory in ASP.NET Core.

Classe middleware

Il middleware è in genere incapsulato in una classe ed esposto con un metodo di estensione. Si consideri il middleware seguente, che specifica le impostazioni cultura per la richiesta corrente da una stringa di query:

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

    }
}

Il codice di esempio precedente viene usato per illustrare la creazione di un componente middleware. Per il supporto di localizzazione predefinito di ASP.NET Core, vedere Globalizzazione e localizzazione in ASP.NET Core.

Testare il middleware passando le impostazioni cultura. Ad esempio, richiedere https://localhost:5001/?culture=no.

Il codice seguente sposta il delegato middleware in una 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);
        }
    }
}

La classe middleware deve includere:

  • Un costruttore pubblico con un parametro di tipo RequestDelegate.
  • Un metodo pubblico denominato Invoke o InvokeAsync. Questo metodo deve:
    • Restituire Task.
    • Accettare un primo parametro di tipo HttpContext.

I parametri aggiuntivi per il costruttore e Invoke/InvokeAsync vengono popolati dall'inserimento delle dipendenze.

Dipendenze del middleware

Il middleware deve seguire il principio delle dipendenze esplicite esponendo le dipendenze nel costruttore. Il middleware viene costruito una volta per ogni durata applicazione. Se è necessario condividere servizi con il middleware all'interno di una richiesta, vedere la sezione Dipendenze del middleware per richiesta.

I componenti middleware possono risolvere le dipendenze dall'inserimento di dipendenze mediante i parametri del costruttore. UseMiddleware può anche accettare direttamente parametri aggiuntivi.

Dipendenze del middleware per richiesta

Poiché il middleware viene creato all'avvio dell'app e non per richiesta, i servizi di durata con ambito usati dai costruttori del middleware non vengono condivisi con altri tipi di inserimento di dipendenze durante ogni richiesta. Se è necessario condividere un servizio con ambito tra il proprio middleware e altri tipi, aggiungere i servizi alla firma del metodo InvokeAsync. Il metodo InvokeAsync può accettare parametri aggiuntivi popolati dall'inserimento delle dipendenze:

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

Le opzioni di durata e registrazione contengono un campione completo di middleware con servizi di durata con ambito.

Metodo di estensione del middleware

Il metodo di estensione seguente espone il middleware tramite IApplicationBuilder:

using Microsoft.AspNetCore.Builder;

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

Il codice seguente chiama il middleware da Startup.Configure:

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

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

Risorse aggiuntive