StatusCodePagesMiddleware中间件与ExceptionHandlerMiddleware中间件比较类似,它们都是在后续请求处理过程中“出错”的情况下利用一个错误处理器来完成最终的请求处理与响应的任务。它们之间的差异在于对“错误”的界定上,对于ExceptionHandlerMiddleware中间件来说,它所谓的错误就是抛出异常,但是对于StatusCodePagesMiddleware中间件来说,则将介于400~599之间的响应状态码视为错误。如下面的代码片段所示,StatusCodePagesMiddleware中间件也采用“标准”的定义方式,针对它的配置选项通过一个对应的对象以Options模式的形式提供给它。 [本文已经同步到《ASP.NET Core框架揭秘》之中]
1: public class StatusCodePagesMiddleware
2: {
3: public StatusCodePagesMiddleware(RequestDelegate next,IOptions<StatusCodePagesOptions> options);
4: public Task Invoke(HttpContext context);
5: }
除了针对错误的界定,StatusCodePagesMiddleware和ExceptionHandlerMiddleware这两个中间件对于错误处理器的表达也不相同。我们知道ExceptionHandlerMiddleware中间件使用的错误处理器实际上就是一个类型为RequestDelegate的委托对象,但是错误处理器之于StatusCodePagesMiddleware中间件来说则是一个类型为Func<StatusCodeContext,Task>的委托对象。如下面的代码片段所示,为StatusCodePagesMiddleware中间件提供配置选项的StatusCodePagesOptions对象的唯一目的就是提供这个作为错误处理器的委托对象。
class StatusCodeContext
public HttpContext HttpContext { get; }
5: public StatusCodePagesOptions Options { get; }
6:
7: public StatusCodeContext(HttpContext context,StatusCodePagesOptions options,RequestDelegate next);
8: }
一、针对响应状态码的错误处理
由于采用了针对响应状态码的错误处理策略,所以实现在StatusCodePagesMiddleware中间件中的所有错误处理操作只会发生在当前响应状态码在400~599之间的情况,如下所示的代码片段体现了这一点。从下面给出的代码片段可以看出,StatusCodePagesMiddleware中间件在决定是否执行错误处理操作时除了会查看当前响应状态码之外,还会查看响应内容以及媒体类型,如果已经包含了响应内容或者设置了媒体类型,该中间件将不会执行任何操作。
6: 7: IOptions<StatusCodePagesOptions> options)
9: _next = next;
10: _options = options.Value;
11: }
12:
13: public async Task Invoke(HttpContext context)
14: {
15: await _next(context);
16: var response = context.Response;
17: if ((response.StatusCode >= 400 && response.StatusCode <= 599) &&!response.ContentLength.HasValue && string.IsNullOrEmpty(response.ContentType))
18: {
19: await _options.HandleAsync(new StatusCodeContext(context,_options,_next));
20: }
21: }
22: }
StatusCodePagesMiddleware中间件针对错误的处理非常简单,它只需要从StatusCodePagesOptions对象中提取出作为错误处理器的这个Func<StatusCodeContext,Task>对象,然后创建一个StatusCodeContext对象作为输入参数调用这个委托对象即可。
二、阻止异常处理
如果当前响应已经被写入了内容,或者响应的媒体类型已经被预先设置,那么StatusCodePagesMiddleware中间件将不会再执行任何的错误处理操作。这种情况实际上代表由后续中间件构成的管道可能需要自行控制当前的响应,所以StatusCodePagesMiddleware中间件不应该再做任何的干预。从这个意义上来讲,StatusCodePagesMiddleware中间件仅仅是作为一种后备的错误处理机制而已。
更进一步来将,如果后续的某个中间件返回了一个状态码在400~599之间的响应,并且这个响应只有报头集合没有主体(媒体类型自然也不会设置),那么按照我们在上面给出的错误处理逻辑,StatusCodePagesMiddleware中间件还是会按照自己的策略来处理并响应请求。为了解决这种情况下,我们必须赋予后续中间件一个能够阻止StatusCodePagesMiddleware中间件进行错误处理的能力。
阻止StatusCodePagesMiddleware中间件进行错误处理的机制是借助于一个名为StatusCodePagesFeature的特性来实现的。StatusCodePagesFeature对应如下这个IStatusCodePagesFeature接口,它具有唯一的布尔类型的属性成员Enabled。默认使用的StatusCodePagesFeature类型实现了这个接口,默认情况下这个开关是开启的。
class StatusCodePagesFeature : IStatusCodePagesFeature
8: bool Enabled { get; set; } = true ;
3: …
6: StatusCodePagesFeature feature = new StatusCodePagesFeature();
8:
10: var response = context.Response;
12: feature.Enabled)
14: await _options.HandleAsync( 15: }
17: }
我们通过一个简单的实例来演示如果利用这个StatusCodePagesFeature特性来屏蔽StatusCodePagesMiddleware中间件。在下面这个应用中,我们将针对请求的处理定义在Invoke方法中,该方法会返回一个状态码为“401 Unauthorized”的响应。我们通过随机数让这个方法会在50%的情况下利用StatusCodePagesFeature特性来阻止StatusCodePagesMiddleware中间件自身对错误的处理。我们通过调用扩展方法UseStatusCodePages注册的StatusCodePagesMiddleware中间件会直接响应一个内容为“Error occurred!”的字符串。
5: new WebHostBuilder()
7: .Configure(app => app
9: .Run(Invoke))
11: .Run();
13:
15: static Task Invoke(HttpContext context)
17: context.Response.StatusCode = 401;
19: if (_random.Next() % 2 == 0)
21: context.Features.Get<IStatusCodePagesFeature>().Enabled = false;
23: return Task.CompletedTask;
24: }
25: }
对于针对该应用的请求来说,我们会得到如下两种不同的响应。没有主体内容响应是通过Invoke方法产生的,这种情况下发生在StatusCodePagesMiddleware中间件通过StatusCodePagesFeature特性被屏蔽的时候。具有主体内容的响应则来源于StatusCodePagesMiddleware中间件。
4: Content-Length: 15
7:
10: Date: Sun,18 Dec 2016 01:59:38 GMT
12: Server: Kestrel
三、注册StatusCodePagesMiddleware中间件
我们在大部分情况下都会调用ApplicationBuilder相应的扩展方法来注册StatusCodePagesMiddleware中间件。对于StatusCodePagesMiddleware中间件的注册来说,除了我们已经很熟悉的UseStatusCodePages方之外,还具有额外一些扩展方法供我们选择。
UseStatusCodePages
我们可以调用如下三个UseStatusCodePages方法重载来注册StatusCodePagesMiddleware中间件。不论我们调用那个重载,系统最终都会根据提供的StatusCodePagesOptions对象调用构造函数来创建这个中间件对象,而且这个StatusCodePagesOptions必须具有一个作为错误处理器的Func<StatusCodeContext,Task>对象。如果没有指定任何参数,StatusCodePagesOptions对象需要以Options模式的形式注册为服务。
4: {
6: }
9: {
11: }
15: return app.UseStatusCodePages(new StatusCodePagesOptions17: HandleAsync = handler19: }string contentType,1)">string bodyFormat)6: {8: context.HttpContext.Response.ContentType = contentType;10: });2: {if (locationFormat.StartsWith("~"))
8: 9: {
12: 13: });
else19: var location = 20: context.HttpContext.Response.Redirect(location);
23: }6: 7: .UseKestrel()
9: .Configure(app => app11: .UseRouter(builder=>builder.MapRoute("error/{statuscode}",HandleError))
13: .Build()15: }17: private async static Task HandleError(HttpContext context)19: var statusCode = context.GetRouteData().Values["statuscode"];
22: }
针对该应用的请求总是会得到一个状态码在400~599之间的响应, StatusCodePagesMiddleware在此情况下会向我们指定的路径(“~/error/{statuscode}”)发送一个客户端重定向。由于重定向请求的路径与注册的路由相匹配,所以作为路由处理器的HandleError方法会响应如图11所示的这个错误页面。
UseStatusCodePagesWithReExecute
除了采用客户端重定向的方式来呈现错误页面之外,我们还可以调用UseStatusCodePagesWithReExecute方法注册StatusCodePagesMiddleware中间件并让它采用服务端重定向的方式来处理错误请求。如下面的代码片段所示,当我们调用这个方法的时候不仅可以指定重定向的路径,还可以指定指定查询字符串。这里作为重定向地址的参数pathFormat依旧是一个路径模板,它可以包含一个表示响应状态的占位符(“{0}”)。
现在我们对上面演示的这个实例略作修改来演示采服务端重定向呈现出来的错误页面。如下面的代码片段所示,我们仅仅将针对UseStatusCodePagesWithRedirects方法的调用替换成针对UseStatusCodePagesWithReExecute方法的调用而已。
对于前面演示的实例,由于错误页面是通过客户端重定向的方式呈现出来的,所以浏览器地址栏显示的是重定向地址。我们在选择这个实例中采用了服务端重定向,虽然显示的页面内容并没有不同,但是地址栏上的地址是不会发生改变的
之所以被命名为UseStatusCodePagesWithReExecute,是因为通过这方法注册的StatusCodePagesMiddleware中间件进行错误处理的时候,它仅仅是提供的重定向路径和查询字符串应用到当前HttpContext,然后递交给后续管道重新执行。UseStatusCodePagesWithReExecute方法中注册StatusCodePagesMiddleware中间件的实现总体上可以由如下所示的代码片段来体现。
8: var formatedQueryString = queryFormat == null ? null : 9: context.HttpContext.Request.Path = newPath;
11: await context.Next(context.HttpContext);
13: }
interface IStatusCodeReExecuteFeature
string OriginalPathBase { get; set; }
7: class StatusCodeReExecuteFeature : IStatusCodeReExecuteFeature
9: 10: 11: string OriginalQueryString { get; set; }
4: {
10:
12: var originalQueryString = context.HttpContext.Request.QueryString;
15: {
20:
22: context.HttpContext.Request.QueryString = newQueryString;
24: {
26: }
27: finally
28: {
29: context.HttpContext.Request.QueryString = originalQueryString;
30: context.HttpContext.Request.Path = originalPath;
31: context.HttpContext.Features.Set<IStatusCodeReExecuteFeature>( 32: }
33: });
34: }
35: }
ASP.NET Core应用的错误处理[1]:三种呈现错误页面的方式
ASP.NET Core应用的错误处理[2]:DeveloperExceptionPageMiddleware中间件
ASP.NET Core应用的错误处理[3]:ExceptionHandlerMiddleware中间件
ASP.NET Core应用的错误处理[4]:StatusCodePagesMiddleware中间件
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。