Migrieren von HTTP-Handlern und -Modulen zu ASP.NET Core-Middleware

In diesem Artikel erfahren Sie, wie Sie vorhandene HTTP-Module und -Handler von system.webserver für ASP.NET zu Middleware für ASP.NET Core migrieren.

Module und Handler überarbeitet

Bevor Sie wir zur ASP.NET Core-Middleware kommen, sehen wir uns eine Zusammenfassung der Funktionsweise von HTTP-Modulen und -Handlern an:

Handler für Module

Handler:

  • Sind Klassen, die IHttpHandler implementieren

  • Werden verwendet, um Anforderungen mit einem bestimmten Dateinamen oder einer bestimmten Erweiterung zu behandeln, z. B. .report

  • Werden in Web.configkonfiguriert

Module:

  • Sind Klassen, die IHttpModule implementieren

  • Werden für jede Anforderung aufgerufen

  • Können kurzgeschlossen werden (sodass eine Anforderung nicht weiter verarbeitet wird)

  • Können etwas zur HTTP-Antwort hinzufügen oder eine eigene erstellen

  • Werden in Web.configkonfiguriert

Die Reihenfolge, in der Module eingehende Anforderungen verarbeiten, wird durch Folgendes bestimmt:

  1. Eine Reihe von Ereignissen, die von ASP.NET ausgelöst werden (z. B. BeginRequest und AuthenticateRequest). Eine vollständige Liste finden Sie unter System.Web.HttpApplication. Jedes Modul kann einen Handler für ein Ereignis oder für mehrere Ereignisse erstellen.

  2. Die Reihenfolge, in der sie in Web.config konfiguriert sind (beim gleichen Ereignis).

Neben Modulen können Sie der Datei Global.asax.cs auch Handler für die Lebenszyklusereignisse hinzufügen. Diese Handler werden nach den Handlern in den konfigurierten Modulen ausgeführt.

Von Handlern und Modulen zu Middleware

Middleware ist einfacher als HTTP-Module und -Handler:

  • Module, Handler, Global.asax.cs, Web.config (mit Ausnahme der IIS-Konfiguration) und der Anwendungslebenszyklus sind nicht mehr vorhanden.

  • Die Aufgaben von Modulen und Handlern werden von Middleware übernommen.

  • Middleware wird mithilfe von Code und nicht in Web.config konfiguriert.

  • Pipelineverzweigungen ermöglichen es, Anforderungen an bestimmte Middleware zu senden, und zwar nicht nur basierend auf der URL, sondern auch basierend auf Anforderungsheadern, Abfragezeichenfolgen und so weiter.
  • Pipelineverzweigungen ermöglichen es, Anforderungen an bestimmte Middleware zu senden, und zwar nicht nur basierend auf der URL, sondern auch basierend auf Anforderungsheadern, Abfragezeichenfolgen und so weiter.

Middleware und Module sind sich sehr ähnlich:

Middleware und Module werden in unterschiedlicher Reihenfolge verarbeitet:

  • Die Reihenfolge der Middleware basiert auf der Reihenfolge, in der sie in die Anforderungspipeline eingefügt werden. Die Reihenfolge der Module basiert hauptsächlich auf System.Web.HttpApplication-Ereignissen.

  • Bei Antworten kehrt sich die Reihenfolge der Middleware im Vergleich zur Reihenfolge bei Anforderungen um, während die Reihenfolge der Module bei Anforderungen und Antworten identisch ist.

  • Weitere Informationen finden Sie unter Erstellen einer Middlewarepipeline mit IApplicationBuilder.

Autorisierungsmiddleware schließt eine Anforderung für nicht autorisierte Benutzer*innen kurz. Eine Anforderung für die Indexseite wird zugelassen und von MVC-Middleware verarbeitet. Eine Anforderung für einen Verkaufsbericht wird zugelassen und von einer benutzerdefinierten Berichtsmiddleware verarbeitet.

Wie Sie in der obigen Abbildung sehen, hat die Authentifizierungsmiddleware die Anforderung kurzgeschlossen.

Migrieren von Modulcode zu Middleware

Ein vorhandenes HTTP-Modul sieht in etwa wie folgt aus:

// ASP.NET 4 module

using System;
using System.Web;

