Intergiciel (middleware) ASP.NET Core

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. Chaque composant :

  • Choisit de passer la requête au composant suivant dans le pipeline.
  • Peut travailler avant et après le composant suivant dans le pipeline.

Les délégués de requête sont utilisés pour créer le pipeline de requête. Les délégués de requête gèrent chaque requête HTTP.

Les délégués de requête sont configurés à l’aide des méthodes d’extension Run, Map et Use. Chaque délégué de requête peut être spécifié inline comme méthode anonyme (appelée intergiciel inline) ou peut être défini dans une classe réutilisable. Ces classes réutilisables et les méthodes anonymes inline sont des middlewares, également appelés composants de middleware. Chaque composant de middleware du pipeline de requête est chargé d’appeler le composant suivant du pipeline ou de court-circuiter le pipeline. Lorsqu’un middleware effectue un court-circuit, on parle de middleware terminal, car il empêche tout autre middleware de traiter la requête.

Migrer des gestionnaires et des modules HTTP vers des middlewares ASP.NET Core explique les différences qui existent entre les pipelines de requêtes dans ASP.NET Core et dans ASP.NET 4.x, et fournit d’autres exemples de middlewares (intergiciels).

Analyse du code des middlewares

ASP.NET Core inclut de nombreux analyseurs de plateforme de compilateur qui inspectent le code d’application à des fins de qualité. Pour plus d’informations, consultez Analyse du code dans les applications ASP.NET Core.

Créer un pipeline de middlewares avec WebApplication

Le pipeline de requête ASP.NET Core est composé d’une séquence de délégués de requête, appelés l’un après l’autre. Le diagramme suivant illustre le concept. Le thread d’exécution suit les flèches noires.

Modèle de traitement des requêtes montrant l’arrivée d’une requête, son passage par trois middlewares et la réponse obtenue en quittant l’application. Chaque middleware exécute sa logique et transmet la requête au middleware suivant au niveau de l’instruction next(). Une fois que le troisième middleware a traité la requête, celle-ci repasse par les deux middlewares précédents dans le sens inverse en vue d’un traitement supplémentaire après les instructions next(), avant de quitter l’application en tant que réponse au client.

Chaque délégué peut effectuer des opérations avant et après le délégué suivant. Les délégués de gestion des exceptions doivent être appelés tôt dans le pipeline pour qu’ils puissent intercepter les exceptions qui se produisent dans les phases ultérieures du pipeline.

L’application ASP.NET Core la plus simple possible définit un seul délégué de requête qui gère toutes les requêtes. Ce cas ne fait pas appel à un pipeline de requête réel. À la place, une seule fonction anonyme est appelée en réponse à chaque requête HTTP.

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

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

app.Run();

Chaînez plusieurs délégués de requête avec Use. Le paramètre next représente le délégué suivant dans le pipeline. Vous pouvez court-circuiter le pipeline en n’appelant pas le paramètre next. Vous pouvez généralement effectuer des actions à la fois avant et après le délégué next, comme illustré dans cet exemple :

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

Quand un délégué ne passe pas une requête au délégué suivant, on parle alors de court-circuit du pipeline de requête. Un court-circuit est souvent souhaitable car il évite le travail inutile. Par exemple, le middleware Fichier statique peut agir en tant que middleware terminal en traitant une requête pour un fichier statique et en court-circuitant le reste du pipeline. Le middleware ajouté au pipeline avant de le middleware qui met fin à la poursuite du traitement traite tout de même le code après les instructions next.Invoke. Toutefois, consultez l’avertissement suivant à propos de la tentative d’écriture sur une réponse qui a déjà été envoyée.

Avertissement

N’appelez pas next.Invoke une fois que la réponse a été envoyée au client. Les changements apportés à HttpResponse après le démarrage de la réponse lèvent une exception. Par exemple, le fait de définir des en-têtes et du code d’état lève une exception. Écrire dans le corps de la réponse après avoir appelé next :

  • Peut entraîner une violation de protocole. Par exemple, écrire plus que le Content-Length indiqué.
  • Peut altérer le format du corps. Par exemple, l’écriture d’un pied de page HTML dans un fichier CSS.

HasStarted est un indice utile pour indiquer si les en-têtes ont été envoyés ou si le corps a fait l’objet d’écritures.

Les délégués Run ne reçoivent pas de paramètre next. Le premier délégué Run est toujours terminal et termine le pipeline. Run est une convention. Certains composants middleware peuvent exposer des méthodes Run[Middleware] qui s’exécutent à la fin du pipeline :

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 vous souhaitez voir les commentaires de code traduits dans une langue autre que l’anglais, dites-le nous dans cette discussion GitHub.

