StatusCodePagesMiddleware中间件与ExceptionHandlerMiddleware中间件比较类似,它们都是在后续请求处理过程中“出错”的情况下利用一个错误处理器来完成最终的请求处理与响应的任务。它们之间的差异在于对“错误”的界定上,对于ExceptionHandlerMiddleware中间件来说,它所谓的错误就是抛出异常,但是对于StatusCodePagesMiddleware中间件来说,则将介于400~599之间的响应状态码视为错误。如下面的代码片段所示,StatusCodePagesMiddleware中间件也采用“标准”的定义方式,针对它的配置选项通过一个对应的对象以Options模式的形式提供给它。
public class StatusCodePagesMiddleware { public StatusCodePagesMiddleware(RequestDelegate next, IOptions<StatusCodePagesOptions> options); public Task Invoke(HttpContext context); }
除了针对错误的界定,StatusCodePagesMiddleware和ExceptionHandlerMiddleware这两个中间件对于错误处理器的表达也不相同。我们知道ExceptionHandlerMiddleware中间件使用的错误处理器实际上就是一个类型为RequestDelegate的委托对象,但是错误处理器之于StatusCodePagesMiddleware中间件来说则是一个类型为Func<StatusCodeContext, Task>的委托对象。如下面的代码片段所示,为StatusCodePagesMiddleware中间件提供配置选项的StatusCodePagesOptions对象的唯一目的就是提供这个作为错误处理器的委托对象。
public class StatusCodePagesOptions { public Func<StatusCodeContext, Task> HandleAsync { get; set; } }
我们知道一个RequestDelegate对象相当于一个类型为Func<HttpContext, Task>类型的委托对象,而一个StatusCodeContext对象实际上也是对一个HttpContext对象的封装,所以StatusCodePagesMiddleware中间件和ExceptionHandlerMiddleware中间件所使采用的错误处理器并没有本质上的不同。如下面的代码片段所示,除了从StatusCodeContext对象中获取代表当前请求上下文的HttpContext对象之外,我们还可以通过其Next属性得到一个RequestDelegate对象,它代表由后续中间件组成的请求处理管道。至于另一个属性Options,很明显它返回我们在创建StatusCodePagesMiddleware中间件所指定的StatusCodePagesOptions对象。
public class StatusCodeContext { public HttpContext HttpContext { get; } public RequestDelegate Next { get; } public StatusCodePagesOptions Options { get; } public StatusCodeContext(HttpContext context, StatusCodePagesOptions options, RequestDelegate next); }
一、针对响应状态码的错误处理
由于采用了针对响应状态码的错误处理策略,所以实现在StatusCodePagesMiddleware中间件中的所有错误处理操作只会发生在当前响应状态码在400~599之间的情况,如下所示的代码片段体现了这一点。从下面给出的代码片段可以看出,StatusCodePagesMiddleware中间件在决定是否执行错误处理操作时除了会查看当前响应状态码之外,还会查看响应内容以及媒体类型,如果已经包含了响应内容或者设置了媒体类型,该中间件将不会执行任何操作。
public class StatusCodePagesMiddleware { private RequestDelegate _next; private StatusCodePagesOptions _options; public StatusCodePagesMiddleware(RequestDelegate next, IOptions<StatusCodePagesOptions> options) { _next = next; _options = options.Value; } public async Task Invoke(HttpContext context) { await _next(context); var response = context.Response; if ((response.StatusCode >= 400 && response.StatusCode <= 599) &&!response.ContentLength.HasValue && string.IsNullOrEmpty(response.ContentType)) { await _options.HandleAsync(new StatusCodeContext(context, _options, _next)); } } }
StatusCodePagesMiddleware中间件针对错误的处理非常简单,它只需要从StatusCodePagesOptions对象中提取出作为错误处理器的这个Func<StatusCodeContext, Task>对象,然后创建一个StatusCodeContext对象作为输入参数调用这个委托对象即可。
二、阻止异常处理
如果当前响应已经被写入了内容,或者响应的媒体类型已经被预先设置,那么StatusCodePagesMiddleware中间件将不会再执行任何的错误处理操作。这种情况实际上代表由后续中间件构成的管道可能需要自行控制当前的响应,所以StatusCodePagesMiddleware中间件不应该再做任何的干预。从这个意义上来讲,StatusCodePagesMiddleware中间件仅仅是作为一种后备的错误处理机制而已。
更进一步来将,如果后续的某个中间件返回了一个状态码在400~599之间的响应,并且这个响应只有报头集合没有主体(媒体类型自然也不会设置),那么按照我们在上面给出的错误处理逻辑,StatusCodePagesMiddleware中间件还是会按照自己的策略来处理并响应请求。为了解决这种情况下,我们必须赋予后续中间件一个能够阻止StatusCodePagesMiddleware中间件进行错误处理的能力。