【五分钟的dotnet】是一个利用您的碎片化时间来学习和丰富.net知识的博文系列。它所包含了.net体系中可能会涉及到的方方面面,好比C#的小细节,AspnetCore,微服务中的.net知识等等。
5min+不是超过5分钟的意思,"+"是知识的增长。so,它是让您花费5分钟如下的时间来提高您的知识储备量。html
其实一说到AspNet Core里面的全局异常,其实你们都不会陌生。由于这玩意儿用的很是频繁,好的异常处理方案可以帮助开发者更快速的定位问题,也可以给用户更好的用户体验。前端
好比,当您访问到一个网页,忽然,它喵的报错了!您没有看错,它报错了!!!而后显示了这样的一个错误页面:程序员
请问,此刻电脑屏幕前的您会什么感觉。(真想掏出那传说中的95级史诗巨剑!)json
可是,倘若咱们稍微处理一下这个异常,好比用我们腾讯爸爸的手段,换个皮肤:app
用户立刻就会想:“哎呀,错误就错误嘛,孰能无过,程序员锅锅也挺辛苦的。”异步
因而可知!!!全局异常的捕获和处理是有多么的重要。async
那么在AspNet Core中咱们该如何捕获和处理异常呢? 可能不少同窗都知道:IExceptionFilter
。 这个过滤器应该算是AspNet里面的老牌过滤器了,从很早就延续至今,它容许我们捕获AspNet Core的控制器中的错误。不过,对于使用 IExceptionFilter
,其实我更建议您考虑它的异步版本: IAsyncExceptionFilter
。(别问为何,问就是爱的供养)。微服务
那么咱们来看看该过滤器是怎么使用的呢? 下面以 IAsyncExceptionFilter
为例,对于同步版本其实也是同样的:学习
public class MyCustomerExceptionFilter : IAsyncExceptionFilter { public Task OnExceptionAsync(ExceptionContext context) { if (context.ExceptionHandled == false) { string msg = context.Exception.Message; context.Result = new ContentResult { Content = msg, StatusCode = StatusCodes.Status200OK, ContentType = "text/html;charset=utf-8" }; } context.ExceptionHandled = true; //异常已处理了 return Task.CompletedTask; } }
上面我们新建了一个自定义的异常过滤器,代码很简单,就是报错了以后依旧让Http返回状态码为200的结果。而且将错误信息返回到客户端。网站
而后还须要在 Startup.cs
中,告诉 MVC
我们新加的这个过滤器:
services.AddControllers(options => options.Filters.Add(new MyCustomerExceptionFilter()));
而后就完了,是否是so easy? 来看看结果:
[HttpGet] public IEnumerable<WeatherForecast> Get() { throw new Exception("has error!"); }
若是不增长该过滤器,咱们将获得Http状态码为500的响应。这对于某些不致命的意外操做来讲,有点杀鸡用牛刀的感受,对于前端用户来讲也不是很友好(明明输错了一个字符,就直接被告知网站崩溃,而且出现乔殿下)。
而我们捕获了异常,进行特殊处理以后就显得很友好了。(返回200,而且告诉用户输错了某字符等)。
在上面的代码中,您会看到有一行 context.ExceptionHandled = true;
。注意!!! 这很关键,当您处理完异常以后,请记得将此属性更改成true,代表异常已经处理过了。若是不更改的话,嘿嘿🤪。会有什么结果呢? 请看下面↓
因为AspNet Core管道的层层传递的特色,我们就有机会在管道中实现全局异常捕获。 新建一个中间件来试试吧:
public class MyExceptionMiddleware { private readonly RequestDelegate _next; public MyExceptionMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext httpContext) { try { await _next(httpContext); } catch (Exception ex) { httpContext.Response.ContentType = "application/problem+json"; var title = "An error occured: " + ex.Message; var details = ex.ToString(); var problem = new ProblemDetails { Status = 200, Title = title, Detail = details }; //Serialize the problem details object to the Response as JSON (using System.Text.Json) var stream = httpContext.Response.Body; await JsonSerializer.SerializeAsync(stream, problem); } } }
而后在 Startup.cs
中,注册管道:
app.UseMiddleware<MyExceptionMiddleware>();
来看看效果:
仍是原来的味道,仍是熟悉的配方,爽歪歪!
管道的添加顺序决定了它的执行顺序,因此若是您想扩大异常捕获的范围,能够将该管道放置在 Configure
的第一行。 可是!! 您会发现,这个默认的AspNet Core项目不是已经在第一行弄了一个异常处理么? 我*&&……&。
if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); }
这行代码你们在初始化新AspNetCore项目时就会看到,也有可能您只有上半段,这和模板有关系。不过这都没有关系,它的做用就是捕获和处理异常而已。关于 UseDeveloperExceptionPage
该扩展我们就很少说了,它的意思是:对于开发模式,一旦报错就会跳转到错误堆栈页面。 而第二个 UseExceptionHandler
就颇有意思了,从它命名就能够看出,它确定是个错误拦截程序。那么它和我们自定义的异常处理管道有什么区别呢?
“不指定确定有个默认吧!” 是的,它就是默认的错误处理。因此,它其实也是一个中间件,它的真身叫作 ExceptionHandlerMiddleware
。在使用 UseExceptionHandler
方法时,咱们能够选填各类参数。好比上方的代码,填入了 "/Error"
参数,表示当产生异常的时候,将定向到对应路径,此处就定位的是: "http://localhost:5001/Error" 。固然您也能够随意指定页面,好比 漂亮的乔殿下页面。😝
还有指定 ExceptionHandlerOptions
参数的方法,该参数是ExceptionHandlerMiddleware
中间件的重要参数:
参数名 | 说明 |
---|---|
ExceptionHandlingPath | 重定向的路径,好比刚才的 ""/Error"" 实际上就是指定的该参数 |
ExceptionHandler | 错误拦截处理程序 |
ExceptionHandler
容许咱们在 ExceptionHandlerMiddleware
内部指定我们自有的异常处理逻辑。而该参数的类型为 RequestDelegate
,是否很眼熟,没错,管道处理!所以UseExceptionHandler
提供了一个简便的写法,可让咱们在ExceptionHandlerMiddleware
中又新建自定义的错误拦截管道来做为处理程序:
//in Configure() app.UseExceptionHandler(appbuilder => appbuilder.Use(ExceptionHandlerDemo)); //该内容会在AspNetCore的管道返回结果至ExceptionHandlerMiddleware时,若是中间件捕获到了异常时调用 private async Task ExceptionHandlerDemo(HttpContext httpContext, Func<Task> next) { //该信息由ExceptionHandlerMiddleware中间件提供,里面包含了ExceptionHandlerMiddleware中间件捕获到的异常信息。 var exceptionDetails = httpContext.Features.Get<IExceptionHandlerFeature>(); var ex = exceptionDetails?.Error; if (ex != null) { httpContext.Response.ContentType = "application/problem+json"; var title = "An error occured: " + ex.Message; var details = ex.ToString(); var problem = new ProblemDetails { Status = 500, Title = title, Detail = details }; var stream = httpContext.Response.Body; await JsonSerializer.SerializeAsync(stream, problem); } }
那么上面两个方法有什么区别呢? 回答:拦截范围。
IExceptionFilter 做为MVC中间件之间的内容,它须要MVC在发现错误以后将错误信息提交给它处理,所以它的错误处理范围仅限于MVC中间件。因此,假如咱们须要捕获MVC中间件以前的一些错误,实际上是捕获不到的。 而对于ExceptionHandlerMiddleware
中间件来讲就很简单了,它做为第一个中间件,凡是在它以后的全部错误它都可以捕得到到。
那么这么看来是否IExceptionFilter
就毫无用武之地了呢? 非也,假如您想在MVC发生异常时快速捕获和处理,使用过滤器实际上是您不错得选择,若是您仅仅关心控制器之间的异常,那么过滤器也是很好的选择。
还记得刚开始咱们在过滤器中说过的这一行代码吗:context.ExceptionHandled = true;
。若是在IExceptionFilter
中将异常标记为已经处理以后,则第一道异常处理中间件就认为没有错误了,不会进入处处理逻辑中。因此,若是我们不把该属性改成 true
,颇有可能出现拦截结果被覆盖的状况。
最后,偷偷说一句:创做不易,点个推荐吧.....