Dans l’exemple précédent, le délégué Run écrit "Hello from 2nd delegate." dans la réponse, puis termine le pipeline. Si un autre délégué Use ou Run est ajouté après le délégué Run, il ne sera pas appelé.

Préférez une surcharge app.Use qui nécessite le passage du contexte à l’instruction next()

La méthode d’extension app.Use non-allouante :

  • Nécessite le passage du contexte à next.
  • Enregistre deux allocations internes par requête, qui sont nécessaires lors de l’utilisation de l’autre surcharge.

Pour plus d’informations, consultez ce problème GitHub.

Ordre des middlewares

Le schéma suivant montre le pipeline complet de traitement des requêtes pour les applications ASP.NET Core MVC et Razor Pages. Vous pouvez voir comment, dans une application classique, les middlewares existants sont ordonnés et à quel endroit les middlewares personnalisés sont ajoutés. Vous avez un contrôle total sur la façon de réorganiser les middlewares existants et d’injecter de nouveaux middlewares personnalisés si nécessaire pour vos scénarios.

Pipeline de middlewares ASP.NET Core

Le middleware Endpoint du schéma précédent exécute le pipeline de filtre pour le type d’application correspondant (MVC ou Razor Pages).

Dans le schéma précédent, le middleware Routing suit Static Files. Il s’agit de l’ordre dans lequel les modèles du projet sont implémentés en appelant explicitement app.UseRouting. Si vous n’appelez pas app.UseRouting, le middleware Routing s’exécutera au début du pipeline par défaut. Pour plus d’informations, consultez Routage.

Pipeline de filtre ASP.NET Core

L’ordre dans lequel les composants de middleware sont ajoutés au fichier Program.cs définit l’ordre dans lequel ils seront appelés lors des requêtes et l’ordre inverse pour la réponse. L’ordre est critique pour la sécurité, les performances et le fonctionnement.

Le code mis en évidence ci-dessous dans Program.cs ajoute des composants middleware liés à la sécurité, dans l’ordre généralement recommandé :

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

Dans le code précédent :

  • Les middlewares qui ne sont pas ajoutés lors de la création d’une application web avec des comptes d’utilisateurs individuels sont commentés.
  • Tous les middlewares ne s’affichent pas dans cet ordre exact, mais beaucoup le sont. Par exemple :
    • UseCors, UseAuthentication et UseAuthorization doivent s’afficher dans l’ordre indiqué.
    • UseCors doit apparaître avant UseResponseCaching. Cette exigence est expliquée dans le problème GitHub dotnet/aspnetcore n° 23218.
    • UseRequestLocalization doit apparaître avant tout middleware pouvant vérifier la culture de la requête (par exemple, app.UseMvcWithDefaultRoute()).

Dans certains scénarios, les middlewares sont ordonnés différemment. Par exemple, l’ordre de la mise en cache et de la compression dépend du scénario, et il existe plusieurs ordres valides. Par exemple :

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

Avec le code précédent, vous pouvez réduire l’utilisation du processeur en mettant en cache la réponse compressée. Cependant, vous risquez ainsi de mettre en cache plusieurs représentations d’une même ressource à l’aide d’algorithmes de compression différents tels que Gzip ou Brotli.

L’ordre suivant combine des fichiers statiques pour permettre la mise en cache des fichiers statiques compressés :

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

Le code Program.cs suivant ajoute des composants middleware utiles pour les scénarios d’application courants :

  1. Gestion des erreurs/exceptions
    • Quand l’application s’exécute dans l’environnement de développement :
      • Le middleware Page d’exceptions du développeur (UseDeveloperExceptionPage) signale des erreurs de runtime de l’application.
      • Le middleware Page d’erreur de base de données (UseDatabaseErrorPage) signale des erreurs de runtime de la base de données.
    • Quand l’application s’exécute dans l’environnement de production :
      • Le middleware Gestionnaire d'exceptions (UseExceptionHandler) intercepte des exceptions levées dans les middlewares suivants.
      • Le middleware Protocole HSTS (HTTP Strict Transport Security) (UseHsts) ajoute l’en-tête Strict-Transport-Security.
  2. Le middleware Redirection HTTPS (UseHttpsRedirection) redirige les requêtes HTTP vers HTTPS.
  3. Le middleware Fichier statique (UseStaticFiles) retourne des fichiers statiques et court-circuite tout traitement supplémentaire de la requête.
  4. Le middleware Cookie Policy (UseCookiePolicy) met l’application en conformité avec les réglementations du RGPD (Règlement général sur la protection des données).
  5. Middleware Routing (UseRouting) pour router les requêtes.
  6. Le middleware Authentification (UseAuthentication) tente d’authentifier l’utilisateur avant qu’il ne soit autorisé à accéder aux ressources sécurisées.
  7. Middleware Authorization (UseAuthorization) autorise un utilisateur à accéder aux ressources sécurisées.
  8. Le middleware Session (UseSession) établit et maintient l’état de la session. Si l’application utilise l’état de session, appelez le middleware Session après le middleware Cookie Policy et avant le middleware MVC.
  9. Middleware Endpoint Routing (UseEndpoints avec MapRazorPages) pour ajouter des points de terminaison Razor Pages au pipeline de requête.
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();