namespace MyApp.Modules
{
    public class MyModule : IHttpModule
    {
        public void Dispose()
        {
        }

        public void Init(HttpApplication application)
        {
            application.BeginRequest += (new EventHandler(this.Application_BeginRequest));
            application.EndRequest += (new EventHandler(this.Application_EndRequest));
        }

        private void Application_BeginRequest(Object source, EventArgs e)
        {
            HttpContext context = ((HttpApplication)source).Context;

            // Do something with context near the beginning of request processing.
        }

        private void Application_EndRequest(Object source, EventArgs e)
        {
            HttpContext context = ((HttpApplication)source).Context;

            // Do something with context near the end of request processing.
        }
    }
}

Wie auf der Seite Middleware gezeigt, ist eine ASP.NET Core-Middleware eine Klasse, die eine Invoke-Methode verfügbar macht, die einen HTTP-Kontext (HttpContext) akzeptiert und eine Aufgabe (Task) zurückgibt. Ihre neue Middleware sieht wie folgt aus:

// ASP.NET Core middleware

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

namespace MyApp.Middleware
{
    public class MyMiddleware
    {
        private readonly RequestDelegate _next;

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

        public async Task Invoke(HttpContext context)
        {
            // Do something with context near the beginning of request processing.

            await _next.Invoke(context);

            // Clean up.
        }
    }

    public static class MyMiddlewareExtensions
    {
        public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<MyMiddleware>();
        }
    }
}

Die vorangehende Middlewarevorlage wurde aus dem Abschnitt zum Schreiben von Middleware übernommen.

Die Hilfsklasse MyMiddlewareExtensions erleichtert das Konfigurieren der Middleware in Ihrer Startup-Klasse. Die UseMyMiddleware-Methode fügt Ihre Middlewareklasse der Anforderungspipeline hinzu. Dienste, die für die Middleware erforderlich sind, werden in den Konstruktor der Middleware eingefügt.

Ihr Modul kann eine Anforderung beenden (z. B., wenn Benutzer*innen nicht autorisiert sind):

// ASP.NET 4 module that may terminate the request

private void Application_BeginRequest(Object source, EventArgs e)
{
    HttpContext context = ((HttpApplication)source).Context;

    // Do something with context near the beginning of request processing.

    if (TerminateRequest())
    {
        context.Response.End();
        return;
    }
}

Eine Middleware behandelt dies, indem für die nächste Middleware in der Pipeline nicht Invoke aufgerufen wird. Beachten Sie, dass die Anforderung dadurch nicht vollständig beendet wird, da vorherige Middlewares weiterhin aufgerufen werden, wenn die Antwort die Pipeline in umgekehrter Richtung durchläuft.

// ASP.NET Core middleware that may terminate the request

public async Task Invoke(HttpContext context)
{
    // Do something with context near the beginning of request processing.

    if (!TerminateRequest())
        await _next.Invoke(context);

    // Clean up.
}

Wenn Sie die Funktionen Ihres Moduls zu Ihrer neuen Middleware migrieren, stellen Sie möglicherweise fest, dass Ihr Code nicht kompiliert wird, da sich die HttpContext-Klasse in ASP.NET Core erheblich verändert hat. Später erfahren Sie, wie Sie zum neuen HTTP-Kontext von ASP.NET Core migrieren.

Migrieren der Moduleinfügung in die Anforderungspipeline

HTTP-Module werden der Anforderungspipeline in der Regel mithilfe von Web.config hinzugefügt:

<?xml version="1.0" encoding="utf-8"?>
<!--ASP.NET 4 web.config-->
<configuration>
  <system.webServer>
    <modules>
      <add name="MyModule" type="MyApp.Modules.MyModule"/>
    </modules>
  </system.webServer>
</configuration>

