將 HTTP 處理常式和模組移轉至 ASP.NET Core 中介軟體Migrate HTTP handlers and modules to ASP.NET Core middleware

本文說明如何移轉現有的 ASP.NET HTTP 模組和處理常式 system.webserver至 ASP.NET Core中介軟體This article shows how to migrate existing ASP.NET HTTP modules and handlers from system.webserver to ASP.NET Core middleware.

模組和第二次接觸的處理常式Modules and handlers revisited

ASP.NET Core 中介軟體之前,讓我們先複習一下 HTTP 模組和處理常式的運作方式:Before proceeding to ASP.NET Core middleware, let's first recap how HTTP modules and handlers work:

模組處理常式

處理常式包括:Handlers are:

  • 類別實作IHttpHandlerClasses that implement IHttpHandler

  • 用來處理要求,以指定的檔案名稱或副檔名,例如 .reportUsed to handle requests with a given file name or extension, such as .report

  • 設定Web.configConfigured in Web.config

模組有︰Modules are:

  • 類別實作IHttpModuleClasses that implement IHttpModule

  • 叫用每個要求Invoked for every request

  • 能夠以最少運算 (停止進一步處理的要求)Able to short-circuit (stop further processing of a request)

  • 無法新增至 HTTP 回應,或自行建立Able to add to the HTTP response, or create their own

  • 設定Web.configConfigured in Web.config

模組中處理連入要求的順序取決於:The order in which modules process incoming requests is determined by:

  1. 應用程式生命週期,這是由 ASP.NET 所引發的系列事件:BeginRequestAuthenticateRequest等等。每個模組都可以建立一或多個事件的處理常式。The application life cycle, which is a series events fired by ASP.NET: BeginRequest, AuthenticateRequest, etc. Each module can create a handler for one or more events.

  2. 對於相同事件,也就是在已設定的順序Web.configFor the same event, the order in which they're configured in Web.config.

除了模組,您可以新增至生命週期事件的處理常式您Global.asax.cs檔案。In addition to modules, you can add handlers for the life cycle events to your Global.asax.cs file. 在 設定模組中的處理常式之後,執行這些處理常式。These handlers run after the handlers in the configured modules.

從處理常式和模組到中介軟體From handlers and modules to middleware

中介軟體是 HTTP 模組和處理常式比簡單的:Middleware are simpler than HTTP modules and handlers:

  • 模組、 處理常式Global.asax.csWeb.config (除了 IIS 組態) 和應用程式生命週期都不見了Modules, handlers, Global.asax.cs, Web.config (except for IIS configuration) and the application life cycle are gone

  • 模組和處理常式的角色有接手的中介軟體The roles of both modules and handlers have been taken over by middleware

  • 中介軟體會設定為使用程式碼,而非在Web.configMiddleware are configured using code rather than in Web.config

  • 管線分支可讓您將要求傳送至特定中介軟體,根據不僅同時也在要求標頭、 查詢字串等的 URL。Pipeline branching lets you send requests to specific middleware, based on not only the URL but also on request headers, query strings, etc.

中介軟體是非常類似於模組:Middleware are very similar to modules:

中介軟體和模組會處理不同的順序:Middleware and modules are processed in a different order:

中介軟體

請注意如何在上圖中,驗證中介軟體縮短,則要求。Note how in the image above, the authentication middleware short-circuited the request.

將模組程式碼移轉至中介軟體Migrating module code to middleware

現有的 HTTP 模組會看起來像這樣:An existing HTTP module will look similar to this:

// 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.
        }
    }
}

中所示中介軟體頁面上,ASP.NET Core 中介軟體是公開的類別Invoke方法採用HttpContext,並傳回TaskAs shown in the Middleware page, an ASP.NET Core middleware is a class that exposes an Invoke method taking an HttpContext and returning a Task. 您新的中介軟體會看起來像這樣:Your new middleware will look like this:

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

上一節中建立上述的中介軟體範本寫入中介軟體The preceding middleware template was taken from the section on writing middleware.