Dans l’exemple de code précédent, chaque méthode d’extension d’intergiciel (middleware) est exposée sur WebApplicationBuilder à travers l’espace de noms Microsoft.AspNetCore.Builder.

UseExceptionHandler est le premier composant d’intergiciel ajouté au pipeline. Par conséquent, le middleware Gestion des exceptions intercepte toutes les exceptions qui se produisent dans les appels ultérieurs.

Le middleware Fichier statique est appelé tôt dans le pipeline pour qu’il puisse gérer les requêtes et procéder au court-circuit sans passer par les composants restants. Le middleware Fichier statique ne fournit aucune vérification d’autorisation. Tous les fichiers distribués par le middleware Static File, notamment ceux sous wwwroot, sont accessibles publiquement. Pour connaître une approche permettant de sécuriser les fichiers statiques, consultez Fichiers statiques dans ASP.NET Core.

Si la requête n’est pas gérée par le middleware Fichier statique, elle est transmise au middleware Authentification (UseAuthentication), qui effectue l’authentification. Le middleware Authentification ne court-circuite pas les requêtes non authentifiées. Même si le middleware Authentication authentifie les requêtes, l’autorisation et le refus interviennent uniquement après que MVC a sélectionné un contrôleur ou une action Razor Pages ou MVC.

L’exemple suivant montre un ordre de middlewares où les requêtes pour les fichiers statiques sont gérées par le middleware Fichier statique avant le middleware Compression de la réponse. Les fichiers statiques ne sont pas compressés avec cet ordre de middlewares. Les réponses Razor Pages peuvent être compressées.

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

app.UseRouting();

app.UseResponseCompression();

app.MapRazorPages();

Pour plus d’informations sur les applications monopages, consultez les guides des modèles de projet React et Angular.

Ordre des UseCors et des UseStaticFiles

L’ordre d’appel des UseCors et des UseStaticFiles dépend de l’application. Pour plus d’informations, consultez Ordre des UseCors et des UseStaticFiles

Ordre du middleware Forwarded Headers

Le middleware Forwarded Headers doit s’exécuter avant tout autre middleware. Cet ordre permet au middleware qui repose sur les informations des en-têtes transférés d’utiliser les valeurs d’en-tête pour le traitement. Pour exécuter le middleware Forwarded Headers après les middlewares Diagnostics et Error Handling, consultez Ordre du middleware Forwarded Headers.

Créer une branche dans le pipeline de middlewares

Les extensions Map sont utilisées comme convention pour créer une branche dans le pipeline. Map crée une branche dans le pipeline de requête en fonction des correspondances du chemin de requête donné. Si le chemin de requête commence par le chemin donné, la branche est exécutée.

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

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

Le tableau suivant présente les requêtes et les réponses de http://localhost:1234 avec le code précédent.

Requête response
localhost:1234 Hello from non-Map delegate.
localhost:1234/map1 Map Test 1
localhost:1234/map2 Map Test 2
localhost:1234/map3 Hello from non-Map delegate.

Quand Map est utilisé, les segments de chemin mis en correspondance sont supprimés de HttpRequest.Path et ajoutés à HttpRequest.PathBase pour chaque requête.

Map prend en charge l’imbrication, par exemple :

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

Map peut également faire correspondre plusieurs segments à la fois :

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

app.Run();

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

MapWhen crée une branche dans le pipeline de requête en fonction du résultat du prédicat donné. Un prédicat de type Func<HttpContext, bool> peut être utilisé pour mapper les requêtes à une nouvelle branche du pipeline. Dans l’exemple suivant, un prédicat est utilisé pour détecter la présence d’une variable de chaîne de requête branch :

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

app.Run();

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

Le tableau suivant présente les requêtes et les réponses de http://localhost:1234 avec le code précédent :

Requête response
localhost:1234 Hello from non-Map delegate.
localhost:1234/?branch=main Branch used = main