Konvertieren Sie dies durch Hinzufügen Ihrer neuen Middleware zur Anforderungspipeline in Ihrer Startup-Klasse:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseBrowserLink();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    app.UseMyMiddleware();

    app.UseMyMiddlewareWithParams();

    var myMiddlewareOptions = Configuration.GetSection("MyMiddlewareOptionsSection").Get<MyMiddlewareOptions>();
    var myMiddlewareOptions2 = Configuration.GetSection("MyMiddlewareOptionsSection2").Get<MyMiddlewareOptions>();
    app.UseMyMiddlewareWithParams(myMiddlewareOptions);
    app.UseMyMiddlewareWithParams(myMiddlewareOptions2);

    app.UseMyTerminatingMiddleware();

    // Create branch to the MyHandlerMiddleware. 
    // All requests ending in .report will follow this branch.
    app.MapWhen(
        context => context.Request.Path.ToString().EndsWith(".report"),
        appBranch => {
            // ... optionally add more middleware to this branch
            appBranch.UseMyHandler();
        });

    app.MapWhen(
        context => context.Request.Path.ToString().EndsWith(".context"),
        appBranch => {
            appBranch.UseHttpContextDemoMiddleware();
        });

    app.UseStaticFiles();

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}

Die genaue Position in der Pipeline, an der Sie Ihre neue Middleware einfügen, hängt von dem Ereignis ab, das von ihr als Modul behandelt wurde (BeginRequest, EndRequest usw.), sowie von der Reihenfolge in der Liste der Module in Web.config.

Wie bereits erwähnt, gibt es keinen Anwendungslebenszyklus in ASP.NET Core, und die Reihenfolge, in der Antworten von Middleware verarbeitet werden, unterscheidet sich von der Reihenfolge, die von Modulen verwendet wird. Das erschwert ggf. Ihre Entscheidung hinsichtlich der Reihenfolge.

Sollte die Reihenfolge zum Problem werden, können Sie Ihr Modul auf mehrere Middlewarekomponenten aufteilen, die unabhängig voneinander sortiert werden können.

Migrieren von Handlercode zu Middleware

Ein HTTP-Handler sieht in etwa wie folgt aus:

// ASP.NET 4 handler

using System.Web;

namespace MyApp.HttpHandlers
{
    public class MyHandler : IHttpHandler
    {
        public bool IsReusable { get { return true; } }

        public void ProcessRequest(HttpContext context)
        {
            string response = GenerateResponse(context);

            context.Response.ContentType = GetContentType();
            context.Response.Output.Write(response);
        }

        // ...

        private string GenerateResponse(HttpContext context)
        {
            string title = context.Request.QueryString["title"];
            return string.Format("Title of the report: {0}", title);
        }

        private string GetContentType()
        {
            return "text/plain";
        }
    }
}

Dies kann in Ihrem ASP.NET Core-Projekt wie folgt in eine Middleware umgewandelt werden:

// ASP.NET Core middleware migrated from a handler

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

namespace MyApp.Middleware
{
    public class MyHandlerMiddleware
    {

        // Must have constructor with this signature, otherwise exception at run time
        public MyHandlerMiddleware(RequestDelegate next)
        {
            // This is an HTTP Handler, so no need to store next
        }

        public async Task Invoke(HttpContext context)
        {
            string response = GenerateResponse(context);

            context.Response.ContentType = GetContentType();
            await context.Response.WriteAsync(response);
        }

        // ...

        private string GenerateResponse(HttpContext context)
        {
            string title = context.Request.Query["title"];
            return string.Format("Title of the report: {0}", title);
        }

        private string GetContentType()
        {
            return "text/plain";
        }
    }

    public static class MyHandlerExtensions
    {
        public static IApplicationBuilder UseMyHandler(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<MyHandlerMiddleware>();
        }
    }
}

Diese Middleware ähnelt stark der Middleware, die Modulen entspricht. Der einzige wirkliche Unterschied besteht darin, dass es hier keinen Aufruf von _next.Invoke(context) gibt. Der Grund: Der Handler befindet sich am Ende der Anforderungspipeline, sodass keine nächste Middleware aufgerufen wird.

Migrieren der Handlereinfügung in die Anforderungspipeline

Das Konfigurieren eines HTTP-Handlers erfolgt in Web.config und sieht in etwa wie folgt aus:

<?xml version="1.0" encoding="utf-8"?>
<!--ASP.NET 4 web.config-->
<configuration>
  <system.webServer>
    <handlers>
      <add name="MyHandler" verb="*" path="*.report" type="MyApp.HttpHandlers.MyHandler" resourceType="Unspecified" preCondition="integratedMode"/>
    </handlers>
  </system.webServer>
