HttpContext 是 Web 应用程序的基本组件,提供对 HTTP 请求和响应信息的访问权限。 从 ASP.NET Framework 迁移到 ASP.NET Core 时,HttpContext 提出了独特的挑战,因为两个框架具有不同的 API 和方法。
为什么 HttpContext 迁移很复杂
ASP.NET Framework 和 ASP.NET Core 具有基本不同的 HttpContext 实现:
- ASP.NET Framework 使用具有内置属性和方法的System.Web.HttpContext
- ASP.NET Core 使用 Microsoft.AspNetCore.Http.HttpContext 更模块化、可扩展的设计
这些差异意味着,您无法直接将 HttpContext 代码从 Framework 移动到 Core 而无需更改。
迁移策略概述
在迁移过程中,有两种处理 HttpContext 的主要方法:
- 完全重写 - 重写所有 HttpContext 代码以使用 ASP.NET Core 的本机 HttpContext 实现
- System.Web 适配器 - 使用适配器在以增量方式迁移时最大程度地减少代码更改
对于大多数应用程序,迁移到 ASP.NET Core 的本机 HttpContext 可提供最佳性能和可维护性。 但是,较大的应用程序或具有大量 HttpContext 用法的应用程序可能受益于在增量迁移期间使用 System.Web 适配器。
选择迁移方法
有两个主要选项用于将 HttpContext 从 ASP.NET Framework 迁移到 ASP.NET Core。 选择取决于迁移时间线、是否需要同时运行这两个应用程序,以及你愿意重写的代码量。
快速决策指南
回答以下问题以选择你的方法:
是否执行完全重写或增量迁移?
- 完整重写→ 完整重写到 ASP.NET Core HttpContext
- 增量迁移→继续问题 2
在多个共享库中是否广泛使用了 HttpContext?
- 是的,许多共享代码→ System.Web 适配器
- 否,独立的 HttpContext 用法 → 完全重写为 ASP.NET Core 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 需要缓冲输出流,例如Output,End(),Clear(),和SuppressContent。
建议:为了支持要求在发送之前缓冲响应的 Response 行为,终结点必须通过实现 IBufferResponseStreamMetadata 的终结点元数据选择使用此功能。
若要在所有 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 版本,需要不同的方法来访问请求和响应信息。
属性转换
本部分介绍如何将最常用的System.Web.HttpContext属性翻译为 ASP.NET Core 中的等效Microsoft.AspNetCore.Http.HttpContext。
HttpContext 属性
HttpContext.Items → HttpContext.Items
IDictionary<object, object> items = httpContext.Items;无等效 → HttpContext.TraceIdentifier
string requestId = httpContext.TraceIdentifier;日志记录的唯一请求 ID
HttpRequest 属性
HttpRequest.HttpMethod → HttpRequest.Method
string httpMethod = httpContext.Request.Method;HttpRequest.QueryString → HttpRequest.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.IsSecureConnection → HttpRequest.IsHttps
var isSecureConnection = httpContext.Request.IsHttps;HttpRequest.UserHostAddress → ConnectionInfo.RemoteIpAddress
var userHostAddress = httpContext.Connection.RemoteIpAddress?.ToString();HttpRequest.Cookies → HttpRequest.Cookies
IRequestCookieCollection cookies = httpContext.Request.Cookies; string unknownCookieValue = cookies["unknownCookie"]; // will be null (no exception) string knownCookieValue = cookies["cookie1name"]; // will be actual valueHttpRequest.RequestContext → RoutingHttpContextExtensions.GetRouteData
var routeValue = httpContext.GetRouteValue("key");HttpRequest.Headers → HttpRequest.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.UserAgent → HttpRequest.Headers
string userAgent = headersDictionary[HeaderNames.UserAgent].ToString();HttpRequest.UrlReferrer → HttpRequest.Headers
string urlReferrer = headersDictionary[HeaderNames.Referer].ToString();HttpRequest.ContentType → HttpRequest.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.Form → HttpRequest.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-urlencoded 或 form-data 时,才读取表单数据
HttpRequest.InputStream → HttpRequest.Body
string inputBody; using (var reader = new System.IO.StreamReader( httpContext.Request.Body, System.Text.Encoding.UTF8)) { inputBody = reader.ReadToEnd(); }警告:仅在管道末尾的处理程序中间件中使用。 正文每个请求只能读取一次
HttpResponse 属性
HttpResponse.Status / HttpResponse.StatusDescription → HttpResponse.StatusCode
// using Microsoft.AspNetCore.Http; httpContext.Response.StatusCode = StatusCodes.Status200OK;HttpResponse.ContentEncoding / HttpResponse.ContentType → HttpResponse.ContentType
// using Microsoft.Net.Http.Headers; var mediaType = new MediaTypeHeaderValue("application/json"); mediaType.Encoding = System.Text.Encoding.UTF8; httpContext.Response.ContentType = mediaType.ToString();HttpResponse.ContentType → HttpResponse.ContentType
httpContext.Response.ContentType = "text/html";HttpResponse.Output → HttpResponseWritingExtensions.WriteAsync
string responseContent = GetResponseContent(); await httpContext.Response.WriteAsync(responseContent);HttpResponse.TransmitFile → 请参阅请求功能
在 ASP.NET Core 的 请求功能中讨论了文件服务
HttpResponse.Headers → HttpResponse.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.Cookies → HttpResponse.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); }必须在响应启动之前使用回调模式来设置Cookies
设置响应标头:
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 核心 API,以获得最佳性能
有关 System.Web 适配器的详细信息,请参阅 System.Web 适配器文档。