UseWhen crée également une branche dans le pipeline de requête en fonction du résultat du prédicat donné. Contrairement à lorsque vous utilisez MapWhen, cette branche est rejointe au pipeline principal si elle ne court-circuite pas ou ne contient pas un middleware 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. <p>");
});

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

Dans l’exemple précédent, une réponse Hello from non-Map delegate. est écrite pour toutes les requêtes. Si la requête inclut une variable de chaîne de requête branch, sa valeur est journalisée avant la jonction au pipeline principal.

Intergiciels (middleware) intégrés

ASP.NET Core est fourni avec les composants de middleware suivant. La colonne Ordre fournit des notes sur l’emplacement du middleware dans le pipeline de traitement de la requête et sur les conditions dans lesquelles le middleware peut mettre fin au traitement de la requête. Lorsqu’un middleware court-circuite le pipeline de traitement de la requête et empêche tout middleware en aval de traiter une requête, on parle de middleware terminal. Pour plus d’informations sur le court-circuit, consultez la section Créer un pipeline de middlewares avec IApplicationBuilder.

Middleware Description Commande
Authentification Prend en charge l’authentification. Avant que HttpContext.User ne soit nécessaire. Terminal pour les rappels OAuth.
Autorisation Fournit la prise en charge des autorisations. Immédiatement après le middleware Authentication.
Cookie Policy Effectue le suivi de consentement des utilisateurs pour le stockage des informations personnelles et applique des standards minimaux pour les champs cookie, comme secure et SameSite. Avant le middleware qui émet des cookie. Exemples : authentification, session, MVC (TempData).
CORS Configure le partage des ressources cross-origin (CORS). Avant les composants qui utilisent CORS. UseCors doit se trouver avant UseResponseCaching en raison de ce bogue.
DeveloperExceptionPage Génère une page contenant des informations d’erreur destinées à être utilisées uniquement dans l’environnement de développement. Avant les composants qui génèrent des erreurs. Les modèles de projet inscrivent automatiquement ce middleware comme premier middleware dans le pipeline lorsque l’environnement est un environnement de développement.
Diagnostics Plusieurs middlewares distincts qui fournissent une page d’exceptions du développeur, la gestion des exceptions, les pages de codes d’état et la page web par défaut pour les nouvelles applications. Avant les composants qui génèrent des erreurs. Terminal pour les exceptions ou la distribution de la page web par défaut pour les nouvelles applications.
En-têtes transférés Transfère les en-têtes en proxy vers la requête actuelle. Avant les composants qui consomment les champs mis à jour. Exemples : schéma, hôte, IP du client, méthode.
Contrôle d’intégrité Contrôle l’intégrité d’une application ASP.NET Core et de ses dépendances, notamment la disponibilité de la base de données. Terminal si une requête correspond à un point de terminaison de contrôle d’intégrité.
Propagation des en-têtes Propage les en-têtes HTTP de la requête entrante vers les requêtes sortantes du client HTTP.
Journalisation HTTP Journalise les requêtes et les réponses HTTP. Au début du pipeline de middlewares.
Remplacement de la méthode HTTP Autorise une requête POST entrante à remplacer la méthode. Avant les composants qui consomment la méthode mise à jour.
Redirection HTTPS Redirige toutes les requêtes HTTP vers HTTPS. Avant les composants qui consomment l’URL.
HSTS (HTTP Strict Transport Security) Middleware d’amélioration de la sécurité qui ajoute un en-tête de réponse spécial. Avant l’envoi des réponses et après les composants qui modifient les requêtes. Exemples : en-têtes transférés, réécriture d’URL.
MVC Traite les requêtes avec MVC/Razor Pages. Terminal si une requête correspond à un itinéraire.
OWIN Interopérabilité avec le middleware, les serveurs et les applications OWIN. Terminal si le middleware OWIN traite entièrement la requête.
Décompression des requêtes Prend en charge la décompression des requêtes. Avant les composants qui lisent le corps des requêtes.
Mise en cache des réponses Prend en charge la mise en cache des réponses. Avant les composants qui nécessitent la mise en cache. UseCORS doit se situer avant UseResponseCaching.
Compression des réponses Prend en charge la compression des réponses. Avant les composants qui nécessitent la compression.
Localisation des requêtes Prend en charge la localisation. Avant la localisation des composants sensibles. Doit se situer après le middleware Routing lorsque vous utilisez RouteDataRequestCultureProvider.
Routage de point de terminaison Définit et contraint des routes de requête. Terminal pour les routes correspondantes.
SPA Gère toutes les requêtes issues de ce point dans la chaîne de middleware en retournant la page par défaut de l’application monopage Plus loin dans la chaîne, afin que les autres middlewares permettant de distribuer des fichiers statiques, des actions MVC, etc., soient prioritaires.
Session Prend en charge la gestion des sessions utilisateur. Avant les composants qui nécessitent la session.
Fichiers statiques Prend en charge le traitement des fichiers statiques et l’exploration des répertoires. Terminal si une requête correspond à un fichier.
URL Rewrite Prend en charge la réécriture d’URL et la redirection des requêtes. Avant les composants qui consomment l’URL.
W3CLogging Génère les journaux d’accès au serveur au format de fichier journal étendu W3C. Au début du pipeline de middlewares.
WebSockets Autorise le protocole WebSockets. Avant les composants qui sont nécessaires pour accepter les requêtes WebSocket.

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. Chaque composant :

  • Choisit de passer la requête au composant suivant dans le pipeline.
  • Peut travailler avant et après le composant suivant dans le pipeline.

