共用方式為


將 ASP.NET 架構 HttpContext 移轉至 ASP.NET 核心

HttpContext 是 Web 應用程式的基本元件,提供對 HTTP 請求和回應資訊的存取。 從 ASP.NET Framework 遷移到 ASP.NET Core 時,HttpContext 帶來了獨特的挑戰,因為這兩個框架具有不同的 API 和方法。

為什麼 HttpContext 遷移很複雜

ASP.NET Framework 和 ASP.NET Core 具有根本不同的 HttpContext 實作:

這些差異表示您無法簡單地將 HttpContext 程式碼從 Framework 移至 Core 而不進行變更。

移轉策略概觀

在移轉期間,您有兩種主要方法來處理 HttpContext:

  1. 完全重寫 - 重寫所有 HttpContext 程式碼以使用 ASP.NET Core 的原生 HttpContext 實作
  2. System.Web 配接器 - 使用配接器將程式碼變更降到最低,同時以累加方式移轉

對於大多數應用程式來說,遷移到 ASP.NET Core 的原生 HttpContext 可提供最佳的效能和可維護性。 不過,較大的應用程式或具有大量 HttpContext 使用量的應用程式可能會受益於在累加移轉期間使用 System.Web 配接器。

選擇移轉方法

您將 HttpContext 從 ASP.NET Framework 移轉至 ASP.NET Core 有兩個主要選項。 您的選擇取決於您的移轉時間表、您是否需要同時執行兩個應用程式,以及您願意重寫多少程式碼。

快速決策指南

回答以下問題以選擇您的方法:

  1. 您是否正在進行完全重寫或增量遷移?

  2. 您是否在共用庫中廣泛使用 HttpContext?

移轉方法比較

方法 程式碼變更 績效 共用程式庫 使用時機
完全重寫 高 - 重寫所有 HttpContext 程式碼 最佳 需要更新 全面重寫、效能為關鍵的應用程式
System.Web 配接器 低 - 保留現有模式 適用於現有程式碼 累加式移轉、廣泛的 HttpContext 使用量

重要差異

HttpContext 存留期

配接器受HttpContext支援,但無法在要求的存留期結束後使用。 因此,HttpContext 在 ASP.NET Core 上執行時,無法在單一要求之外運作,而在 ASP.NET Framework 上,它有時可以運作。 ObjectDisposedException在要求結束之前使用時,將會擲回 。

建議:將所需的值儲存到 POCO 中,並保留該物件。

請求執行緒處理的注意事項

警告

ASP.NET Core 不保證要求的執行緒親和性。 如果您的程式碼需要執行緒安全存取 HttpContext,則必須確保適當的同步。

在 ASP.NET Framework 中,要求具有線程親和性, Current 且只能在該線程上使用。 ASP.NET Core 沒有這項保證,因此 Current 可以在相同的異步內容中使用,但不保證執行緒。

建議:如果讀取/寫入至 HttpContext,您必須確保以單個線程的方式執行此動作。 您可以藉由設定 ISingleThreadedRequestMetadata,強制要求永遠不要在任何異步內容上同時執行。 這會產生效能影響,只有在您無法重構使用方式以確保非並行存取時,才應該使用。 有一個實作可供使用 SingleThreadedRequestAttribute新增至控制器:

[SingleThreadedRequest]
public class SomeController : Controller
{
    ...
} 

請求資料流緩衝

根據預設,傳入要求不一定是可搜尋的,也不完全可用。 若要取得 .NET Framework 中的行為,您可以選擇預先緩衝輸入流。 這會完整讀取傳入數據流,並將它緩衝至記憶體或磁碟(視設定而定)。

建議:您可以套用實作 介面的 IPreBufferRequestStreamMetadata 端點元數據來啟用此功能。 這可作為可套用至控制器或方法的屬性 PreBufferRequestStreamAttribute

若要在所有MVC端點上啟用此功能,有一個擴充方法可用,如下所示:

app.MapDefaultControllerRoute()
    .PreBufferRequestStream();

回應串流緩衝

上的 Response 某些 API 需要緩衝輸出資料流,例如 OutputEnd()Clear()SuppressContent

建議:為了支持在傳送之前需要緩衝響應的行為,端點必須使用實作Response的端點元數據來選擇加入該功能。

若要在所有MVC端點上啟用此功能,有一個擴充方法可用,如下所示:

app.MapDefaultControllerRoute()
    .BufferResponseStream();

完全重寫為 ASP.NET Core 的 HttpContext

當您執行完整的移轉時,請選擇此方法,並可以重寫 HttpContext 相關程式碼以使用 ASP.NET Core 的原生實作。

與 ASP.NET Framework 相比,ASP.NET Core 的 HttpContext 提供了更模組化和可擴展的設計。 這種方法提供了最佳效能,但在遷移過程中需要進行更多程式碼變更。

概觀

HttpContext ASP.NET Core 中已大幅變更。 將 HTTP 模組或處理程式移轉至中間件時,您必須更新程式代碼以使用新的 HttpContext API。