MyMiddlewareExtensions協助程式類別可讓您更輕鬆地設定您的中介軟體,在您Startup類別。The MyMiddlewareExtensions helper class makes it easier to configure your middleware in your Startup class. UseMyMiddleware方法會將您的中介軟體類別加入至要求管線。The UseMyMiddleware method adds your middleware class to the request pipeline. 中介軟體所需的服務取得插入中介軟體的建構函式中。Services required by the middleware get injected in the middleware's constructor.

您的模組可能會終止要求,例如,如果使用者未獲授權:Your module might terminate a request, for example if the user isn't authorized:

// 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;
    }
}

中介軟體會處理此不呼叫Invoke管線中的下一個中介軟體上。A middleware handles this by not calling Invoke on the next middleware in the pipeline. 記住這不會完全終止要求,因為前一個中介軟體將仍會叫用時的回應會回到管線。Keep in mind that this doesn't fully terminate the request, because previous middlewares will still be invoked when the response makes its way back through the pipeline.

// 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.
}

當您將您的模組功能移轉到新中介軟體時,您可能會發現不編譯您的程式碼,因為HttpContext類別已大幅改變 ASP.NET Core 中。When you migrate your module's functionality to your new middleware, you may find that your code doesn't compile because the HttpContext class has significantly changed in ASP.NET Core. 稍後在,您將了解如何移轉至新的 ASP.NET Core HttpContext。Later on, you'll see how to migrate to the new ASP.NET Core HttpContext.

移轉至要求管線的模組插入Migrating module insertion into the request pipeline

HTTP 模組通常會新增至要求管線,使用Web.config:HTTP modules are typically added to the request pipeline using Web.config:

<?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>

轉換所新增您新的中介軟體至要求管線中您Startup類別:Convert this by adding your new middleware to the request pipeline in your Startup class:

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?}");
    });
}

您可以在該處插入新中介軟體管線中的確切取決於它處理為模組的事件 (BeginRequestEndRequest等) 和其在清單中的模組中的順序Web.configThe exact spot in the pipeline where you insert your new middleware depends on the event that it handled as a module (BeginRequest, EndRequest, etc.) and its order in your list of modules in Web.config.

如先前所述,有是 ASP.NET Core 中的任何應用程式生命週期和模組所使用的順序由中介軟體處理回應的順序不同。As previously stated, there's no application life cycle in ASP.NET Core and the order in which responses are processed by middleware differs from the order used by modules. 這可能會使您訂購的決策更具挑戰性。This could make your ordering decision more challenging.

如果順序會變成問題,您可以將您的模組分割成多個可以獨立地排序的中介軟體元件。If ordering becomes a problem, you could split your module into multiple middleware components that can be ordered independently.

將處理常式程式碼移轉至中介軟體Migrating handler code to middleware

HTTP 處理常式看起來像這樣:An HTTP handler looks something like this:

// 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";
        }
    }
}

在 ASP.NET Core 專案中,您會將它轉譯成中介軟體如下所示:In your ASP.NET Core project, you would translate this to a middleware similar to this:

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

此中介軟體是非常類似於對應模組到中介軟體。This middleware is very similar to the middleware corresponding to modules. 唯一的真正差異在於,以下是不需要呼叫_next.Invoke(context)The only real difference is that here there's no call to _next.Invoke(context). 這很合理,因為處理常式結尾的要求管線,因此沒有下一個中介軟體來叫用。That makes sense, because the handler is at the end of the request pipeline, so there will be no next middleware to invoke.

移轉的處理常式插入至要求管線Migrating handler insertion into the request pipeline

設定 HTTP 處理常式完成Web.config ,看起來像這樣:Configuring an HTTP handler is done in Web.config and looks something like this:

<?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>