Les délégués de requête sont utilisés pour créer le pipeline de requête. Les délégués de requête gèrent chaque requête HTTP.

Les délégués de requête sont configurés à l’aide des méthodes d’extension Run, Map et Use. Chaque délégué de requête peut être spécifié inline comme méthode anonyme (appelée intergiciel inline) ou peut être défini dans une classe réutilisable. Ces classes réutilisables et les méthodes anonymes inline sont des middlewares, également appelés composants de middleware. Chaque composant de middleware du pipeline de requête est chargé d’appeler le composant suivant du pipeline ou de court-circuiter le pipeline. Lorsqu’un middleware effectue un court-circuit, on parle de middleware terminal, car il empêche tout autre middleware de traiter la requête.

Migrer des gestionnaires et des modules HTTP vers des middlewares ASP.NET Core explique les différences qui existent entre les pipelines de requêtes dans ASP.NET Core et dans ASP.NET 4.x, et fournit d’autres exemples de middlewares (intergiciels).

Créer un pipeline de middleware avec IApplicationBuilder

Le pipeline de requête ASP.NET Core est composé d’une séquence de délégués de requête, appelés l’un après l’autre. Le diagramme suivant illustre le concept. Le thread d’exécution suit les flèches noires.

Modèle de traitement des requêtes montrant l’arrivée d’une requête, son passage par trois middlewares et la réponse obtenue en quittant l’application. Chaque middleware exécute sa logique et transmet la requête au middleware suivant au niveau de l’instruction next(). Une fois que le troisième middleware a traité la requête, celle-ci repasse par les deux middlewares précédents dans le sens inverse en vue d’un traitement supplémentaire après les instructions next(), avant de quitter l’application en tant que réponse au client.

Chaque délégué peut effectuer des opérations avant et après le délégué suivant. Les délégués de gestion des exceptions doivent être appelés tôt dans le pipeline pour qu’ils puissent intercepter les exceptions qui se produisent dans les phases ultérieures du pipeline.

L’application ASP.NET Core la plus simple possible définit un seul délégué de requête qui gère toutes les requêtes. Ce cas ne fait pas appel à un pipeline de requête réel. À la place, une seule fonction anonyme est appelée en réponse à chaque requête HTTP.

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

Chaînez plusieurs délégués de requête avec Use. Le paramètre next représente le délégué suivant dans le pipeline. Vous pouvez court-circuiter le pipeline en n’appelant pas le paramètre next. Vous pouvez généralement effectuer des actions à la fois avant et après le délégué suivant, comme illustré dans cet exemple :

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

Quand un délégué ne passe pas une requête au délégué suivant, on parle alors de court-circuit du pipeline de requête. Un court-circuit est souvent souhaitable car il évite le travail inutile. Par exemple, le middleware Fichier statique peut agir en tant que middleware terminal en traitant une requête pour un fichier statique et en court-circuitant le reste du pipeline. Le middleware ajouté au pipeline avant de le middleware qui met fin à la poursuite du traitement traite tout de même le code après les instructions next.Invoke. Toutefois, consultez l’avertissement suivant à propos de la tentative d’écriture sur une réponse qui a déjà été envoyée.

Avertissement

N’appelez pas next.Invoke une fois que la réponse a été envoyée au client. Les changements apportés à HttpResponse après le démarrage de la réponse lèvent une exception. Par exemple, le fait de définir des en-têtes et du code d’état lève une exception. Écrire dans le corps de la réponse après avoir appelé next :

  • Peut entraîner une violation de protocole. Par exemple, écrire plus que le Content-Length indiqué.
  • Peut altérer le format du corps. Par exemple, l’écriture d’un pied de page HTML dans un fichier CSS.