</configuration>

Sie können dies konvertieren, indem Sie Ihre neue Handlermiddleware der Anforderungspipeline in Ihrer Startup-Klasse hinzufügen, ähnlich wie bei Middleware, die aus Modulen konvertiert wird. Das Problem bei diesem Ansatz besteht darin, dass alle Anforderungen an Ihre neue Handlermiddleware gesendet werden. Sie möchten jedoch, dass nur Anforderungen mit einer bestimmten Erweiterung Ihre Middleware erreichen. Dadurch erhalten Sie die gleiche Funktionalität wie mit Ihrem HTTP-Handler.

Eine mögliche Lösung ist, die MapWhen-Erweiterungsmethode zu verwenden, um die Pipeline für Anforderungen mit einer bestimmten Erweiterung zu verzweigen. Hierzu wird die gleiche Configure-Methode verwendet, in der Sie auch die andere Middleware hinzufügen:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseBrowserLink();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    app.UseMyMiddleware();

    app.UseMyMiddlewareWithParams();

    var myMiddlewareOptions = Configuration.GetSection("MyMiddlewareOptionsSection").Get<MyMiddlewareOptions>();
    var myMiddlewareOptions2 = Configuration.GetSection("MyMiddlewareOptionsSection2").Get<MyMiddlewareOptions>();
    app.UseMyMiddlewareWithParams(myMiddlewareOptions);
    app.UseMyMiddlewareWithParams(myMiddlewareOptions2);

    app.UseMyTerminatingMiddleware();

    // Create branch to the MyHandlerMiddleware. 
    // All requests ending in .report will follow this branch.
    app.MapWhen(
        context => context.Request.Path.ToString().EndsWith(".report"),
        appBranch => {
            // ... optionally add more middleware to this branch
            appBranch.UseMyHandler();
        });

    app.MapWhen(
        context => context.Request.Path.ToString().EndsWith(".context"),
        appBranch => {
            appBranch.UseHttpContextDemoMiddleware();
        });

    app.UseStaticFiles();

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}

MapWhen akzeptiert folgende Parameter:

  1. Ein Lambda, das den HTTP-Kontext (HttpContext) akzeptiert und true zurückgibt, wenn die Anforderung die Verzweigung durchlaufen soll. Das bedeutet, dass Sie Anforderungen nicht nur basierend auf ihrer Erweiterung, sondern auch auf Basis von Anforderungsheadern, Abfragezeichenfolgenparametern und Ähnlichem verzweigen können.

  2. Ein Lambda, das ein Element vom Typ IApplicationBuilder akzeptiert und die gesamte Middleware für die Verzweigung hinzufügt. Das bedeutet, dass Sie der Verzweigung zusätzliche Middleware vor Ihrer Handlermiddleware hinzufügen können.

Middleware, die der Pipeline hinzugefügt wird, bevor die Verzweigung für alle Anforderungen aufgerufen wird. Die Verzweigung hat also keine Auswirkungen auf sie.

Laden von Middlewareoptionen mithilfe des Optionsmusters

Einige Module und Handler verfügen über Konfigurationsoptionen, die in Web.config gespeichert sind. In ASP.NET Core wird jedoch anstelle von Web.config ein neues Konfigurationsmodell verwendet.