您可以將此轉換加入至要求管線中的新處理常式中介軟體程式Startup類別,類似於從模組轉換的中介軟體。You could convert this by adding your new handler middleware to the request pipeline in your Startup class, similar to middleware converted from modules. 該方法的問題是,它會將所有要求都傳送至新處理常式中介軟體。The problem with that approach is that it would send all requests to your new handler middleware. 不過,您只想具有特定副檔名的要求進入您的中介軟體。However, you only want requests with a given extension to reach your middleware. 就能得到相同的功能,您必須與您的 HTTP 處理常式。That would give you the same functionality you had with your HTTP handler.

其中一個解決方案是分支的要求指定的擴充功能中,管線使用MapWhen擴充方法。One solution is to branch the pipeline for requests with a given extension, using the MapWhen extension method. 這麼做在同一個Configure您可在其中新增其他中介軟體的方法:You do this in the same Configure method where you add the other middleware:

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 採用這些參數:MapWhen takes these parameters:

  1. 採用 lambda HttpContext ,並傳回true如果要求應該關閉分支。A lambda that takes the HttpContext and returns true if the request should go down the branch. 這表示您可以分支不只是根據其延伸模組,而是根據在要求標頭、 查詢字串參數等的要求。This means you can branch requests not just based on their extension, but also on request headers, query string parameters, etc.

  2. 採用 lambda IApplicationBuilder ,並將新分支的所有中介軟體。A lambda that takes an IApplicationBuilder and adds all the middleware for the branch. 這表示您可以加入其他中介軟體至分支之前處理常式中介軟體。This means you can add additional middleware to the branch in front of your handler middleware.

之前的分支將會叫用的所有要求; 新增至管線的中介軟體分支不會影響在其上。Middleware added to the pipeline before the branch will be invoked on all requests; the branch will have no impact on them.

正在載入使用 「 選項 」 模式的中介軟體選項Loading middleware options using the options pattern

部分模組和處理常式已儲存在組態選項Web.config。不過,在 ASP.NET Core 中新的組態模型用來代替Web.configSome modules and handlers have configuration options that are stored in Web.config. However, in ASP.NET Core a new configuration model is used in place of Web.config.

組態系統提供您一個選項來解決這個問題:The new configuration system gives you these options to solve this:

  1. 建立類別以包裝您的中介軟體選項,例如:Create a class to hold your middleware options, for example:

    public class MyMiddlewareOptions
    {
        public string Param1 { get; set; }
        public string Param2 { get; set; }
    }
    
  2. 儲存選項值Store the option values

    組態系統,可讓您儲存選項任何地方您希望的值。The configuration system allows you to store option values anywhere you want. 不過,在網站使用最appsettings.json,因此我們將該方法:However, most sites use appsettings.json, so we'll take that approach:

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

    MyMiddlewareOptionsSection以下是區段名稱。MyMiddlewareOptionsSection here is a section name. 它不一定要與您的選項類別的名稱相同。It doesn't have to be the same as the name of your options class.

  3. 選項類別相關聯的選項值Associate the option values with the options class

    「 選項 」 模式使用 ASP.NET Core 的相依性插入架構為建立關聯的選項類型 (例如MyMiddlewareOptions) 與MyMiddlewareOptions具有實際選項物件。The options pattern uses ASP.NET Core's dependency injection framework to associate the options type (such as MyMiddlewareOptions) with a MyMiddlewareOptions object that has the actual options.

    更新您Startup類別:Update your Startup class:

    1. 如果您使用appsettings.json,將它新增至組態產生器中Startup建構函式:If you're using appsettings.json, add it to the configuration builder in the Startup constructor:

      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. 設定選項的服務:Configure the options service:

      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. 您的選項類別相關聯的選項:Associate your options with your options class:

      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. 插入至中介軟體建構函式的選項。Inject the options into your middleware constructor. 這是類似於插入到控制器的選項。This is similar to injecting options into a 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
        }
    }
    

    UseMiddleware擴充方法,將它新增至中的介軟體IApplicationBuilder相依性插入會負責。The UseMiddleware extension method that adds your middleware to the IApplicationBuilder takes care of dependency injection.

    這並不限於IOptions物件。This isn't limited to IOptions objects. 如此一來,可插入中介軟體所需要的任何其他物件。Any other object that your middleware requires can be injected this way.