HasStarted est un indice utile pour indiquer si les en-têtes ont été envoyés ou si le corps a fait l’objet d’écritures.

Les délégués Run ne reçoivent pas de paramètre next. Le premier délégué Run est toujours terminal et termine le pipeline. Run est une convention. Certains composants middleware peuvent exposer des méthodes Run[Middleware] qui s’exécutent à la fin du pipeline :

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 vous souhaitez voir les commentaires de code traduits dans une langue autre que l’anglais, dites-le nous dans cette discussion GitHub.

Dans l’exemple précédent, le délégué Run écrit "Hello from 2nd delegate." dans la réponse, puis termine le pipeline. Si un autre délégué Use ou Run est ajouté après le délégué Run, il ne sera pas appelé.

Ordre des middlewares

Le schéma suivant montre le pipeline complet de traitement des requêtes pour les applications ASP.NET Core MVC et Razor Pages. Vous pouvez voir comment, dans une application classique, les middlewares existants sont ordonnés et à quel endroit les middlewares personnalisés sont ajoutés. Vous avez un contrôle total sur la façon de réorganiser les middlewares existants et d’injecter de nouveaux middlewares personnalisés si nécessaire pour vos scénarios.

Pipeline de middlewares ASP.NET Core

Le middleware Endpoint du schéma précédent exécute le pipeline de filtre pour le type d’application correspondant (MVC ou Razor Pages).

Pipeline de filtre ASP.NET Core

L’ordre dans lequel les composants de middleware sont ajoutés dans la méthode Startup.Configure définit l’ordre dans lequel ils sont appelés sur les requêtes et l’ordre inverse pour la réponse. L’ordre est critique pour la sécurité, les performances et le fonctionnement.

La méthode Startup.Configure suivante ajoute des composants middleware liés à la sécurité, dans l’ordre généralement recommandé :

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

Dans le code précédent :

  • Les middlewares qui ne sont pas ajoutés lors de la création d’une application web avec des comptes d’utilisateurs individuels sont commentés.
  • Tous les middlewares ne s’affichent pas dans cet ordre exact, mais beaucoup le sont. Par exemple :
    • UseCors, UseAuthentication et UseAuthorization doivent s’afficher dans l’ordre indiqué.
    • UseCors doit se trouver avant UseResponseCaching en raison de ce bogue.
    • UseRequestLocalization doit apparaître avant tout middleware pouvant vérifier la culture de la requête (par exemple, app.UseMvcWithDefaultRoute()).

Dans certains scénarios, les middlewares sont ordonnés différemment. Par exemple, l’ordre de la mise en cache et de la compression dépend du scénario, et il existe plusieurs ordres valides. Par exemple :

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

Avec le code précédent, vous pouvez réduire l’utilisation du processeur en mettant en cache la réponse compressée. Cependant, vous risquez ainsi de mettre en cache plusieurs représentations d’une même ressource à l’aide d’algorithmes de compression différents tels que Gzip ou Brotli.

L’ordre suivant combine des fichiers statiques pour permettre la mise en cache des fichiers statiques compressés :

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

La méthode Startup.Configure suivante ajoute des composants middleware utiles pour les scénarios d’application courants :

  1. Gestion des erreurs/exceptions
    • Quand l’application s’exécute dans l’environnement de développement :
      • Le middleware Page d’exceptions du développeur (UseDeveloperExceptionPage) signale des erreurs de runtime de l’application.
      • Le middleware Database Error Page signale des erreurs de runtime de la base de données.
    • Quand l’application s’exécute dans l’environnement de production :
      • Le middleware Gestionnaire d'exceptions (UseExceptionHandler) intercepte des exceptions levées dans les middlewares suivants.
      • Le middleware Protocole HSTS (HTTP Strict Transport Security) (UseHsts) ajoute l’en-tête Strict-Transport-Security.
  2. Le middleware Redirection HTTPS (UseHttpsRedirection) redirige les requêtes HTTP vers HTTPS.
  3. Le middleware Fichier statique (UseStaticFiles) retourne des fichiers statiques et court-circuite tout traitement supplémentaire de la requête.
  4. Le middleware Cookie Policy (UseCookiePolicy) met l’application en conformité avec les réglementations du RGPD (Règlement général sur la protection des données).
  5. Middleware Routing (UseRouting) pour router les requêtes.
  6. Le middleware Authentification (UseAuthentication) tente d’authentifier l’utilisateur avant qu’il ne soit autorisé à accéder aux ressources sécurisées.
  7. Middleware Authorization (UseAuthorization) autorise un utilisateur à accéder aux ressources sécurisées.
  8. Le middleware Session (UseSession) établit et maintient l’état de la session. Si l’application utilise l’état de session, appelez le middleware Session après le middleware Cookie Policy et avant le middleware MVC.
  9. Middleware Endpoint Routing (UseEndpoints avec MapRazorPages) pour ajouter des points de terminaison Razor Pages au pipeline de requête.
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();
    });
}

