public class Program { private static Random _random = new Random(); public static void Main() { new WebHostBuilder() .UseKestrel() .ConfigureServices(svcs => svcs.AddRouting()) .Configure(app => app .UseStatusCodePagesWithReExecute("/error/{0}") .UseRouter(builder=>builder.MapRoute("error/{statuscode}", HandleError)) .Run(context=>Task.Run(()=>context.Response.StatusCode = _random.Next(400,599)))) .Build() .Run(); } private async static Task HandleError(HttpContext context) { var statusCode = context.GetRouteData().Values["statuscode"]; await context.Response.WriteAsync($"Error occurred ({statusCode})"); } }
对于前面演示的实例,由于错误页面是通过客户端重定向的方式呈现出来的,所以浏览器地址栏显示的是重定向地址。我们在选择这个实例中采用了服务端重定向,虽然显示的页面内容并没有不同,但是地址栏上的地址是不会发生改变的
之所以被命名为UseStatusCodePagesWithReExecute,是因为通过这方法注册的StatusCodePagesMiddleware中间件进行错误处理的时候,它仅仅是提供的重定向路径和查询字符串应用到当前HttpContext,然后递交给后续管道重新执行。UseStatusCodePagesWithReExecute方法中注册StatusCodePagesMiddleware中间件的实现总体上可以由如下所示的代码片段来体现。
public static class StatusCodePagesExtensions { public static IApplicationBuilder UseStatusCodePagesWithReExecute(this IApplicationBuilder app,string pathFormat,string queryFormat = null) { return app.UseStatusCodePages(async context => { var newPath = new PathString(string.Format(CultureInfo.InvariantCulture, pathFormat, context.HttpContext.Response.StatusCode)); var formatedQueryString = queryFormat == null ? null :string.Format(CultureInfo.InvariantCulture, queryFormat, context.HttpContext.Response.StatusCode); context.HttpContext.Request.Path = newPath; context.HttpContext.Request.QueryString = newQueryString; await context.Next(context.HttpContext); }); } }
与ExceptionHandlerMiddleware中间价类似,StatusCodePagesMiddleware中间件在处理请求的过程中会改变当前请求上下文的状态,具体体现在将指定的请求路径和查询字符串重新应用到当前请求上下文中。为了不影响前置中间件对请求的正常处理,StatusCodePagesMiddleware中间件在完成自身处理流程之后必须将当前请求上下文恢复到原始的状态。StatusCodePagesMiddleware中间件依旧是采用一个特性来保存原始的路径和查询字符串。这个特性对应的接口为具有如下定义的IStatusCodeReExecuteFeature,令人费解的是该接口仅仅包含两个针对路径的属性,并没有我们希望的用于携带原始查询上下文的属性,但是默认实现类型StatusCodeReExecuteFeature包含了这个属性。
public interface IStatusCodeReExecuteFeature { string OriginalPath { get; set; } string OriginalPathBase { get; set; } } public class StatusCodeReExecuteFeature : IStatusCodeReExecuteFeature { public string OriginalPath { get; set; } public string OriginalPathBase { get; set; } public string OriginalQueryString { get; set; } }
当StatusCodePagesMiddleware中间件在处理异常请求的过程中,在将指定的重定向路径和查询字符串应用到当前请求上下文上之前,它会根据原始的上下文创建一个StatusCodeReExecuteFeature特性对象并将其添加到当前HttpContext之上。当整个请求处理过程结束之后,StatusCodePagesMiddleware中间件还会负责将这个特性从当前HttpContext中移除,并恢复原始的请求路径和查询字符串。如下所示的代码片段体现了UseStatusCodePagesWithReExecute方法的真实逻辑。
public static class StatusCodePagesExtensions { public static IApplicationBuilder UseStatusCodePagesWithReExecute(this IApplicationBuilder app,string pathFormat,string queryFormat = null) { return app.UseStatusCodePages(async context => { var newPath = new PathString(string.Format(CultureInfo.InvariantCulture, pathFormat, context.HttpContext.Response.StatusCode)); var formatedQueryString = queryFormat == null ? null :string.Format(CultureInfo.InvariantCulture, queryFormat, context.HttpContext.Response.StatusCode); var newQueryString = queryFormat == null ? QueryString.Empty : new QueryString(formatedQueryString); var originalPath = context.HttpContext.Request.Path; var originalQueryString = context.HttpContext.Request.QueryString; context.HttpContext.Features.Set<IStatusCodeReExecuteFeature>(new StatusCodeReExecuteFeature() { OriginalPathBase = context.HttpContext.Request.PathBase.Value, OriginalPath = originalPath.Value, OriginalQueryString = originalQueryString.HasValue ? originalQueryString.Value : null, }); context.HttpContext.Request.Path = newPath; context.HttpContext.Request.QueryString = newQueryString; try { await context.Next(context.HttpContext); } finally { context.HttpContext.Request.QueryString = originalQueryString; context.HttpContext.Request.Path = originalPath; context.HttpContext.Features.Set<IStatusCodeReExecuteFeature>(null); } }); } }
总结