Comprendre les intergiciels

Effectué

L’objectif d’une application web est de recevoir des requêtes HTTP et d’y répondre. Lorsqu’une requête est reçue, le serveur génère la réponse appropriée. Tout ce que fait ASP.NET Core a trait à ce cycle de requête/réponse.

Quand une application ASP.NET Core reçoit une requête HTTP, celle-ci passe par une série de composants au terme desquels une réponse est générée. Ces composants sont appelés des intergiciels (middleware). Un intergiciel peut être vu comme un pipeline par lequel la requête transite, et chaque couche d’un intergiciel peut exécuter du code avant et après la couche qui suit dans le pipeline.

Diagramme illustrant une requête HTTP et plusieurs intergiciels.

Intergiciels et délégués

Un intergiciel est implémenté en tant que délégué qui accepte un objet HttpContext et retourne une Task. L’objet HttpContext représente la requête et la réponse actuelles. Le délégué est une fonction qui traite la requête et la réponse.

Considérons par exemple le code suivant :

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

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

app.Run();

Dans le code précédent :

  • WebApplication.CreateBuilder(args) crée un objet WebApplicationBuilder.
  • builder.Build() crée un objet WebApplication.
  • Le premier app.Run() définit un délégué qui accepte un objet HttpContext et retourne une Task. Le délégué écrit « Hello world! » dans la réponse.
  • La deuxième app.Run() démarre l’application.

Quand l’application reçoit une requête HTTP, le délégué est appelé. Le délégué écrit « Hello world! » dans la réponse et termine la requête.

Chaînage d’intergiciels

La plupart des applications comptent plusieurs composants intergiciels qui s’exécutent en séquence. L’ordre dans lequel vous ajoutez des composants intergiciels au pipeline est important. Les composants s’exécutent dans l’ordre dans lequel ils ont été ajoutés.

Intergiciels terminaux et non terminaux

Un intergiciel peut être terminal ou non terminal. Un intergiciel non terminal traite la requête, puis appelle l’intergiciel suivant dans le pipeline. L’intergiciel terminal est le dernier intergiciel du pipeline et n’est pas suivi d’un intergiciel à appeler.

Les délégués ajoutés avec app.Use() peuvent être des intergiciels terminaux ou non terminaux. Ces délégués attendent un objet HttpContext et un objet RequestDelegate comme paramètres. En général, le délégué inclut await next.Invoke();. Cela permet de passer le contrôle à l’intergiciel suivant dans le pipeline. Le code avant cette ligne s’exécute avant l’intergiciel suivant, et le code après cette ligne s’exécute après l’intergiciel suivant. Un délégué ajouté avec app.Use() a deux opportunités d’agir sur une requête avant que la réponse ne soit envoyée au client : avant la génération de la réponse par l’intergiciel terminal et une fois la réponse générée par l’intergiciel terminal.

Un délégué ajouté avec app.Run() est toujours un intergiciel terminal. Il n’appelle pas l’intergiciel suivant dans le pipeline. Il s’agit du dernier composant intergiciel qui s’exécute. Il attend uniquement un objet HttpContext comme paramètre. app.Run() est un raccourci pour ajouter un intergiciel terminal.

Prenons l’exemple suivant :

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

app.Use(async (context, next) =>
{
    await context.Response.WriteAsync("Hello from middleware 1. Passing to the next middleware!\r\n");

    // Call the next middleware in the pipeline
    await next.Invoke();

    await context.Response.WriteAsync("Hello from middleware 1 again!\r\n");
});

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from middleware 2!\r\n");
});

app.Run();

Dans le code précédent :

  • app.Use() définit un composant intergiciel qui :
    • Écrit « Hello from middleware 1. Passing to the next middleware! » à la réponse.
    • Passe la requête au composant intergiciel suivant dans le pipeline et attend qu’il se termine avec await next.Invoke().
    • Une fois le composant suivant dans le pipeline terminé, il écrit "Hello from middleware 1 again!".
  • Le premier app.Run() définit un composant intergiciel qui écrit «Hello from middleware 2! » dans la réponse.
  • La deuxième app.Run() démarre l’application.

Au moment de l’exécution, quand un navigateur web envoie une requête à cette application, les composants intergiciels s’exécutent dans l’ordre dans lequel ils ont été ajoutés au pipeline. L’application retourne la réponse suivante :

Hello from middleware 1. Passing to the next middleware!
Hello from middleware 2!
Hello from middleware 1 again!

Intergiciels (middleware) intégrés

ASP.NET Core fournit un ensemble de composants intergiciels intégrés que vous pouvez utiliser pour ajouter des fonctionnalités courantes à votre application. Outre les composants intergiciels explicitement ajoutés, certains intergiciels sont implicitement ajoutés pour vous par défaut. Par exemple, WebApplication.CreateBuilder() retourne un WebApplicationBuilder qui ajoute l’intergiciel de routage de page d’exceptions du développeur, ajoute conditionnellement l’intergiciel d’authentification et d’autorisation si les services associés sont configurés, et ajoute l’intergiciel de routage de point de terminaison.

Par exemple, considérez le fichier Program.cs suivant :

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error", createScopeForErrors: true);
    // 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.UseAntiforgery();

app.MapStaticAssets();
app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode();

app.Run();

Dans le code précédent :

  • app.UseExceptionHandler() ajoute un composant intergiciel qui intercepte les exceptions et retourne une page d’erreur.
  • app.UseHsts() ajoute un composant intergiciel qui définit l’en-tête Strict-Transport-Security.
  • app.UseHttpsRedirection() ajoute un composant intergiciel qui redirige les requêtes HTTP vers HTTPS.
  • app.UseAntiforgery() ajoute un composant intergiciel qui empêche les attaques de falsification de requête intersites (CSRF, Cross Site Request Forgery).
  • app.MapStaticAssets() et app.MapRazorComponents<App>() mappent les itinéraires aux points de terminaison, qui sont ensuite gérés par l’intergiciel de routage de point de terminaison. L’intergiciel de routage de point de terminaison est implicitement ajouté par WebApplicationBuilder.

Il existe de nombreux autres composants intergiciels intégrés que vous pouvez utiliser dans votre application en fonction du type d’application et de vos besoins. Consultez la documentation pour obtenir la liste complète.

Conseil

Dans ce contexte, les méthodes qui commencent par Use sont généralement destinées au mappage des intergiciels. Les méthodes qui commencent par Map sont généralement destinées au mappage des points de terminaison.

Important

L’ordre dans lequel les composants intergiciels sont ajoutés au pipeline est important. Certains composants intergiciels doivent s’exécuter avant d’autres composants intergiciels pour que ceux-ci fonctionnent correctement. Consultez la documentation de chaque composant intergiciel pour déterminer l’ordre correct.