Dans l’exemple de code précédent, chaque méthode d’extension d’intergiciel (middleware) est exposée sur IApplicationBuilder à travers l’espace de noms Microsoft.AspNetCore.Builder.

UseExceptionHandler est le premier composant d’intergiciel ajouté au pipeline. Par conséquent, le middleware Gestion des exceptions intercepte toutes les exceptions qui se produisent dans les appels ultérieurs.

Le middleware Fichier statique est appelé tôt dans le pipeline pour qu’il puisse gérer les requêtes et procéder au court-circuit sans passer par les composants restants. Le middleware Fichier statique ne fournit aucune vérification d’autorisation. Tous les fichiers distribués par le middleware Static File, notamment ceux sous wwwroot, sont accessibles publiquement. Pour connaître une approche permettant de sécuriser les fichiers statiques, consultez Fichiers statiques dans ASP.NET Core.

Si la requête n’est pas gérée par le middleware Fichier statique, elle est transmise au middleware Authentification (UseAuthentication), qui effectue l’authentification. Le middleware Authentification ne court-circuite pas les requêtes non authentifiées. Même si le middleware Authentication authentifie les requêtes, l’autorisation et le refus interviennent uniquement après que MVC a sélectionné un contrôleur ou une action Razor Pages ou MVC.

L’exemple suivant montre un ordre de middlewares où les requêtes pour les fichiers statiques sont gérées par le middleware Fichier statique avant le middleware Compression de la réponse. Les fichiers statiques ne sont pas compressés avec cet ordre de middlewares. Les réponses Razor Pages peuvent être compressées.

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

Pour les applications monopages, le middleware SPA UseSpaStaticFiles est généralement le dernier dans le pipeline de middlewares. Le middleware SPA vient en dernier :

  • Pour permettre à tous les autres middlewares de répondre en premier aux requêtes correspondantes.
  • Pour permettre aux applications monopages avec un routage côté client de s’exécuter pour toutes les routes qui ne sont pas reconnues par l’application serveur.

Pour plus d’informations sur les applications monopages, consultez les guides des modèles de projet React et Angular.

Ordre du middleware Forwarded Headers

Le middleware Forwarded Headers doit s’exécuter avant tout autre middleware. Cet ordre permet au middleware qui repose sur les informations des en-têtes transférés d’utiliser les valeurs d’en-tête pour le traitement. Pour exécuter le middleware Forwarded Headers après les middlewares Diagnostics et Error Handling, consultez Ordre du middleware Forwarded Headers.

Créer une branche dans le pipeline de middlewares

Les extensions Map sont utilisées comme convention pour créer une branche dans le pipeline. Map crée une branche dans le pipeline de requête en fonction des correspondances du chemin de requête donné. Si le chemin de requête commence par le chemin donné, la branche est exécutée.

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

Le tableau suivant présente les requêtes et les réponses de http://localhost:1234 avec le code précédent.

Requête response
localhost:1234 Hello from non-Map delegate.
localhost:1234/map1 Map Test 1
localhost:1234/map2 Map Test 2
localhost:1234/map3 Hello from non-Map delegate.

Quand Map est utilisé, les segments de chemin mis en correspondance sont supprimés de HttpRequest.Path et ajoutés à HttpRequest.PathBase pour chaque requête.

Map prend en charge l’imbrication, par exemple :

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

Map peut également faire correspondre plusieurs segments à la fois :

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 crée une branche dans le pipeline de requête en fonction du résultat du prédicat donné. Un prédicat de type Func<HttpContext, bool> peut être utilisé pour mapper les requêtes à une nouvelle branche du pipeline. Dans l’exemple suivant, un prédicat est utilisé pour détecter la présence d’une variable de chaîne de requête branch :

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

Le tableau suivant présente les requêtes et les réponses de http://localhost:1234 avec le code précédent :

Requête response
localhost:1234 Hello from non-Map delegate.
localhost:1234/?branch=main Branche utilisée = main

UseWhen crée également une branche dans le pipeline de requête en fonction du résultat du prédicat donné. Contrairement à lorsque vous utilisez MapWhen, cette branche est rejointe au pipeline principal si elle ne court-circuite pas ou ne contient pas un middleware 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.");
        });
    }
}