Das neue Konfigurationssystem bietet hierfür folgende Lösungsmöglichkeiten:

  1. Erstellen Sie eine Klasse für Ihre Middlewareoptionen. Beispiel:

    public class MyMiddlewareOptions
    {
        public string Param1 { get; set; }
        public string Param2 { get; set; }
    }
    
  2. Speichern Sie die Optionswerte.

    Mit dem Konfigurationssystem können Optionswerte an einem beliebigen Ort gespeichert werden. Die meisten Websites verwenden allerdings appsettings.json, und wir folgen hier ebenfalls diesem Ansatz:

    {
      "MyMiddlewareOptionsSection": {
        "Param1": "Param1Value",
        "Param2": "Param2Value"
      }
    }
    

    MyMiddlewareOptionsSection ist hier ein Abschnittsname. Er muss nicht mit dem Namen Ihrer Optionsklasse identisch sein.

  3. Ordnen Sie die Optionswerte der Optionsklasse zu.

    Das Optionsmuster verwendet das Framework für Abhängigkeitsinjektion von ASP.NET Core, um den Optionstyp (z. B. MyMiddlewareOptions) einem MyMiddlewareOptions-Objekt zuzuordnen, das über die eigentlichen Optionen verfügt.

    Aktualisieren Sie Ihre Startup-Klasse:

    1. Wenn Sie appsettings.json verwenden, fügen Sie die Datei dem Konfigurations-Generator im Startup-Konstruktor hinzu:

      public Startup(IHostingEnvironment env)
      {
          var builder = new ConfigurationBuilder()
              .SetBasePath(env.ContentRootPath)
              .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
              .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
              .AddEnvironmentVariables();
          Configuration = builder.Build();
      }
      
    2. Konfigurieren Sie den Optionsdienst:

      public void ConfigureServices(IServiceCollection services)
      {
          // Setup options service
          services.AddOptions();
      
          // Load options from section "MyMiddlewareOptionsSection"
          services.Configure<MyMiddlewareOptions>(
              Configuration.GetSection("MyMiddlewareOptionsSection"));
      
          // Add framework services.
          services.AddMvc();
      }
      
    3. Ordnen Sie Ihre Optionen Ihrer Optionsklasse zu:

      public void ConfigureServices(IServiceCollection services)
      {
          // Setup options service
          services.AddOptions();
      
          // Load options from section "MyMiddlewareOptionsSection"
          services.Configure<MyMiddlewareOptions>(
              Configuration.GetSection("MyMiddlewareOptionsSection"));
      
          // Add framework services.
          services.AddMvc();
      }
      
  4. Fügen Sie die Optionen in Ihren Middlewarekonstruktor ein. Dies ist vergleichbar mit dem Einfügen von Optionen in einen Controller.

    public class MyMiddlewareWithParams
    {
        private readonly RequestDelegate _next;
        private readonly MyMiddlewareOptions _myMiddlewareOptions;
    
        public MyMiddlewareWithParams(RequestDelegate next,
            IOptions<MyMiddlewareOptions> optionsAccessor)
        {
            _next = next;
            _myMiddlewareOptions = optionsAccessor.Value;
        }
    
        public async Task Invoke(HttpContext context)
        {
            // Do something with context near the beginning of request processing
            // using configuration in _myMiddlewareOptions
    
            await _next.Invoke(context);
    
            // Do something with context near the end of request processing
            // using configuration in _myMiddlewareOptions
        }
    }
    

    Die Erweiterungsmethode UseMiddleware, die Ihre Middleware zu IApplicationBuilder hinzufügt, übernimmt die Abhängigkeitsinjektion.

    Dies ist nicht auf IOptions-Objekte beschränkt. Jedes andere Objekt, das ihre Middleware benötigt, kann auf diese Weise eingefügt werden.

Laden von Middlewareoptionen durch direktes Einfügen

Das Optionsmuster hat den Vorteil, dass es eine lose Kopplung zwischen Optionswerten und ihren Consumern schafft. Nachdem Sie eine Optionsklasse den tatsächlichen Optionswerten zugeordnet haben, kann jede andere Klasse über das Framework für Abhängigkeitsinjektion Zugriff auf die Optionen erhalten. Es ist nicht erforderlich, Optionswerte zu übergeben.

Das funktioniert allerdings nicht mehr, wenn Sie die gleiche Middleware zweimal mit unterschiedlichen Optionen verwenden möchten – beispielsweise eine Autorisierungsmiddleware, die in verschiedenen Verzweigungen verwendet wird und unterschiedliche Rollen zulässt. Einer einzelnen Optionsklasse können nicht zwei verschiedene Optionsobjekte zugeordnet werden.

