Schreiben von benutzerdefinierter ASP.NET Core-Middleware

Von Fiyaz Hasan, Rick Anderson und Steve Smith

Middleware ist Software, die zu einer Anwendungspipeline zusammengesetzt wird, um Anforderungen und Antworten zu verarbeiten. ASP.NET Core stellt in umfangreichem Maß integrierte Middlewarekomponenten zur Verfügung. In manchen Szenarios möchten Sie aber vielleicht selbst eine benutzerdefinierte Middleware schreiben.

In diesem Thema wird beschrieben, wie Sie auf Konventionen basierende Middleware schreiben. Informationen zu einem Ansatz, der eine starke Typisierung und eine anforderungsbasierte Aktivierung verwendet, finden Sie unter Factorybezogene Middlewareaktivierung in ASP.NET Core.

Middlewareklasse

Für gewöhnlich ist Middleware in einer Klasse gekapselt und wird mit einer Erweiterungsmethode verfügbar gemacht. Betrachten Sie die folgende Inline-Middleware, die die Kultur für die aktuelle Anforderung anhand einer Abfragezeichenfolge festlegt:

using System.Globalization;

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

app.UseHttpsRedirection();

app.Use(async (context, next) =>
{
    var cultureQuery = context.Request.Query["culture"];
    if (!string.IsNullOrWhiteSpace(cultureQuery))
    {
        var culture = new CultureInfo(cultureQuery);

        CultureInfo.CurrentCulture = culture;
        CultureInfo.CurrentUICulture = culture;
    }

    // Call the next delegate/middleware in the pipeline.
    await next(context);
});

app.Run(async (context) =>
{
    await context.Response.WriteAsync(
        $"CurrentCulture.DisplayName: {CultureInfo.CurrentCulture.DisplayName}");
});

app.Run();

Die oben hervorgehobene Inline-Middleware wird verwendet, um das Erstellen einer Middlewarekomponente durch einen Aufruf von Microsoft.AspNetCore.Builder.UseExtensions.Use zu veranschaulichen. Die vorangehende Use-Erweiterungsmethode fügt der Anforderungspipeline der Anwendung einen inline definierten Middleware-Delegaten hinzu.

Für die Use-Erweiterung sind zwei Überladungen verfügbar:

  • Eine der Überladungen verwendet einen HttpContext und einen Func<Task>. Rufen Sie Func<Task> ohne Parameter auf.
  • Die andere Überladung verwendet einen HttpContext und einen RequestDelegate. Rufen Sie den RequestDelegate auf, indem Sie den HttpContext übergeben.

Bevorzugen Sie die letztgenannte Überladung, da sie zwei interne Zuweisungen pro Anforderung einspart, die bei Verwendung der anderen Überladung benötigt werden.

Testen Sie die Middleware, indem Sie die Kultur übergeben. Fordern Sie beispielsweise https://localhost:5001/?culture=es-es an.

Lesen Sie den Artikel Globalisierung und Lokalisierung in ASP.NET Core, um mehr über die integrierte Lokalisierungsunterstützung von ASP.NET Core zu erfahren.

Im folgenden Code wird der Middlewaredelegat in eine Klasse verschoben:

using System.Globalization;

namespace Middleware.Example;

public class RequestCultureMiddleware
{
    private readonly RequestDelegate _next;

    public RequestCultureMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var cultureQuery = context.Request.Query["culture"];
        if (!string.IsNullOrWhiteSpace(cultureQuery))
        {
            var culture = new CultureInfo(cultureQuery);

            CultureInfo.CurrentCulture = culture;
            CultureInfo.CurrentUICulture = culture;
        }

        // Call the next delegate/middleware in the pipeline.
        await _next(context);
    }
}

Die Middlewareklasse muss Folgendes enthalten:

  • Einen öffentlichen Konstruktor mit einem Parameter des Typs RequestDelegate.
  • Eine öffentliche Methode mit dem Namen Invoke oder InvokeAsync. Diese Methode muss Folgendes:
    • Eine Task zurückgeben.
    • Einen ersten Parameter des Typs HttpContext akzeptieren.

Zusätzliche Parameter für den Konstruktor und Invoke/InvokeAsync werden mittels Abhängigkeitsinjektion aufgefüllt.

In der Regel wird eine Erweiterungsmethode erstellt, um die Middleware über IApplicationBuilder verfügbar zu machen:

using System.Globalization;

namespace Middleware.Example;

public class RequestCultureMiddleware
{
    private readonly RequestDelegate _next;

