我们基于 Razor Class Library 实现了自定义错误页面的公用类库(详见之前的随笔),但是在实际使用时发现如果在 middleware 中发生了异常,则不能显示自定义错误页面,而是返回默认的 500 空白页面。

public static IApplicationBuilder UseCustomErrorPages(this IApplicationBuilder app)
{
app.UseExceptionHandler("/errors/500");
app.UseStatusCodePagesWithReExecute("/errors/{0}");
return app;
}

自定义错误页面使用的是上面的配置,当发生异常时,会走路由 /errors/500 到达对应的自定义错误页面的 mvc action 。 如果是 mvc 中产生异常,能正常到达;但是当 middleware 中产生异常时,在去往自定义错误页面的途中,又途径异常 middleware ,从而让自定义错误页面也产生了异常。这就是自定义错误页面不能显示的原因。

问题的原因想明白了,接下来通过集成测试重现这个问题。

public class ErrorPageTests : IClassFixture<WebApplicationFactory<Startup>>
{
[Fact]
public async Task ErrorPageForMiddleWareException()
{
var client = _factory.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services => services.AddMvc());
builder.Configure(app =>
{
app.UseCustomErrorPages();
app.Use(next => context => throw new Exception("Failed in middleware"));
app.UseMvcWithDefaultRoute();
});
}).CreateClient(); var response = await client.GetAsync("/");
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
Assert.Contains($"请求失败:500", content);
}
}

先写好测试的好处之一是在尝试解决方法时可以快速得到反馈,虽然写测试代码会花去更多时间,但仅这一点好处就得远大于失。

有了测试代码的保驾护航,这时就可以放心大胆的动手尝试解决问题了。

既然问题的根源是“在去往自定义错误页面的途中,又途径异常 middleware”,那我们只要抄近路绕过这些 middlewares ,直接奔向 asp.net core mvc 的 middleware ,问题不就解决了吗?

那怎么绕过去呢?不用 app.UseExceptionHandler ,自己写个 middleware ?先别做这个啥事,先搞清楚 app.UseExceptionHandler 究竟干了些啥?如果能通过 app.UseExceptionHandler 解决这个问题,岂不更好?

打开 app.UseExceptionHandler 所在的 github 仓库 Microsoft.AspNetCore.Diagnostics ,找到对应的中间件实现源码 ExceptionHandlerMiddleware ,下面的删减后的源码:

public class ExceptionHandlerMiddleware
{
//...
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
//...
PathString originalPath = context.Request.Path;
if (_options.ExceptionHandlingPath.HasValue)
{
context.Request.Path = _options.ExceptionHandlingPath;
} try
{
//...
await _options.ExceptionHandler(context);
//...
}
//...
}
}
}

原来在 ExceptionHandlerMiddleware 中只是将请求路径修改为 "/errors/500" 然后调用 ExceptionHandler ,但是我们在 app.UseExceptionHandler 只设置了请求路径,并没有设置 ExceptionHandler ,那就是默认  ExceptionHandler ,默认的 ExceptionHandler 是什么?

在 ExceptionHandlerMiddleware 的构造函数中找到了答案 —— 请求管线中的下一个中间件

if (_options.ExceptionHandler == null)
{
//...
_options.ExceptionHandler = _next;
}

只要将这里的 ExceptionHandler 修改为返回自定义错误页面的 handler ,问题就能解决。

ExceptionHandler 的类型是 RequestDelegate ,现在问题变成了如何在 RequestDelegate 中执行 mvc action 并返回响应内容?

在这个地方走了一些弯路,开始想通过 IActionResultExecutor 实现,但没成功。

后来想到最简单的方法就是利用已有的 mvc 中间件,基于 app.UseMvcWithDefaultRoute() 构建出 RequestDelegate 。基于这个思路,在 ExceptionHandlerExtensions 中以 Action<IApplicationBuilder> 为参数的扩展方法中学到一招:

var subAppBuilder = app.New();
configure(subAppBuilder);
var exceptionHandlerPipeline = subAppBuilder.Build(); return app.UseExceptionHandler(new ExceptionHandlerOptions
{
ExceptionHandler = exceptionHandlerPipeline
});

原来可以如此简单地通过 IApplicationBuilder 构建出 RequestDelegate 。

通过学习的这一招完美地解决了问题!

public static IApplicationBuilder UseCustomErrorPages(this IApplicationBuilder app)
{
var options = new ExceptionHandlerOptions
{
ExceptionHandlingPath = "/errors/500",
ExceptionHandler = app.New().UseMvcWithDefaultRoute().Build()
};
app.UseExceptionHandler(options);
app.UseStatusCodePagesWithReExecute("/errors/{0}");
return app;
}

