Écrire un intergiciel (middleware) ASP.NET Core personnalisé

Par Fiyaz Hasan, Rick Anderson et Steve Smith

Un middleware est un logiciel qui est assemblé dans un pipeline d’application pour gérer les requêtes et les réponses. Même si ASP.NET Core propose une large palette de composants d’intergiciel (middleware) intégrés, dans certains scénarios, vous voudrez certainement écrire un intergiciel personnalisé.

Cette rubrique explique comment écrire un intergiciel basé sur une convention. Pour une approche qui utilise un typage fort et une activation par requête, consultez Activation d’intergiciels en usine dans ASP.NET Core.

Classe d’intergiciel (middleware)

Les intergiciels sont généralement encapsulés dans une classe et exposés avec une méthode d’extension. Prenez en compte l’intergiciel inclus suivant, qui définit la culture de la requête actuelle à partir d’une chaîne de requête :

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

L’intergiciel inclus mis en évidence ci-dessus est utilisé pour illustrer la création d’un composant d’intergiciel en appelant Microsoft.AspNetCore.Builder.UseExtensions.Use. La méthode d’extension Use précédente ajoute un délégué intergiciel défini comme inclus au pipeline de requête de l’application.

Deux surcharges sont disponibles pour l’extension Use :

  • La première exige un HttpContext et un Func<Task>. Appelez le Func<Task> sans aucun paramètre.
  • La seconde méthode exige un HttpContext et un RequestDelegate. Appelez le RequestDelegate en passant le HttpContext.

Préfère utiliser la seconde surcharge alors qu’il enregistre deux allocations internes par requête, qui sont nécessaires lors de l’utilisation de l’autre surcharge.

Testez l’intergiciel en transmettant la culture. Par exemple, demandez https://localhost:5001/?culture=es-es.

Consultez Globalisation et localisation dans ASP.NET Core pour la prise en charge de la localisation intégrée d’ASP.NET Core.

Le code suivant déplace le délégué de l’intergiciel dans une 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 d’intergiciel (middleware) doit inclure :

  • Un constructeur public avec un paramètre de type RequestDelegate.
  • Une méthode publique nommée Invoke ou InvokeAsync. Cette méthode doit :
    • Retournez une Task.
    • Accepter un premier paramètre de type HttpContext.

Les paramètres supplémentaires pour le constructeur et Invoke/InvokeAsync sont remplis par Injection de dépendance (DI).

En règle générale, une méthode d’extension est créée pour exposer l’intergiciel via 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>();
    }
}

Le code suivant appelle l’intergiciel à partir 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();

Dépendances de l’intergiciel (middleware)

L’intergiciel doit suivre le principe de dépendances explicites en exposant ses dépendances dans son constructeur. L’intergiciel est construit une fois par durée de vie d’application.

Les composants de middleware peuvent résoudre leurs dépendances à partir de l’injection de dépendances à l’aide des paramètres du constructeur. UseMiddleware peut également accepter des paramètres supplémentaires directement.

Dépendances de l’intergiciel (middleware) par requête

L’intergiciel est construit au démarrage de l’application et a donc une durée de vie d’application. Les services de durée de vie étendue utilisés par les constructeurs de l’intergiciel ne sont pas partagés avec d’autres types injectés par des dépendances lors de chaque requête. Si vous devez partager un service étendu entre votre intergiciel et d’autres types, ajoutez ces services à la signature de la méthode InvokeAsync. La méthode InvokeAsync peut accepter des paramètres supplémentaires qui sont renseignés par injection de dépendances :

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

Les options de durée de vie et d’inscription contiennent un exemple complet d’intergiciel avec des services de durée de vie étendue.

Le code suivant est utilisé pour tester l’intergiciel précédent :

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

Interface IMessageWriter et implémentation :

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

Ressources supplémentaires

De Rick Anderson et Steve Smith

Un middleware est un logiciel qui est assemblé dans un pipeline d’application pour gérer les requêtes et les réponses. Même si ASP.NET Core propose une large palette de composants d’intergiciel (middleware) intégrés, dans certains scénarios, vous voudrez certainement écrire un intergiciel personnalisé.

Notes

Cette rubrique explique comment écrire un intergiciel basé sur une convention. Pour une approche qui utilise un typage fort et une activation par requête, consultez Activation d’intergiciels en usine dans ASP.NET Core.

Classe d’intergiciel (middleware)

Les intergiciels sont généralement encapsulés dans une classe et exposés avec une méthode d’extension. Prenez en compte le middleware suivant, qui définit la culture de la requête actuelle à partir d’une chaîne de requête :

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

    }
}

L’exemple de code précédent est utilisé pour illustrer la création d’un composant de middleware. Consultez Globalisation et localisation dans ASP.NET Core pour la prise en charge de la localisation intégrée d’ASP.NET Core.

Testez l’intergiciel en transmettant la culture. Par exemple, demandez https://localhost:5001/?culture=no.

Le code suivant déplace le délégué de l’intergiciel dans une 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 d’intergiciel (middleware) doit inclure :

  • Un constructeur public avec un paramètre de type RequestDelegate.
  • Une méthode publique nommée Invoke ou InvokeAsync. Cette méthode doit :
    • Retournez une Task.
    • Accepter un premier paramètre de type HttpContext.

Les paramètres supplémentaires pour le constructeur et Invoke/InvokeAsync sont remplis par Injection de dépendance (DI).

Dépendances de l’intergiciel (middleware)

L’intergiciel doit suivre le principe de dépendances explicites en exposant ses dépendances dans son constructeur. L’intergiciel est construit une fois par durée de vie d’application. Consultez la section Dépendances des middlewares par requête si vous avez besoin de partager des services avec un middleware au sein d’une requête.

Les composants de middleware peuvent résoudre leurs dépendances à partir de l’injection de dépendances à l’aide des paramètres du constructeur. UseMiddleware peut également accepter des paramètres supplémentaires directement.

Dépendances de l’intergiciel (middleware) par requête

Étant donné que le middleware est construit au démarrage de l’application, et non par requête, les services de durée de vie délimités utilisés par les constructeurs du middleware ne sont pas partagés avec d’autres types injectés par des dépendances lors de chaque requête. Si vous devez partager un service délimité entre votre intergiciel et d’autres types, ajoutez-le à la signature de la méthode InvokeAsync. La méthode InvokeAsync peut accepter des paramètres supplémentaires qui sont renseignés par injection de dépendances :

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

Les options de durée de vie et d’inscription contiennent un exemple complet d’intergiciel avec des services de durée de vie étendue.

Méthode d’extension d’intergiciel

La méthode d’extension suivante expose le middleware par le biais de IApplicationBuilder :

using Microsoft.AspNetCore.Builder;

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

Le code suivant appelle l’intergiciel à partir 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}");
        });
    }
}

Ressources supplémentaires