Die Lösung besteht darin, die Optionsobjekte mit den tatsächlichen Optionswerten in Ihrer Startup-Klasse abzurufen und sie direkt an die einzelnen Instanzen Ihrer Middleware zu übergeben.

  1. Fügen Sie appsettings.json einen zweiten Schlüssel hinzu.

    Um der appsettings.json Datei einen zweiten Satz von Optionen hinzuzufügen, verwenden Sie einen neuen Schlüssel, um sie eindeutig zu identifizieren:

    {
      "MyMiddlewareOptionsSection2": {
        "Param1": "Param1Value2",
        "Param2": "Param2Value2"
      },
      "MyMiddlewareOptionsSection": {
        "Param1": "Param1Value",
        "Param2": "Param2Value"
      }
    }
    
  2. Rufen Sie Optionswerte ab, und übergeben Sie sie an die Middleware. Die Erweiterungsmethode Use... (die Ihre Middleware der Pipeline hinzufügt) ist ein logischer Ort für die Übergabe der Optionswerte:

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole(Configuration.GetSection("Logging"));
        loggerFactory.AddDebug();
    
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseBrowserLink();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
        }
    
        app.UseMyMiddleware();
    
        app.UseMyMiddlewareWithParams();
    
        var myMiddlewareOptions = Configuration.GetSection("MyMiddlewareOptionsSection").Get<MyMiddlewareOptions>();
        var myMiddlewareOptions2 = Configuration.GetSection("MyMiddlewareOptionsSection2").Get<MyMiddlewareOptions>();
        app.UseMyMiddlewareWithParams(myMiddlewareOptions);
        app.UseMyMiddlewareWithParams(myMiddlewareOptions2);
    
        app.UseMyTerminatingMiddleware();
    
        // Create branch to the MyHandlerMiddleware. 
        // All requests ending in .report will follow this branch.
        app.MapWhen(
            context => context.Request.Path.ToString().EndsWith(".report"),
            appBranch => {
                // ... optionally add more middleware to this branch
                appBranch.UseMyHandler();
            });
    
        app.MapWhen(
            context => context.Request.Path.ToString().EndsWith(".context"),
            appBranch => {
                appBranch.UseHttpContextDemoMiddleware();
            });
    
        app.UseStaticFiles();
    
        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });
    }
    
  3. Ermöglichen Sie es der Middleware, einen Optionsparameter zu akzeptieren. Stellen Sie eine Überladung der Erweiterungsmethode Use... bereit (die den Optionsparameter akzeptiert und an UseMiddleware übergibt). Wenn UseMiddleware mit Parametern aufgerufen wird, werden die Parameter bei der Instanziierung des Middlewareobjekts an Ihren Middlewarekonstruktor übergeben.

    public static class MyMiddlewareWithParamsExtensions
    {
        public static IApplicationBuilder UseMyMiddlewareWithParams(
            this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<MyMiddlewareWithParams>();
        }
    
        public static IApplicationBuilder UseMyMiddlewareWithParams(
            this IApplicationBuilder builder, MyMiddlewareOptions myMiddlewareOptions)
        {
            return builder.UseMiddleware<MyMiddlewareWithParams>(
                new OptionsWrapper<MyMiddlewareOptions>(myMiddlewareOptions));
        }
    }
    

    Beachten Sie, wie das Optionsobjekt von einem OptionsWrapper-Objekt umschlossen wird. Dadurch wird IOptions implementiert, wie vom Middlewarekonstruktor erwartet.

Migrieren zum neuen HTTP-Kontext (HttpContext)

Sie haben bereits gesehen, dass die Invoke-Methode in Ihrer Middleware einen Parameter vom Typ HttpContextakzeptiert:

public async Task Invoke(HttpContext context)

HttpContext wurde in ASP.NET Core erheblich verändert. In diesem Abschnitt wird gezeigt, wie die am häufigsten verwendeten Eigenschaften von System.Web.HttpContext in den neuen HTTP-Kontext (Microsoft.AspNetCore.Http.HttpContext) übersetzt werden.

HttpContext

HttpContext.Items entspricht Folgendem:

IDictionary<object, object> items = httpContext.Items;

Eindeutige Anforderungs-ID (kein System.Web.HttpContext-Pendant)

Stellt eine eindeutige ID für jede Anforderung bereit. Dies ist in Protokollen äußerst praktisch.

string requestId = httpContext.TraceIdentifier;

HttpContext.Request

HttpContext.Request.HttpMethod entspricht Folgendem:

string httpMethod = httpContext.Request.Method;

HttpContext.Request.QueryString entspricht Folgendem:

IQueryCollection queryParameters = httpContext.Request.Query;