    public RequestCultureMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var cultureQuery = context.Request.Query["culture"];
        if (!string.IsNullOrWhiteSpace(cultureQuery))
        {
            var culture = new CultureInfo(cultureQuery);

            CultureInfo.CurrentCulture = culture;
            CultureInfo.CurrentUICulture = culture;
        }

        // Call the next delegate/middleware in the pipeline.
        await _next(context);
    }
}

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

Der folgende Code ruft die Methode von Program.cs auf:

using Middleware.Example;
using System.Globalization;

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

app.UseHttpsRedirection();

app.UseRequestCulture();

app.Run(async (context) =>
{
    await context.Response.WriteAsync(
        $"CurrentCulture.DisplayName: {CultureInfo.CurrentCulture.DisplayName}");
});

app.Run();

Middlewareabhängigkeiten

Middleware sollte das Prinzip der expliziten Abhängigkeiten befolgen, indem sie ihre Abhängigkeiten in ihrem Konstruktor verfügbar macht. Middleware wird einmal während der Anwendungslebensdauer erstellt.

Middlewarekomponenten können Ihre Abhängigkeiten über Dependency Injection (DI) mit Konstruktorparametern auflösen. UseMiddleware kann auch direkt zusätzliche Parameter annehmen.

Voranforderungsbasierte Middlewareabhängigkeiten

Middleware wird beim Start der App erstellt und ist daher für die Lebensdauer der Anwendung gültig. Bereichsbezogene Lebensdauerdienste von Middlewarekonstruktoren in den einzelnen Anforderungen werden nicht gemeinsam mit anderen Typen mit Abhängigkeitsinjektion verwendet. Um einen bereichsbezogenen Dienst sowohl in Middleware als auch in anderen Typen zu verwenden, fügen Sie diese Dienste zur Signatur der InvokeAsync-Methode hinzu. Die InvokeAsync-Methode kann zusätzliche Parameter akzeptieren, die durch DI aufgefüllt werden:

namespace Middleware.Example;

public class MyCustomMiddleware
{
    private readonly RequestDelegate _next;

    public MyCustomMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    // IMessageWriter is injected into InvokeAsync
    public async Task InvokeAsync(HttpContext httpContext, IMessageWriter svc)
    {
        svc.Write(DateTime.Now.Ticks.ToString());
        await _next(httpContext);
    }
}

public static class MyCustomMiddlewareExtensions
{
    public static IApplicationBuilder UseMyCustomMiddleware(
        this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<MyCustomMiddleware>();
    }
}

Die Lebensdauer und Registrierungsoptionen enthalten ein vollständiges Beispiel der Middleware mit Diensten mit bereichsbezogener Lebensdauer.

Der folgende Code wird verwendet, um die vorherige Middleware zu testen:

using Middleware.Example;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<IMessageWriter, LoggingMessageWriter>();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseMyCustomMiddleware();

app.MapGet("/", () => "Hello World!");

app.Run();

IMessageWriter-Schnittstelle und Implementierung:

namespace Middleware.Example;

public interface IMessageWriter
{
    void Write(string message);
}

public class LoggingMessageWriter : IMessageWriter
{

    private readonly ILogger<LoggingMessageWriter> _logger;

    public LoggingMessageWriter(ILogger<LoggingMessageWriter> logger) =>
        _logger = logger;

    public void Write(string message) =>
        _logger.LogInformation(message);
}

Zusätzliche Ressourcen

Von Rick Anderson und Steve Smith

Middleware ist Software, die zu einer Anwendungspipeline zusammengesetzt wird, um Anforderungen und Antworten zu verarbeiten. ASP.NET Core stellt in umfangreichem Maß integrierte Middlewarekomponenten zur Verfügung. In manchen Szenarios möchten Sie aber vielleicht selbst eine benutzerdefinierte Middleware schreiben.

Hinweis

In diesem Thema wird beschrieben, wie Sie auf Konventionen basierende Middleware schreiben. Informationen zu einem Ansatz, der eine starke Typisierung und eine anforderungsbasierte Aktivierung verwendet, finden Sie unter Factorybezogene Middlewareaktivierung in ASP.NET Core.

Middlewareklasse

