Intergiciel (middleware) ASP.NET Core
Remarque
Ceci n’est pas la dernière version de cet article. Pour la version actuelle, consultez la version .NET 9 de cet article.
Avertissement
Cette version d’ASP.NET Core n’est plus prise en charge. Pour plus d’informations, consultez la Stratégie de prise en charge de .NET et .NET Core. Pour la version actuelle, consultez la version .NET 8 de cet article.
Important
Ces informations portent sur la préversion du produit, qui est susceptible d’être en grande partie modifié avant sa commercialisation. Microsoft n’offre aucune garantie, expresse ou implicite, concernant les informations fournies ici.
Pour la version actuelle, consultez la version .NET 9 de cet article.
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.
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();
Court-circuiter la pipeline de requête
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 rest 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
pendant ou après que la réponse a été envoyée au client. Une fois qu’une HttpResponse a démarré, les modifications provoquent une exception. Par exemple, définir des en-têtes et un code d’état lève une exception après le début de la réponse. Écrire dans le corps de la réponse après avoir appelé next
:
- Peut provoquer une violation de protocole, comme écrire plus que la valeur déclarée pour
Content-Length
. - Peut endommager le format du corps, comme écrire 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.
Pour plus d’informations, consultez Court-circuiter l’intergiciel après le routage.
Run
délégués
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.
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.
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 Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebMiddleware.Data;
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
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();
builder.Services.AddControllersWithViews();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
// app.UseCookiePolicy();
app.UseRouting();
// app.UseRateLimiter();
// app.UseRequestLocalization();
// app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
// app.UseSession();
// app.UseResponseCompression();
// app.UseResponseCaching();
app.MapRazorPages();
app.MapDefaultControllerRoute();
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
etUseAuthorization
doivent s’afficher dans l’ordre indiqué.UseCors
doit apparaître avantUseResponseCaching
. 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.UseStaticFiles()
.- UseRateLimiter doit être appelé après
UseRouting
lorsque des API spécifiques au point de terminaison de limitation de débit sont utilisées. Par exemple, si l’attribut[EnableRateLimiting]
est utilisé,UseRateLimiter
doit être appelé aprèsUseRouting
. Lorsque vous appelez uniquement des limiteurs globaux,UseRateLimiter
peut être appelé avantUseRouting
.
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 :
- 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
.
- Quand l’application s’exécute dans l’environnement de développement :
- Le middleware Redirection HTTPS (UseHttpsRedirection) redirige les requêtes HTTP vers HTTPS.
- Le middleware Fichier statique (UseStaticFiles) retourne des fichiers statiques et court-circuite tout traitement supplémentaire de la requête.
- 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).
- Middleware Routing (UseRouting) pour router les requêtes.
- Le middleware Authentification (UseAuthentication) tente d’authentifier l’utilisateur avant qu’il ne soit autorisé à accéder aux ressources sécurisées.
- Middleware Authorization (UseAuthorization) autorise un utilisateur à accéder aux ressources sécurisées.
- 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.
- 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 monopage, consultez Vue d’ensemble des applications monopage (SPA) dans ASP.NET Core.
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
L’intergiciel d’en-têtes transférés doit s’exécuter avant tout autre intergiciel. 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.");
});
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.");
});
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.");
});
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.");
});
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 WebApplication.
Intergiciel (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 les cookies. 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. |
Mise en cache de sortie | Prend en charge la mise en cache des réponses en fonction de la configuration. | Avant les composants qui nécessitent la mise en cache. UseRouting doit se situer avant UseOutputCaching . UseCORS doit se situer avant UseOutputCaching . |
Mise en cache des réponses | Prend en charge la mise en cache des réponses. Nécessite la participation du client. Utilisez la mise en cache de sortie pour un contrôle serveur complet. | Avant les composants qui nécessitent la mise en cache. UseCORS doit se situer avant UseResponseCaching . N’est généralement pas bénéfique pour les applications d’interface utilisateur telles que Razor Pages, car les navigateurs définissent habituellement des en-têtes de requête qui empêchent la mise en cache. La mise en cache de sortie est utile pour les applications d’interface utilisateur. |
Décompression des requêtes | Prend en charge la décompression des requêtes. | Avant les composants qui lisent le corps des requêtes. |
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. |
Délais d'attente des tests | Permet de configurer les délais d'attente des requêtes, global et par point d'extrémité. | UseRequestTimeouts doit succéder à UseExceptionHandler , UseDeveloperExceptionPage et UseRouting . |
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
- Les options de durée de vie et d’inscription contiennent un exemple complet de middleware avec des services de durée de vie délimitée, temporaire et singleton.
- Écrire un intergiciel (middleware) ASP.NET Core personnalisé
- Tester les middlewares ASP.NET Core
- Configurer gRPC-Web dans ASP.NET Core
- Migrer des gestionnaires et des modules HTTP vers des middlewares ASP.NET Core
- Démarrage d’une application dans ASP.NET Core
- Fonctionnalités de requête dans ASP.NET Core
- Activation d’un intergiciel (middleware) basé sur une fabrique dans ASP.NET Core
- Activation d’un intergiciel (middleware) avec un conteneur tiers dans 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.
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 rest 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.
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.
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 Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebMiddleware.Data;
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
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();
builder.Services.AddControllersWithViews();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
// app.UseCookiePolicy();
app.UseRouting();
// app.UseRateLimiter();
// app.UseRequestLocalization();
// app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
// app.UseSession();
// app.UseResponseCompression();
// app.UseResponseCaching();
app.MapRazorPages();
app.MapDefaultControllerRoute();
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
etUseAuthorization
doivent s’afficher dans l’ordre indiqué.UseCors
doit apparaître avantUseResponseCaching
. 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.UseStaticFiles()
.- UseRateLimiter doit être appelé après
UseRouting
lorsque des API spécifiques au point de terminaison de limitation de débit sont utilisées. Par exemple, si l’attribut[EnableRateLimiting]
est utilisé,UseRateLimiter
doit être appelé aprèsUseRouting
. Lorsque vous appelez uniquement des limiteurs globaux,UseRateLimiter
peut être appelé avantUseRouting
.
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 :
- 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
.
- Quand l’application s’exécute dans l’environnement de développement :
- Le middleware Redirection HTTPS (UseHttpsRedirection) redirige les requêtes HTTP vers HTTPS.
- Le middleware Fichier statique (UseStaticFiles) retourne des fichiers statiques et court-circuite tout traitement supplémentaire de la requête.
- 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).
- Middleware Routing (UseRouting) pour router les requêtes.
- Le middleware Authentification (UseAuthentication) tente d’authentifier l’utilisateur avant qu’il ne soit autorisé à accéder aux ressources sécurisées.
- Middleware Authorization (UseAuthorization) autorise un utilisateur à accéder aux ressources sécurisées.
- 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.
- 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
L’intergiciel d’en-têtes transférés doit s’exécuter avant tout autre intergiciel. 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.");
});
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.");
});
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.");
});
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.");
});
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 WebApplication.
Intergiciel (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 les cookies. 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. |
Mise en cache de sortie | Prend en charge la mise en cache des réponses en fonction de la configuration. | Avant les composants qui nécessitent la mise en cache. UseRouting doit se situer avant UseOutputCaching . UseCORS doit se situer avant UseOutputCaching . |
Mise en cache des réponses | Prend en charge la mise en cache des réponses. Nécessite la participation du client. Utilisez la mise en cache de sortie pour un contrôle serveur complet. | Avant les composants qui nécessitent la mise en cache. UseCORS doit se situer avant UseResponseCaching . N’est généralement pas bénéfique pour les applications d’interface utilisateur telles que Razor Pages, car les navigateurs définissent habituellement des en-têtes de requête qui empêchent la mise en cache. La mise en cache de sortie est utile pour les applications d’interface utilisateur. |
Décompression des requêtes | Prend en charge la décompression des requêtes. | Avant les composants qui lisent le corps des requêtes. |
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
- Les options de durée de vie et d’inscription contiennent un exemple complet de middleware avec des services de durée de vie délimitée, temporaire et singleton.
- Écrire un intergiciel (middleware) ASP.NET Core personnalisé
- Tester les middlewares ASP.NET Core
- Configurer gRPC-Web dans ASP.NET Core
- Migrer des gestionnaires et des modules HTTP vers des middlewares ASP.NET Core
- Démarrage d’une application dans ASP.NET Core
- Fonctionnalités de requête dans ASP.NET Core
- Activation d’un intergiciel (middleware) basé sur une fabrique dans ASP.NET Core
- Activation d’un intergiciel (middleware) avec un conteneur tiers dans 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.
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 rest 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.
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.
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
etUseAuthorization
doivent s’afficher dans l’ordre indiqué.UseCors
doit apparaître avantUseResponseCaching
. 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 :
- 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
.
- Quand l’application s’exécute dans l’environnement de développement :
- Le middleware Redirection HTTPS (UseHttpsRedirection) redirige les requêtes HTTP vers HTTPS.
- Le middleware Fichier statique (UseStaticFiles) retourne des fichiers statiques et court-circuite tout traitement supplémentaire de la requête.
- 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).
- Middleware Routing (UseRouting) pour router les requêtes.
- Le middleware Authentification (UseAuthentication) tente d’authentifier l’utilisateur avant qu’il ne soit autorisé à accéder aux ressources sécurisées.
- Middleware Authorization (UseAuthorization) autorise un utilisateur à accéder aux ressources sécurisées.
- 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.
- 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
L’intergiciel d’en-têtes transférés doit s’exécuter avant tout autre intergiciel. 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.");
});
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.");
});
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.");
});
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.");
});
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 WebApplication.
Intergiciel (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 les cookies. 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
- Les options de durée de vie et d’inscription contiennent un exemple complet de middleware avec des services de durée de vie délimitée, temporaire et singleton.
- Écrire un intergiciel (middleware) ASP.NET Core personnalisé
- Tester les middlewares ASP.NET Core
- Configurer gRPC-Web dans ASP.NET Core
- Migrer des gestionnaires et des modules HTTP vers des middlewares ASP.NET Core
- Démarrage d’une application dans ASP.NET Core
- Fonctionnalités de requête dans ASP.NET Core
- Activation d’un intergiciel (middleware) basé sur une fabrique dans ASP.NET Core
- Activation d’un intergiciel (middleware) avec un conteneur tiers dans 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).
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.
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 rest 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.
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).
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
etUseAuthorization
doivent s’afficher dans l’ordre indiqué.UseCors
doit se trouver avantUseResponseCaching
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 :
- 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
.
- Quand l’application s’exécute dans l’environnement de développement :
- Le middleware Redirection HTTPS (UseHttpsRedirection) redirige les requêtes HTTP vers HTTPS.
- Le middleware Fichier statique (UseStaticFiles) retourne des fichiers statiques et court-circuite tout traitement supplémentaire de la requête.
- 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).
- Middleware Routing (UseRouting) pour router les requêtes.
- Le middleware Authentification (UseAuthentication) tente d’authentifier l’utilisateur avant qu’il ne soit autorisé à accéder aux ressources sécurisées.
- Middleware Authorization (UseAuthorization) autorise un utilisateur à accéder aux ressources sécurisées.
- 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.
- 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
L’intergiciel d’en-têtes transférés doit s’exécuter avant tout autre intergiciel. 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.");
});
}
}
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.");
});
}
}
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.
Intergiciel (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 les cookies. 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
- Les options de durée de vie et d’inscription contiennent un exemple complet de middleware avec des services de durée de vie délimitée, temporaire et singleton.
- Écrire un intergiciel (middleware) ASP.NET Core personnalisé
- Tester les middlewares ASP.NET Core
- Migrer des gestionnaires et des modules HTTP vers des middlewares ASP.NET Core
- Démarrage d’une application dans ASP.NET Core
- Fonctionnalités de requête dans ASP.NET Core
- Activation d’un intergiciel (middleware) basé sur une fabrique dans ASP.NET Core
- Activation d’un intergiciel (middleware) avec un conteneur tiers dans ASP.NET Core