// If no query parameter "key" used, values will have 0 items
// If single value used for a key (...?key=v1), values will have 1 item ("v1")
// If key has multiple values (...?key=v1&key=v2), values will have 2 items ("v1" and "v2")
IList<string> values = queryParameters["key"];

// If no query parameter "key" used, value will be ""
// If single value used for a key (...?key=v1), value will be "v1"
// If key has multiple values (...?key=v1&key=v2), value will be "v1,v2"
string value = queryParameters["key"].ToString();

HttpContext.Request.Url und HttpContext.Request.RawUrl entsprechen Folgendem:

// using Microsoft.AspNetCore.Http.Extensions;
var url = httpContext.Request.GetDisplayUrl();

HttpContext.Request.IsSecureConnection entspricht Folgendem:

var isSecureConnection = httpContext.Request.IsHttps;

HttpContext.Request.UserHostAddress entspricht Folgendem:

var userHostAddress = httpContext.Connection.RemoteIpAddress?.ToString();

HttpContext.Request.Cookies entspricht Folgendem:

IRequestCookieCollection cookies = httpContext.Request.Cookies;
string unknownCookieValue = cookies["unknownCookie"]; // will be null (no exception)
string knownCookieValue = cookies["cookie1name"];     // will be actual value

HttpContext.Request.RequestContext.RouteData entspricht Folgendem:

var routeValue = httpContext.GetRouteValue("key");

HttpContext.Request.Headers entspricht Folgendem:

// using Microsoft.AspNetCore.Http.Headers;
// using Microsoft.Net.Http.Headers;

IHeaderDictionary headersDictionary = httpContext.Request.Headers;

// GetTypedHeaders extension method provides strongly typed access to many headers
var requestHeaders = httpContext.Request.GetTypedHeaders();
CacheControlHeaderValue cacheControlHeaderValue = requestHeaders.CacheControl;

// For unknown header, unknownheaderValues has zero items and unknownheaderValue is ""
IList<string> unknownheaderValues = headersDictionary["unknownheader"];
string unknownheaderValue = headersDictionary["unknownheader"].ToString();

// For known header, knownheaderValues has 1 item and knownheaderValue is the value
IList<string> knownheaderValues = headersDictionary[HeaderNames.AcceptLanguage];
string knownheaderValue = headersDictionary[HeaderNames.AcceptLanguage].ToString();

HttpContext.Request.UserAgent entspricht Folgendem:

string userAgent = headersDictionary[HeaderNames.UserAgent].ToString();

HttpContext.Request.UrlReferrer entspricht Folgendem:

string urlReferrer = headersDictionary[HeaderNames.Referer].ToString();

HttpContext.Request.ContentType entspricht Folgendem:

// using Microsoft.Net.Http.Headers;

MediaTypeHeaderValue mediaHeaderValue = requestHeaders.ContentType;
string contentType = mediaHeaderValue?.MediaType.ToString();   // ex. application/x-www-form-urlencoded
string contentMainType = mediaHeaderValue?.Type.ToString();    // ex. application
string contentSubType = mediaHeaderValue?.SubType.ToString();  // ex. x-www-form-urlencoded

System.Text.Encoding requestEncoding = mediaHeaderValue?.Encoding;

HttpContext.Request.Form entspricht Folgendem:

if (httpContext.Request.HasFormContentType)
{
    IFormCollection form;

    form = httpContext.Request.Form; // sync
    // Or
    form = await httpContext.Request.ReadFormAsync(); // async

    string firstName = form["firstname"];
    string lastName = form["lastname"];
}

Warnung

Lesen Sie Formularwerte nur, wenn der Inhaltsuntertyp x-www-form-urlencoded oder form-data lautet.

HttpContext.Request.InputStream entspricht Folgendem:

string inputBody;
using (var reader = new System.IO.StreamReader(
    httpContext.Request.Body, System.Text.Encoding.UTF8))
{
    inputBody = reader.ReadToEnd();
}

Warnung

Verwenden Sie diesen Code nur in einer Handler-Middleware am Ende einer Pipeline.

Der unformatierte Text kann wie oben gezeigt nur einmal pro Anforderung gelesen werden. Middleware, die versucht, den Text nach dem ersten Lesevorgang zu lesen, liest einen leeren Text.

Das gilt nicht für das Lesen eines Formulars, wie weiter oben gezeigt, da hierbei ein Puffer verwendet wird.

