Verstehen von Middleware

Abgeschlossen

Der Zweck einer Webanwendung besteht darin, HTTP-Anforderungen zu empfangen und darauf zu reagieren. Nachdem eine Anforderung empfangen wurde, generiert der Server eine passende Antwort. In ASP.NET Core dreht sich alles um diesen Anforderungs-Antwort-Zyklus.

Wenn eine ASP.NET Core-App eine HTTP-Anforderung empfängt, durchläuft diese einige Komponenten, um die Antwort zu generieren. Diese Komponenten werden als Middleware bezeichnet. Sie können sich Middleware als eine Pipeline vorstellen, durch die die Anforderung fließt. Jede Schicht der Middleware kann Code vor und nach der nächsten Schicht in der Pipeline ausführen.

Ein Diagramm, das eine HTTP-Anforderung darstellt, da es sich um mehrere Middleware handelt.

Middleware und Delegaten

Middleware wird als Delegat implementiert, der ein HttpContext-Objekt akzeptiert und eine Task zurückgibt. Das HttpContext-Objekt stellt die aktuelle Anforderung und die Antwort dar. Der Delegat ist eine Funktion, die die Anforderung und die Antwort verarbeitet.

Stellen Sie sich beispielsweise den folgende Code vor:

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

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

app.Run();

Im obigen Code:

  • WebApplication.CreateBuilder(args) erstellt ein neues WebApplicationBuilder-Objekt.
  • builder.Build() erstellt ein neues WebApplication-Objekt.
  • Die erste app.Run()-Funktion definiert einen Delegaten, der ein HttpContext-Objekt akzeptiert und eine Task zurückgibt. Der Delegat schreibt „Hello world!“ in die Antwort.
  • Die zweite app.Run() startet die App.

Wenn die App eine HTTP-Anforderung empfängt, wird der Delegat aufgerufen. Der Delegat schreibt „Hello world!“ in die Antwort und schließt die Anforderung ab.

Verketten von Middleware

In den meisten Apps nutzen Sie mehrere Middlewarekomponenten, die in einer Sequenz ausgeführt werden. Die Reihenfolge, in der Sie Middlewarekomponenten der Pipeline hinzufügen, ist wichtig. Die Komponenten werden in der Reihenfolge ausgeführt, in der sie hinzugefügt wurden.

Abschließende und nicht abschließende Middleware

Jede Middleware kann abschließend oder nicht abschließend sein. Nicht abschließende Middleware verarbeitet die Anforderung und ruft dann die nächste Middleware in der Pipeline auf. Abschließende Middleware ist die letzte Middleware in der Pipeline und ruft keine nachfolgende Middleware auf.

Delegaten, die mit app.Use() hinzugefügt wurden, können abschließende oder nicht abschließende Middleware sein. Diese Delegaten erwarten ein HttpContext-Objekt und ein RequestDelegate-Objekt als Parameter. In der Regel enthält ein Delegat eine await next.Invoke();-Funktion. Damit wird die Steuerung an die nächste Middleware in der Pipeline übergeben. Code vor dieser Zeile wird vor der nächsten Middleware ausgeführt, und Code nach dieser Zeile wird nach der nächsten Middleware ausgeführt. Ein mit app.Use() hinzugefügter Delegat kann eine Anforderung auf zwei Arten verarbeiten, bevor die Antwort an den Client gesendet wird: einmal vor dem Generieren der Antwort durch die abschließende Middleware und dann erneut, nachdem die Antwort von der abschließenden Middleware generiert wurde.

Mit app.Run() hinzugefügte Delegaten sind immer abschließende Middleware. Sie rufen keine nachfolgende Middleware in der Pipeline auf. Sie sind die letzte Middlewarekomponente, die ausgeführt wird. Sie erwarten lediglich ein HttpContext-Objekt als Parameter. app.Run() ist ein Alias für das Hinzufügen von abschließender Middleware.

Betrachten Sie das folgende Beispiel:

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

Im obigen Code:

  • app.Use() definiert eine Middlewarekomponente wie folgt:
    • Zunächst wird „Hello from middleware 1. Passing to the next middleware!“ in die Antwort geschrieben.
    • Anschließend wird die Anforderung an die nächste Middlewarekomponente in der Pipeline übergeben und gewartet, bis diese mit await next.Invoke() abgeschlossen wurde.
    • Nachdem die nachfolgende Komponente in der Pipeline abgeschlossen wurde, wird erneut „Hello from middleware 1 again!“ geschrieben.
  • Die erste app.Run()-Funktion definiert eine Middlewarekomponente, die „Hello from middleware 2!“ in die Antwort schreibt.
  • Die zweite app.Run() startet die App.

Wenn ein Webbrowser zur Laufzeit eine Anforderung an diese App sendet, werden die Middlewarekomponenten in der Reihenfolge ausgeführt, in der sie der Pipeline hinzugefügt wurden. Die App gibt die folgende Antwort zurück:

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

Integrierte Middleware

ASP.NET Core bietet verschiedene integrierte Middlewarekomponenten, mit denen Sie Ihrer App gängige Funktionen hinzufügen können. Zusätzlich zu den explizit hinzugefügten Middlewarekomponenten wird einige Middleware standardmäßig implizit für Sie hinzugefügt. So gibt WebApplication.CreateBuilder() beispielsweise einen WebApplicationBuilder zurück, der die Middleware für das Routing von Ausnahmen von Entwicklungsseiten hinzufügt, und bedingt auch die Middleware zur Authentifizierung und Autorisierung, wenn die zugehörigen Dienste konfiguriert sind. Außerdem wird Middleware für das Routing zum Endpunkt hinzugefügt.

Betrachten Sie beispielsweise die folgende Program.cs Datei:

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

Im obigen Code:

  • app.UseExceptionHandler() fügt eine Middlewarekomponente hinzu, die Ausnahmen abfängt und eine Fehlerseite zurückgibt.
  • app.UseHsts() fügt eine Middlewarekomponente hinzu, die den Strict-Transport-Security-Header festlegt.
  • app.UseHttpsRedirection() fügt eine Middlewarekomponente hinzu, die HTTP-Anforderungen an HTTPS umleitet.
  • app.UseAntiforgery() fügt eine Middlewarekomponente hinzu, die CSRF-Angriffe (Cross-Site Request Forgery) verhindert.
  • app.MapStaticAssets() und app.MapRazorComponents<App>() ordnen Routen Endpunkten zu, die dann von der Middleware für das Routing zum Endpunkt behandelt werden. Die Middleware für das Routing zum Endpunkt wird implizit von WebApplicationBuilder hinzugefügt.

Es gibt viele weitere integrierte Middlewarekomponenten, die Sie je nach App-Typ und Ihren Anforderungen in Ihrer App verwenden können. In der Dokumentation finden Sie die vollständige Liste.

Tipp

In diesem Zusammenhang dienen Methoden, die mit Use beginnen, im Allgemeinen zum Zuordnen von Middleware. Methoden, die mit Map beginnen, dienen im Allgemeinen für die Zuordnung zu Endpunkten.

Wichtig

Die Reihenfolge, in der Middlewarekomponenten der Pipeline hinzugefügt werden, ist wichtig! Bestimmte Middlewarekomponenten müssen vor anderen ausgeführt werden, um ordnungsgemäß zu funktionieren. In der Dokumentation zu den einzelnen Middlewarekomponenten finden Sie Informationen zur richtigen Reihenfolge.