Für gewöhnlich ist Middleware in einer Klasse gekapselt und wird mit einer Erweiterungsmethode verfügbar gemacht. Sehen Sie sich folgende Middleware an, die die Kultur der aktuellen Anforderung über eine Abfragezeichenfolge festlegt:

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) =>
        {
            var cultureQuery = context.Request.Query["culture"];
            if (!string.IsNullOrWhiteSpace(cultureQuery))
            {
                var culture = new CultureInfo(cultureQuery);

                CultureInfo.CurrentCulture = culture;
                CultureInfo.CurrentUICulture = culture;
            }

            // Call the next delegate/middleware in the pipeline
            await next();
        });

        app.Run(async (context) =>
        {
            await context.Response.WriteAsync(
                $"Hello {CultureInfo.CurrentCulture.DisplayName}");
        });

    }
}

Im vorhergehenden Beispielcode wird die Erstellung einer Middlewarekomponente veranschaulicht. Lesen Sie den Artikel Globalisierung und Lokalisierung in ASP.NET Core, um mehr über die integrierte Lokalisierungsunterstützung von ASP.NET Core zu erfahren.

Testen Sie die Middleware, indem Sie die Kultur übergeben. Fordern Sie beispielsweise https://localhost:5001/?culture=no an.

Im folgenden Code wird der Middlewaredelegat in eine Klasse verschoben:

using Microsoft.AspNetCore.Http;
using System.Globalization;
using System.Threading.Tasks;

namespace Culture
{
    public class RequestCultureMiddleware
    {
        private readonly RequestDelegate _next;

        public RequestCultureMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task InvokeAsync(HttpContext context)
        {
            var cultureQuery = context.Request.Query["culture"];
            if (!string.IsNullOrWhiteSpace(cultureQuery))
            {
                var culture = new CultureInfo(cultureQuery);

                CultureInfo.CurrentCulture = culture;
                CultureInfo.CurrentUICulture = culture;

            }

            // Call the next delegate/middleware in the pipeline
            await _next(context);
        }
    }
}

Die Middlewareklasse muss Folgendes enthalten:

  • Einen öffentlichen Konstruktor mit einem Parameter des Typs RequestDelegate.
  • Eine öffentliche Methode mit dem Namen Invoke oder InvokeAsync. Diese Methode muss Folgendes:
    • Eine Task zurückgeben.
    • Einen ersten Parameter des Typs HttpContext akzeptieren.

Zusätzliche Parameter für den Konstruktor und Invoke/InvokeAsync werden mittels Abhängigkeitsinjektion aufgefüllt.

Middlewareabhängigkeiten

Middleware sollte das Prinzip der expliziten Abhängigkeiten befolgen, indem sie ihre Abhängigkeiten in ihrem Konstruktor verfügbar macht. Middleware wird einmal während der Anwendungslebensdauer erstellt. Lesen Sie den Abschnitt Voranforderungsbasierte Middlewareabhängigkeiten, wenn Sie Dienste für Middleware innerhalb einer Anforderung gemeinsam verwenden müssen.

Middlewarekomponenten können Ihre Abhängigkeiten über Dependency Injection (DI) mit Konstruktorparametern auflösen. UseMiddleware kann auch direkt zusätzliche Parameter annehmen.

Voranforderungsbasierte Middlewareabhängigkeiten

Weil Middleware zum Zeitpunkt des Anwendungsstarts erstellt wird (und nicht voranforderungsbasiert), werden bereichsbezogene Lebensdauerdienste von Middlewarekonstruktoren in den einzelnen Anforderungen nicht gemeinsam mit anderen Typen mit Dependency Injection verwendet. Wenn Sie einen bereichsbezogenen Dienst sowohl in Ihrer Middleware als auch in anderen Typen verwenden müssen, fügen Sie diese Dienste zur Signatur der InvokeAsync-Methode hinzu. Die InvokeAsync-Methode kann zusätzliche Parameter akzeptieren, die durch DI aufgefüllt werden:

public class CustomMiddleware
{
    private readonly RequestDelegate _next;

    public CustomMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    // IMyScopedService is injected into InvokeAsync
    public async Task InvokeAsync(HttpContext httpContext, IMyScopedService svc)
    {
        svc.MyProperty = 1000;
        await _next(httpContext);
    }
}

Die Lebensdauer und Registrierungsoptionen enthalten ein vollständiges Beispiel der Middleware mit Diensten mit bereichsbezogener Lebensdauer.

Erweiterungsmethode für die Middleware

Die folgende Erweiterungsmethode stellt die Middleware über IApplicationBuilder zur Verfügung:

using Microsoft.AspNetCore.Builder;

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

Der folgende Code ruft die Methode von Startup.Configure auf:

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.UseRequestCulture();

        app.Run(async (context) =>
        {
            await context.Response.WriteAsync(
                $"Hello {CultureInfo.CurrentCulture.DisplayName}");
        });
    }
}

Zusätzliche Ressourcen