在 ASP.NET Core 中間件中,Invoke 方法會採用一個類型為 HttpContext 的參數:

public async Task Invoke(HttpContext context)

HttpContext 與 ASP.NET Framework 版本不同,需要不同的方法來存取要求和響應資訊。

屬性翻譯

本節說明如何將最常用的屬性轉換為 ASP.NET System.Web.HttpContext Core 中的對等 Microsoft.AspNetCore.Http.HttpContext 屬性。

HttpContext 屬性

HttpRequest 屬性

  • HttpRequest.HttpMethodHttpRequest.Method

    string httpMethod = httpContext.Request.Method;
    
  • HttpRequest.QueryStringHttpRequest.QueryString

    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();
    
  • HttpRequest.Url / HttpRequest.RawUrl多個屬性

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

    使用 Request.Scheme、Host、PathBase、Path、QueryString

  • HttpRequest.IsSecureConnectionHttpRequest.IsHttps

    var isSecureConnection = httpContext.Request.IsHttps;
    
  • HttpRequest.UserHostAddressConnectionInfo.RemoteIpAddress

    var userHostAddress = httpContext.Connection.RemoteIpAddress?.ToString();
    
  • HttpRequest.CookiesHttpRequest.Cookies

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

    var routeValue = httpContext.GetRouteValue("key");
    
  • HttpRequest.HeadersHttpRequest.Headers

    // 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();
    
  • HttpRequest.UserAgentHttpRequest.Headers

    string userAgent = headersDictionary[HeaderNames.UserAgent].ToString();
    
  • HttpRequest.UrlReferrerHttpRequest.Headers

    string urlReferrer = headersDictionary[HeaderNames.Referer].ToString();
    
  • HttpRequest.ContentTypeHttpRequest.ContentType

    // 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;
    
  • HttpRequest.FormHttpRequest.Form

    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-form-urlencodedform-data 時才讀取表單值

  • HttpRequest.InputStreamHttpRequest.Body

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

    警告: 僅在管線結尾的處理常式中介軟體中使用。 每個請求只能讀取內文一次

HttpResponse 屬性

  • HttpResponse.Status / HttpResponse.StatusDescriptionHttpResponse.StatusCode

    // using Microsoft.AspNetCore.Http;
    httpContext.Response.StatusCode = StatusCodes.Status200OK;
    
  • HttpResponse.ContentEncoding / HttpResponse.ContentTypeHttpResponse.ContentType

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

    httpContext.Response.ContentType = "text/html";
    
  • HttpResponse.OutputHttpResponseWritingExtensions.WriteAsync

    string responseContent = GetResponseContent();
    await httpContext.Response.WriteAsync(responseContent);
    
  • HttpResponse.TransmitFile查看請求功能

    ASP.NET Core 中的請求功能中討論了提供檔案

  • HttpResponse.HeadersHttpResponse.OnStarting

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

    必須使用回呼模式來設定標頭,以便在回應開始之前完成設置。

  • HttpResponse.CookiesHttpResponse.OnStarting

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

    必須使用回呼模式在回應開始之前設定 Cookie。

  • 設定回應標頭:

    public async Task Invoke(HttpContext httpContext)
    {
        // Set callback to execute before response starts
        httpContext.Response.OnStarting(SetHeaders, state: httpContext);
        // ... rest of middleware logic
    }
    
  • 設定回應 Cookie:

public async Task Invoke(HttpContext httpContext)
{
    // Set callbacks to execute before response starts
    httpContext.Response.OnStarting(SetCookies, state: httpContext);
    httpContext.Response.OnStarting(SetHeaders, state: httpContext);
    // ... rest of middleware logic
}

System.Web 轉接器

備註

這會利用 System.Web 配接器 來簡化移轉。

當您在共用程式庫之間使用大量 HttpContext 時,或執行想要將程式碼變更降到最低的增量移轉時,請選擇此方法。

System.Web 配接器提供相容性層,可讓您在 ASP.NET Core 應用程式中使用熟悉HttpContext的 API。 在下列情況下,此方法特別有用:

  • 您有共享的函式庫使用 HttpContext
  • 您正在執行累加式移轉
  • 您想要在移轉程式期間將程式碼變更降到最低

使用 System.Web 配接器的優點

  • 最小的程式碼變更:保留您現有的 System.Web.HttpContext 使用模式
  • 共用程式庫:程式庫可以同時與 ASP.NET Framework 和 ASP.NET Core 搭配使用
  • 增量遷移:逐個遷移應用程序,而不會破壞共享依賴關係
  • 更快的遷移:減少遷移複雜應用程序所需的時間

考慮事項

  • 效能:雖然不錯,但與原生 ASP.NET Core API 相比,適配器會帶來一些開銷
  • 功能同位:並非所有 HttpContext 功能都可透過配接器取得
  • 長期策略:考慮最終遷移到原生 ASP.NET Core API 以獲得最佳效能

如需 System.Web 配接器的詳細資訊,請參閱 System.Web 配接器檔

其他資源