将 HTTP 处理程序和模块迁移到 ASP.NET Core 中间件
本文演示如何将现有 ASP.NET HTTP 模块和处理程序从 system.webserver 迁移到 ASP.NET Core 中间件。
重新访问的模块和处理程序
在继续讨论 ASP.NET Core 中间件之前,让我们先回顾一下 HTTP 模块和处理程序如何工作:
处理程序是:
实现 IHttpHandler 的类
用于处理具有给定文件名或扩展名(如 .report)的请求
在 Web.config 中进行配置
模块是:
实现 IHttpModule 的类
对每个请求进行调用
能够短路(停止请求的进一步处理)
能够添加到 HTTP 响应,或创建自己的响应
在 Web.config 中进行配置
模块处理传入请求的顺序取决于:
由 ASP.NET 触发的序列事件,如 BeginRequest 和 AuthenticateRequest。 有关完整列表,请参见System.Web.HttpApplication。 每个模块都可以为一个或多个事件创建处理程序。
它们在 Web.config 中进行配置的顺序(对于同一事件)。
除了模块之外,还可以将生命周期事件的处理程序添加到 Global.asax.cs
文件。 这些处理程序在所配置的模块中的处理程序之后运行。
从处理程序和模块到中间件
中间件比 HTTP 模块和处理程序更简单:
模块、处理程序、
Global.asax.cs
、Web.config(IIS 配置除外)和应用程序生命周期已不存在模块和处理程序的角色由中间件接管
中间件使用代码而不是在 Web.config 中进行配置
- 管道分支使你可以不仅基于 URL,而且基于请求标头、查询字符串等向特定中间件发送请求。
- 管道分支使你可以不仅基于 URL,而且基于请求标头、查询字符串等向特定中间件发送请求。
中间件与模块非常相似:
原则上针对每个请求进行调用
能够通过不将请求传递到下一个中间件来使请求短路
能够创建自己的 HTTP 响应
中间件和模块采用不同顺序进行处理:
中间件的顺序基于它们插入请求管道中的顺序,而模块的顺序主要基于 System.Web.HttpApplication 事件。
针对响应的中间件顺序与针对请求的顺序相反,而模块的顺序对于请求和响应是相同的
在上图中,请注意身份验证中间件如何使请求短路。
将模块代码迁移到中间件
现有 HTTP 模块类似于下面这样:
// 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 中间件是一个类,它会公开采用 HttpContext
并返回 Task
的 Invoke
方法。 新中间件类似于下面这样:
// 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>();
}
}
}
上面的中间件模板取自有关编写中间件的部分。
通过 MyMiddlewareExtensions 帮助程序类可以更轻松地在 Startup
类中配置中间件。 UseMyMiddleware
方法会将中间件类添加到请求管道。 中间件所需的服务会注入中间件的构造函数中。
模块可能会终止请求,例如如果用户未获得授权:
// 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
来处理此问题。 请记住,这不会完全终止请求,因为在响应通过管道返回时,仍会调用以前的中间件。
// 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 中进行了重大更改。 稍后,你将了解如何迁移到新的 ASP.NET Core HttpContext。
将模块插入迁移到请求管道中
HTTP 模块通常使用 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
类中将新中间件添加到请求管道:
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?}");
});
}
在管道中插入新中间件的确切位置取决于它将其作为模块处理的事件(BeginRequest
、EndRequest
等),以及它在 Web.config 内模块列表中的顺序。
如前所述,在 ASP.NET Core 中没有应用程序生命周期,中间件处理响应的顺序与模块使用的顺序不同。 这可能会使排序决策更具挑战性。
如果排序成为问题,可以将模块拆分为多个可以独立排序的中间件组件。
将处理程序代码迁移到中间件
HTTP 处理程序类似于下面这样:
// 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 项目中,可将此转换为类似于下面这样的中间件:
// 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>();
}
}
}
此中间件与对应于模块的中间件非常相似。 唯一的真正区别是此处没有调用 _next.Invoke(context)
。 这十分重要,因为处理程序位于请求管道末尾,因此没有下一个中间件要进行调用。
将处理程序插入迁移到请求管道中
HTTP 处理程序的配置在 Web.config 中进行,类似于下面这样:
<?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
类中将新处理程序中间件添加到请求管道来对此进行转换,类似于从模块转换的中间件。 该方法的问题在于,它会将所有请求都发送到新处理程序中间件。 但是,你仅希望具有给定扩展名的请求到达中间件。 这会提供与 HTTP 处理程序相同的功能。
一种解决方案是使用 MapWhen
扩展方法为具有给定扩展名的请求对管道进行分支。 在添加其他中间件的相同 Configure
方法中执行此操作:
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
采用以下参数:
一个 lambda,它采用
HttpContext
,并在请求应沿着分支向下前进时返回true
。 这意味着不仅可以基于请求扩展名,还可以基于请求标头、查询字符串参数等对请求进行分支。一个 lambda,它采用
IApplicationBuilder
并为分支添加所有中间件。 这意味着可以将其他中间件添加到处理程序中间件前面的分支。
中间件在对所有请求调用分支之前添加到管道;分支对它们没有影响。
使用选项模式加载中间件选项
某些模块和处理程序具有存储在 Web.config 中的配置选项。但是在 ASP.NET Core 中,使用新配置模型取代了 Web.config。
新配置系统提供了以下选项来解决此问题:
创建用于保存中间件选项的类,例如:
public class MyMiddlewareOptions { public string Param1 { get; set; } public string Param2 { get; set; } }
存储选项值
配置系统允许将选项值存储在所需的任何位置。 但是,大多数站点使用
appsettings.json
,因此我们会采用该方法:{ "MyMiddlewareOptionsSection": { "Param1": "Param1Value", "Param2": "Param2Value" } }
此处的 MyMiddlewareOptionsSection 是节名称。 它不必与选项类的名称相同。
将选项值与选项类关联
选项模式使用 ASP.NET Core 的依赖项注入框架将选项类型(例如
MyMiddlewareOptions
)与具有实际选项的MyMiddlewareOptions
对象关联。更新
Startup
类:如果使用
appsettings.json
,请将它添加到Startup
构造函数中的配置生成器: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(); }
配置选项服务:
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(); }
将选项与选项类关联:
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(); }
将选项注入中间件构造函数。 这类似于将选项注入控制器。
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 } }
将中间件添加到
IApplicationBuilder
的 UseMiddleware 扩展方法负责依赖项注入。这并不限于
IOptions
对象。 中间件所需的任何其他对象都可以通过这种方式注入。
通过直接注入加载中间件选项
选项模式的优点在于,它可在选项值与其使用者之间创建松散耦合。 将选项类与实际选项值相关联后,任何其他类都可以通过依赖项注入框架访问选项。 无需围绕选项值进行传递。
不过如果要通过不同选项使用两次相同中间件,则这会出现问题。 例如,在允许不同角色的不同分支中使用的授权中间件。 无法将两个不同选项对象与一个选项类关联。
解决方案是在 Startup
类中获取具有实际选项值的选项对象,并将这些内容直接传递给中间件的每个实例。
将第二个键添加到
appsettings.json
若要将第二组选项添加到
appsettings.json
文件,请使用新键来唯一地标识它:{ "MyMiddlewareOptionsSection2": { "Param1": "Param1Value2", "Param2": "Param2Value2" }, "MyMiddlewareOptionsSection": { "Param1": "Param1Value", "Param2": "Param2Value" } }
检索选项值并将它们传递给中间件。
Use...
扩展方法(用于将中间件添加到管道)是要传入选项值的逻辑位置: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?}"); }); }
使中间件可以采用选项参数。 提供
Use...
扩展方法(它采用选项参数并将它传递给UseMiddleware
)的重载。 使用参数调用UseMiddleware
时,它会在实例化中间件对象时将参数传递给中间件构造函数。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
对象中包装选项对象。 这会实现中间件构造函数所需的IOptions
。
迁移到新 HttpContext
你在前面曾看到,中间件中的 Invoke
方法采用类型为 HttpContext
的参数:
public async Task Invoke(HttpContext context)
HttpContext
在 ASP.NET Core 中进行了重大更改。 此部分演示了如何将 System.Web.HttpContext 的最常用属性转换为新的 Microsoft.AspNetCore.Http.HttpContext
。
HttpContext
HttpContext.Items 转换为:
IDictionary<object, object> items = httpContext.Items;
唯一请求 ID(无 System.Web.HttpContext 对应项)
提供每个请求的唯一 id。 包含在日志中会非常有用。
string requestId = httpContext.TraceIdentifier;
HttpContext.Request
HttpContext.Request.HttpMethod 转换为:
string httpMethod = httpContext.Request.Method;
HttpContext.Request.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();
HttpContext.Request.Url 和 HttpContext.Request.RawUrl 转换为:
// using Microsoft.AspNetCore.Http.Extensions;
var url = httpContext.Request.GetDisplayUrl();
HttpContext.Request.IsSecureConnection 转换为:
var isSecureConnection = httpContext.Request.IsHttps;
HttpContext.Request.UserHostAddress 转换为:
var userHostAddress = httpContext.Connection.RemoteIpAddress?.ToString();
HttpContext.Request.Cookies 转换为:
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 转换为:
var routeValue = httpContext.GetRouteValue("key");
HttpContext.Request.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();
HttpContext.Request.UserAgent 转换为:
string userAgent = headersDictionary[HeaderNames.UserAgent].ToString();
HttpContext.Request.UrlReferrer 转换为:
string urlReferrer = headersDictionary[HeaderNames.Referer].ToString();
HttpContext.Request.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;
HttpContext.Request.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 时才读取窗体值。
HttpContext.Request.InputStream 转换为:
string inputBody;
using (var reader = new System.IO.StreamReader(
httpContext.Request.Body, System.Text.Encoding.UTF8))
{
inputBody = reader.ReadToEnd();
}
警告
仅在管道末尾的处理程序类型中间件中才使用此代码。
对于每个请求,只能读取上面所示的原始正文一次。 在首次读取之后尝试读取正文的中间件会读取空正文。
这不适用于读取前面所示的窗体,因为该操作在缓冲区中完成。
HttpContext.Response
HttpContext.Response.Status 和 HttpContext.Response.StatusDescription 转换为:
// using Microsoft.AspNetCore.Http;
httpContext.Response.StatusCode = StatusCodes.Status200OK;
HttpContext.Response.ContentEncoding 和 HttpContext.Response.ContentType 转换为:
// 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 = "text/html";
HttpContext.Response.Output 转换为:
string responseContent = GetResponseContent();
await httpContext.Response.WriteAsync(responseContent);
HttpContext.Response.TransmitFile
ASP.NET Core 中的请求功能中讨论了如何提供文件。
HttpContext.Response.Headers
发送响应头比较复杂,因为如果在将任何内容写入响应正文后设置它们,则不会进行发送。
解决方案是设置恰好在开始向响应写入之前调用的回调方法。 最好在中间件中的 Invoke
方法开头执行此操作。 由此回调方法设置响应头。
下面的代码设置名为 SetHeaders
的回调方法:
public async Task Invoke(HttpContext httpContext)
{
// ...
httpContext.Response.OnStarting(SetHeaders, state: httpContext);
SetHeaders
回调方法会类似于下面这样:
// 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
Cookie 在 Set-Cookie 响应头中传递给浏览器。 因此,发送 Cookie 需要与用于发送响应头相同的回调:
public async Task Invoke(HttpContext httpContext)
{
// ...
httpContext.Response.OnStarting(SetCookies, state: httpContext);
httpContext.Response.OnStarting(SetHeaders, state: httpContext);
SetCookies
回调方法会类似于下面这样:
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);
}