Dans l’exemple précédent, la réponse « Hello from main pipeline » est écrite pour toutes les requêtes. Si la requête inclut une variable de chaîne de requête branch, sa valeur est journalisée avant la jonction au pipeline principal.

Intergiciels (middleware) intégrés

ASP.NET Core est fourni avec les composants de middleware suivant. La colonne Ordre fournit des notes sur l’emplacement du middleware dans le pipeline de traitement de la requête et sur les conditions dans lesquelles le middleware peut mettre fin au traitement de la requête. Lorsqu’un middleware court-circuite le pipeline de traitement de la requête et empêche tout middleware en aval de traiter une requête, on parle de middleware terminal. Pour plus d’informations sur le court-circuit, consultez la section Créer un pipeline de middlewares avec IApplicationBuilder.

Middleware Description Commande
Authentification Prend en charge l’authentification. Avant que HttpContext.User ne soit nécessaire. Terminal pour les rappels OAuth.
Autorisation Fournit la prise en charge des autorisations. Immédiatement après le middleware Authentication.
Cookie Policy Effectue le suivi de consentement des utilisateurs pour le stockage des informations personnelles et applique des standards minimaux pour les champs cookie, comme secure et SameSite. Avant le middleware qui émet des cookie. Exemples : authentification, session, MVC (TempData).
CORS Configure le partage des ressources cross-origin (CORS). Avant les composants qui utilisent CORS. UseCors doit se trouver avant UseResponseCaching en raison de ce bogue.
Diagnostics Plusieurs middlewares distincts qui fournissent une page d’exceptions du développeur, la gestion des exceptions, les pages de codes d’état et la page web par défaut pour les nouvelles applications. Avant les composants qui génèrent des erreurs. Terminal pour les exceptions ou la distribution de la page web par défaut pour les nouvelles applications.
En-têtes transférés Transfère les en-têtes en proxy vers la requête actuelle. Avant les composants qui consomment les champs mis à jour. Exemples : schéma, hôte, IP du client, méthode.
Contrôle d’intégrité Contrôle l’intégrité d’une application ASP.NET Core et de ses dépendances, notamment la disponibilité de la base de données. Terminal si une requête correspond à un point de terminaison de contrôle d’intégrité.
Propagation des en-têtes Propage les en-têtes HTTP de la requête entrante vers les requêtes sortantes du client HTTP.
Remplacement de la méthode HTTP Autorise une requête POST entrante à remplacer la méthode. Avant les composants qui consomment la méthode mise à jour.
Redirection HTTPS Redirige toutes les requêtes HTTP vers HTTPS. Avant les composants qui consomment l’URL.
HSTS (HTTP Strict Transport Security) Middleware d’amélioration de la sécurité qui ajoute un en-tête de réponse spécial. Avant l’envoi des réponses et après les composants qui modifient les requêtes. Exemples : en-têtes transférés, réécriture d’URL.
MVC Traite les requêtes avec MVC/Razor Pages. Terminal si une requête correspond à un itinéraire.
OWIN Interopérabilité avec le middleware, les serveurs et les applications OWIN. Terminal si le middleware OWIN traite entièrement la requête.
Mise en cache des réponses Prend en charge la mise en cache des réponses. Avant les composants qui nécessitent la mise en cache. UseCORS doit se situer avant UseResponseCaching.
Compression des réponses Prend en charge la compression des réponses. Avant les composants qui nécessitent la compression.
Localisation des requêtes Prend en charge la localisation. Avant la localisation des composants sensibles. Doit se situer après le middleware Routing lorsque vous utilisez RouteDataRequestCultureProvider.
Routage de point de terminaison Définit et contraint des routes de requête. Terminal pour les routes correspondantes.
SPA Gère toutes les requêtes issues de ce point dans la chaîne de middleware en retournant la page par défaut de l’application monopage Plus loin dans la chaîne, afin que les autres middlewares permettant de distribuer des fichiers statiques, des actions MVC, etc., soient prioritaires.
Session Prend en charge la gestion des sessions utilisateur. Avant les composants qui nécessitent la session.
Fichiers statiques Prend en charge le traitement des fichiers statiques et l’exploration des répertoires. Terminal si une requête correspond à un fichier.
URL Rewrite Prend en charge la réécriture d’URL et la redirection des requêtes. Avant les composants qui consomment l’URL.
WebSockets Active le protocole WebSocket. Avant les composants qui sont nécessaires pour accepter les requêtes WebSocket.

Ressources supplémentaires