解决 ASP.NET Core 自定义错误页面对 Middleware 异常无效的问题的更多相关文章

  1. 在Asp.Net的Global.asax中Application_Error跳转到自定义错误页无效的解决办法

    在开发Asp.Net系统的时候,我们很多时候希望系统发生错误后能够跳转到一个自定义的错误页面,于是我们经常会在Global.asax中的Application_Error方法中使用Response.R ...

  2. ASP.NET MVC下自定义错误页和展示错误页的几种方式

    在网站运行中,错误是不可避免的,错误页的产生也是不可缺少的. 这几天看了博友的很多文章,自己想总结下我从中学到的和实际中配置的. 首先,需要知道产生错误页的来源,一种是我们的.NET平台抛出的,一种是 ...

  3. 关于在 ASP.NET 的 Global.asax 中 Application_Error 方法内,设置跳转到自定义错误页无效的问题

    转自:https://www.cnblogs.com/OpenCoder/p/5070645.html 在 Global.asax 中的 Application_Error 方法中,使用 Respon ...

  4. ASP.NET MVC-异常处理&自定义错误页

    一.应用场景 对于B/S应用程序,在部署到正式环境运行的过程中,很有可能出现一些在前期测试过程中没有发现的一些异常或者错误,或者说只有在特定条件满足时才会发生的一些异常,对于使用ASP.NET MVC ...

  5. 如何在ASP.NET Core自定义中间件中读取Request.Body和Response.Body的内容?

    原文:如何在ASP.NET Core自定义中间件中读取Request.Body和Response.Body的内容? 文章名称: 如何在ASP.NET Core自定义中间件读取Request.Body和 ...

  6. IIS配置ASP.NET和服务器错误页

    以下两种方法均为全站出错处理 方法一: 1.在Web.config配置文件中<system.web></system.web>中添加<customErrors mode= ...

  7. MVC自定义错误页404静态页

    昨天公司要求给所有项目添加自定义404错误页,具体的要求实现的有以下几点: 1.实现自定义错误(如各种error,404等)跳转到指定的页面 2.所指定的页面输出的http状态值必须是404或其他指定 ...

  8. asp.net core 自定义认证方式--请求头认证

    asp.net core 自定义认证方式--请求头认证 Intro 最近开始真正的实践了一些网关的东西,最近写几篇文章分享一下我的实践以及遇到的问题. 本文主要介绍网关后面的服务如何进行认证. 解决思 ...

  9. asp.net core 自定义异常处理中间件

    asp.net core 自定义异常处理中间件 Intro 在 asp.net core 中全局异常处理,有时候可能不能满足我们的需要,可能就需要自己自定义一个中间件处理了,最近遇到一个问题,有一些异 ...

随机推荐

  1. BZOJ 5093: [Lydsy1711月赛]图的价值

    第二类斯特林数模版题 需要一些组合数的小$ trick$ upd:这里更新了本题巧妙的$ O(k)$做法,虽然常数很大就是了 传送门:here 题意:求所有$ n$个节点的无重边自环图的价值和,定义一 ...

  2. 计算int数组中的最大,最小,平均值

    public static void testNumber(int[] arr) { int max = arr[0]; int min = arr[0]; int avg = 0; int sum ...

  3. Subsequence(序列自动机模板题)

    题目链接:https://nanti.jisuanke.com/t/38232 题目大意:给你一个字符串,然后再给你m个字符串,然后问你在第一个字符串中不连续的子串能不能构成输入的子串. 具体思路:构 ...

  4. Python3 字符串与hex之间的相互转换

    在字符串转换上,python2和python3是不同的,在查看一些python2的脚本时候,总是遇到字符串与hex之间之间的转换出现问题,记录一下解决方法. 1. 在Python2.7.x上,hex字 ...

  5. 【转】Python3 操作符重载方法

    Python3 操作符重载方法 本文由 Luzhuo 编写,转发请保留该信息. 原文: http://blog.csdn.net/Rozol/article/details/70769628 以下代码 ...

  6. SQL Server Change Tracking

    1 概述 “更改跟踪”和“变更数据捕获”捕获和记录用户表的DML更改(插入.更新和删除操作),为某些有特殊需求的应用程序服务. 1.1 更改跟踪 更改跟踪捕获表的数据行更改这一行为,但不会捕获更改的具 ...

  7. 【原创】大数据基础之Mesos+Marathon+Docker部署nginx

    一 安装 安装docker:https://www.cnblogs.com/barneywill/p/10343091.html安装mesos:https://www.cnblogs.com/barn ...

  8. ILMerge参考文档

    ILMerge Michael BarnettResearch in Software Engineering (RiSE)Microsoft ResearchCopyright © Microsof ...

  9. oracle查看执行最慢与查询次数最多的sql语句

    前言 在ORACLE数据库应用调优中,一个SQL的执行次数/频率也是常常需要关注的,因为某个SQL执行太频繁,要么是由于应用设计有缺陷,需要在业务逻辑上做出优化处理,要么是业务特殊性所导致.如果执行频 ...

  10. 出错:Cause: org.apache.ibatis.executor.ExecutorException: A query was run and no Result Maps were found for the Mapped Statement 'cn.mgy.mapper.UserMapper.findById'.

    详细出错代码: org.apache.ibatis.exceptions.PersistenceException: ### Error querying database. Cause: org.a ...