HttpContext.Response

HttpContext.Response.Status und HttpContext.Response.StatusDescription entsprechen Folgendem:

// using Microsoft.AspNetCore.Http;
httpContext.Response.StatusCode = StatusCodes.Status200OK;

HttpContext.Response.ContentEncoding und HttpContext.Response.ContentType entsprechen Folgendem:

// using Microsoft.Net.Http.Headers;
var mediaType = new MediaTypeHeaderValue("application/json");
mediaType.Encoding = System.Text.Encoding.UTF8;
httpContext.Response.ContentType = mediaType.ToString();

HttpContext.Response.ContentType für sich allein entspricht Folgendem:

httpContext.Response.ContentType = "text/html";

HttpContext.Response.Output entspricht Folgendem:

string responseContent = GetResponseContent();
await httpContext.Response.WriteAsync(responseContent);

HttpContext.Response.TransmitFile

Das Bereitstellen einer Datei wird unter Anforderungsfeatures in ASP.NET Core erläutert.

HttpContext.Response.Headers

Das Senden von Antwortheadern wird dadurch erschwert, dass sie nicht gesendet werden, wenn Sie sie festlegen, nachdem etwas in den Antworttext geschrieben wurde.

Die Lösung besteht darin, eine Rückrufmethode festzulegen, die direkt vor Beginn des Schreibens in die Antwort aufgerufen wird. Dieser Schritt sollte am besten zu Beginn der Invoke-Methode in Ihrer Middleware ausgeführt werden. Diese Rückrufmethode legt Ihre Antwortheader fest.

Der folgende Code legt eine Rückrufmethode namens SetHeaders fest:

public async Task Invoke(HttpContext httpContext)
{
    // ...
    httpContext.Response.OnStarting(SetHeaders, state: httpContext);

Die SetHeaders-Rückrufmethode würde wie folgt aussehen:

// using Microsoft.AspNet.Http.Headers;
// using Microsoft.Net.Http.Headers;

private Task SetHeaders(object context)
{
    var httpContext = (HttpContext)context;

    // Set header with single value
    httpContext.Response.Headers["ResponseHeaderName"] = "headerValue";

    // Set header with multiple values
    string[] responseHeaderValues = new string[] { "headerValue1", "headerValue1" };
    httpContext.Response.Headers["ResponseHeaderName"] = responseHeaderValues;

    // Translating ASP.NET 4's HttpContext.Response.RedirectLocation  
    httpContext.Response.Headers[HeaderNames.Location] = "http://www.example.com";
    // Or
    httpContext.Response.Redirect("http://www.example.com");

    // GetTypedHeaders extension method provides strongly typed access to many headers
    var responseHeaders = httpContext.Response.GetTypedHeaders();

    // Translating ASP.NET 4's HttpContext.Response.CacheControl 
    responseHeaders.CacheControl = new CacheControlHeaderValue
    {
        MaxAge = new System.TimeSpan(365, 0, 0, 0)
        // Many more properties available 
    };

    // If you use .NET Framework 4.6+, Task.CompletedTask will be a bit faster
    return Task.FromResult(0);
}

HttpContext.Response.Cookies

Cookies werden in einem Antwortheader vom Typ Set-Cookie an den Browser gesendet. Daher ist zum Senden von cookies der gleiche Rückruf erforderlich wie beim Senden von Antwortheadern:

public async Task Invoke(HttpContext httpContext)
{
    // ...
    httpContext.Response.OnStarting(SetCookies, state: httpContext);
    httpContext.Response.OnStarting(SetHeaders, state: httpContext);

Die SetCookies-Rückrufmethode würde wie folgt aussehen:

private Task SetCookies(object context)
{
    var httpContext = (HttpContext)context;

    IResponseCookies responseCookies = httpContext.Response.Cookies;

    responseCookies.Append("cookie1name", "cookie1value");
    responseCookies.Append("cookie2name", "cookie2value",
        new CookieOptions { Expires = System.DateTime.Now.AddDays(5), HttpOnly = true });

    // If you use .NET Framework 4.6+, Task.CompletedTask will be a bit faster
    return Task.FromResult(0); 
}

Zusätzliche Ressourcen