正在載入透過直接插入中介軟體選項Loading middleware options through direct injection

「 選項 」 模式的優點是,它會建立鬆散耦合的選項值和其取用者之間。The options pattern has the advantage that it creates loose coupling between options values and their consumers. 一旦您已建立的選項類別關聯的實際選項值,任何其他類別可以取得存取權透過相依性插入架構的選項。Once you've associated an options class with the actual options values, any other class can get access to the options through the dependency injection framework. 就不需要以傳遞選項的值。There's no need to pass around options values.

這會細分不過如果您想要使用不同選項兩次,使用相同的中介軟體。This breaks down though if you want to use the same middleware twice, with different options. 例如授權中介軟體可讓不同角色的不同分支中使用。For example an authorization middleware used in different branches allowing different roles. 您無法將兩個不同的選項物件關聯的一個選項類別。You can't associate two different options objects with the one options class.

解決方法是取得使用中的實際選項值的選項物件您Startup類別,並將它們直接加入中介軟體的每個執行個體。The solution is to get the options objects with the actual options values in your Startup class and pass those directly to each instance of your middleware.

  1. 新增第二個碼appsettings.jsonAdd a second key to appsettings.json

    若要新增第二組選項,以appsettings.json檔案,請使用新的金鑰來唯一識別它:To add a second set of options to the appsettings.json file, use a new key to uniquely identify it:

    {
      "MyMiddlewareOptionsSection2": {
        "Param1": "Param1Value2",
        "Param2": "Param2Value2"
      },
      "MyMiddlewareOptionsSection": {
        "Param1": "Param1Value",
        "Param2": "Param2Value"
      }
    }
    
  2. 擷取選項值,並將它們傳遞給中介軟體。Retrieve options values and pass them to middleware. Use... (其會新增至管線的中介軟體) 的擴充方法是傳入的選項值的邏輯位置:The Use... extension method (which adds your middleware to the pipeline) is a logical place to pass in the option values:

    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. 啟用中介軟體来採取的 options 參數。Enable middleware to take an options parameter. 提供的多載Use...擴充方法 (採用的 options 參數,並將它傳遞給UseMiddleware)。Provide an overload of the Use... extension method (that takes the options parameter and passes it to UseMiddleware). UseMiddleware稱為使用參數時,它會將參數傳遞至您的中介軟體建構函式中介軟體物件具現化時。When UseMiddleware is called with parameters, it passes the parameters to your middleware constructor when it instantiates the middleware object.

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

    請注意這會在將選項物件的包裝OptionsWrapper物件。Note how this wraps the options object in an OptionsWrapper object. 這會實作IOptions,如預期般的中介軟體建構函式。This implements IOptions, as expected by the middleware constructor.

移轉至新的 HttpContextMigrating to the new HttpContext

您稍早所見,Invoke方法中介軟體中的使用的型別參數HttpContext:You saw earlier that the Invoke method in your middleware takes a parameter of type HttpContext:

public async Task Invoke(HttpContext context)

HttpContext 已大幅改變 ASP.NET Core 中。HttpContext has significantly changed in ASP.NET Core. 本節說明如何將最常使用的屬性轉譯System.Web.HttpContextMicrosoft.AspNetCore.Http.HttpContextThis section shows how to translate the most commonly used properties of System.Web.HttpContext to the new Microsoft.AspNetCore.Http.HttpContext.

HttpContextHttpContext

HttpContext.Items會轉譯成:HttpContext.Items translates to:

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

唯一的要求識別碼 (沒有 System.Web.HttpContext 對應項目)Unique request ID (no System.Web.HttpContext counterpart)

針對每個要求,提供您的唯一識別碼。Gives you a unique id for each request. 要包含在您的記錄檔中非常有用。Very useful to include in your logs.

string requestId = httpContext.TraceIdentifier;

HttpContext.RequestHttpContext.Request

HttpContext.Request.HttpMethod會轉譯成:HttpContext.Request.HttpMethod translates to:

string httpMethod = httpContext.Request.Method;

HttpContext.Request.QueryString會轉譯成:HttpContext.Request.QueryString translates to:

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.UrlHttpContext.Request.RawUrl轉譯成:HttpContext.Request.Url and HttpContext.Request.RawUrl translate to:

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

HttpContext.Request.IsSecureConnection會轉譯成:HttpContext.Request.IsSecureConnection translates to:

var isSecureConnection = httpContext.Request.IsHttps;

HttpContext.Request.UserHostAddress會轉譯成:HttpContext.Request.UserHostAddress translates to:

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

HttpContext.Request.Cookies會轉譯成:HttpContext.Request.Cookies translates to:

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會轉譯成:HttpContext.Request.RequestContext.RouteData translates to:

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

HttpContext.Request.Headers會轉譯成:HttpContext.Request.Headers translates to:

// 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會轉譯成:HttpContext.Request.UserAgent translates to:

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

HttpContext.Request.UrlReferrer會轉譯成:HttpContext.Request.UrlReferrer translates to:

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

HttpContext.Request.ContentType會轉譯成:HttpContext.Request.ContentType translates to:

// 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會轉譯成:HttpContext.Request.Form translates to:

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"];
}

警告

讀取表單值的內容的子型別才x-www-表單-urlencoded或是表單資料Read form values only if the content sub type is x-www-form-urlencoded or form-data.

HttpContext.Request.InputStream會轉譯成:HttpContext.Request.InputStream translates to:

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

警告

此程式碼只能用在處理常式型別中介軟體,在管線結尾處。Use this code only in a handler type middleware, at the end of a pipeline.

每個要求一次如上所示,您可以閱讀原始的主體。You can read the raw body as shown above only once per request. 嘗試讀取主體,在第一次讀取之後的中介軟體會讀取空白主體。Middleware trying to read the body after the first read will read an empty body.

這不適用於讀取表單,如先前所示因為完成從緩衝區。This doesn't apply to reading a form as shown earlier, because that's done from a buffer.

HttpContext.ResponseHttpContext.Response

HttpContext.Response.StatusHttpContext.Response.StatusDescription轉譯成:HttpContext.Response.Status and HttpContext.Response.StatusDescription translate to:

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

HttpContext.Response.ContentEncodingHttpContext.Response.ContentType轉譯成:HttpContext.Response.ContentEncoding and HttpContext.Response.ContentType translate to:

// 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上自己也會轉譯成:HttpContext.Response.ContentType on its own also translates to:

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

HttpContext.Response.Output會轉譯成:HttpContext.Response.Output translates to:

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

HttpContext.Response.TransmitFileHttpContext.Response.TransmitFile

提供的檔案會討論此處Serving up a file is discussed here.

HttpContext.Response.HeadersHttpContext.Response.Headers

傳送回應標頭複雜,原因,如果您設定它們的任何項目寫入至回應主體之後,它們將不會傳送。Sending response headers is complicated by the fact that if you set them after anything has been written to the response body, they will not be sent.

解決方法是將設定寫入回應啟動之前將會直接呼叫的回呼方法。The solution is to set a callback method that will be called right before writing to the response starts. 最好的做法是在開頭Invoke中介軟體中的方法。This is best done at the start of the Invoke method in your middleware. 它是這個回呼方法,以設定您的回應標頭。It's this callback method that sets your response headers.

下列程式碼設定呼叫的回呼方法SetHeaders:The following code sets a callback method called SetHeaders:

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

SetHeaders回呼方法會看起來像這樣:The SetHeaders callback method would look like this:

// 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.CookiesHttpContext.Response.Cookies

在瀏覽器的 cookie 移動Set-cookie回應標頭。Cookies travel to the browser in a Set-Cookie response header. 傳送回應標頭,將傳送 cookie 需要如此一來,所用相同的回呼:As a result, sending cookies requires the same callback as used for sending response headers:

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

SetCookies回呼方法看起來如下所示:The SetCookies callback method would look like the following:

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

其他資源Additional resources