C# 全局异常捕获(for .net Core)

小柊 发表于 2018年12月17日 23时42分04秒

序、背景

在16年,笔者曾在博客里写了一篇《C# 全局异常捕获》的文章,里面讲了一下如何在整个项目中捕获未处理的异常,只不过当时写的时候还是以.net Framework和Asp.net为目标写的。

然而这两年里整个.net的圈子发生了非常大的变化,比如16年刚发布的还不温不火的.net Core,终于在这两年间熊熊的燃烧起来,现在去Nuget上面看,这两年内更新过的项目,基本都提供了对.net Standard的支持,而曾经的.net Framework因为各种各样的历史包袱,开始显得有些力不从心,甚至在今年.net Framework第一次将被.net Standard甩下——.net Core3.0将首先支持.net Standard 2.1,而.net Framework 4.8则还会在.net Standard 2.0上停留(可以看微软的这篇博客《Announcing.NET Standard 2.1》)

今天半夜准备睡觉的时候,收到了一位朋友的留言,希望笔者能补充一下在.net Core下,全局异常捕获的方式。

 

所以今天决定赶一下,讲讲在新的.net Core平台下,如何进行全局异常捕获。

 

一、差异点

之前的文章里,笔者一共讲了五种方法去进行全局异常捕获,下面笔者先罗列一个表格,简单标记一下之前博客中的五个方法现在还能不能在.net Core中使用,和上一篇相比,有什么差异的部分:

方法 差异
在Program.cs使用Try...Catch... 还能使用,但依旧不能捕获其他线程的异常
监听Application.ThreadException事件 不能使用,目前.net Core仍不支持System.Windows.Forms命名空间
监听AppDomain.CurrentDomain.UnhandledException事件 可以使用,但有变动的地方。
Web项目Global.asax中编写Application_Error 不能使用,Asp.net Core取消了Global.asax
Web项目使用IExceptionFilter拦截器拦截异常 可以,略有变动

 

二、在Program.cs使用Try...Catch...

这个方法和之前没有任何的区别,还是那么的挫…和鸡肋,详细的可以参考笔者之前的博客,这里就先略过了。

 

三、监听Application.ThreadException事件

截止笔者写这篇博客的时候.net Core还是没有提供System.Windows.Forms命名空间,而先前的Application类是在System.Windows.Forms命名空间下的,所以在.net Core下,是没有办法监听Application.ThreadException事件的,之前的方法在.net Core下自然也就没法用了。

不过据说.net Core 3.0会引入WinForms和WPF,就不知道那时候System.Windows.Forms命名空间和Application类会不会回来了。

 

四、监听AppDomain.CurrentDomain.UnhandledException事件

在最初的.net Core 1.x里,AppDomain命名空间因为需要运行时的支持,并且对此开销不菲,所以微软并没有提供AppDomain的支持(参见微软的这篇文档:《Port.NET Framework libraries to .NET Core》),不过很多开发者并不接受这个理由,而且AppDomain中还有不少有用的类与方法——比如之前用的UnhandledException。所以社区里关于这块的讨论一直没有停止过(比如这个在corefx下的issue:《Supportglobal unhandled exception handler》)。

 

(一)在.net Core 2.0及后续版本中

大概是听到了开发者们的呼声,微软终于在.net Standard 2.0(对应.net Core 2.0版本)中,重新加入了System.AppDomain命名空间。

不过在.net Standard 2.0中重新加入的System.AppDomain命名空间,并不是一个完整的AppDomain。其中的很多类与方法并不能正常工作,会抛出PlatformNotSupportedException异常(详见微软的这篇文档《.NET Standard FAQ》):

 

不过这些问题也不是特别的大,毕竟大伙朝思暮想的AppDomain.CurrentDomain.UnhandledException事件可以在.net Core 2.0及以后的版本上正常工作,只是和之前相比,事件的参数类型有些变化罢了。

比如下面的代码,我们在其他线程中抛出一个异常:

 

运行一下查看效果:

 

完美,基本上和之前没有任何的差别。

 

(二)在.net Core 1.x版本中

如果您的项目正在使用.net Core 1.x的版本,且没有任何想升级到.net Core 2.0及以上版本的想法,那是不是就没有办法了呢。

并不是。国外有位大神在阅读CoreCLR源码后发现,在.net Core 1.x版本中,AppDomain其实也是存在的,只不过并没有对外暴露给.net开发员们。

于是大神就写了一个程序包(System.AppDomain)用于提供.net Core 1.x版本下的AppDomain功能实现,具体的实现原理么,大神只是简单的说了句

Internally A LOT of reflection is going on, makinguse of types in System.Reflection and System.Linq.Expressions

在内部利用System.Reflection和System.Linq.Expressions这两个命名空间进行了大量的反射

大概就是利用反射和表达式树去访问.netCore 1.x中隐藏的AppDomain。

使用方法基本和上面一模一样,只是首先需要去Nuget上安装System.AppDomain:

在项目右键,单击“管理Nuget程序包”,然后搜索浏览“System.AppDomain”程序包,这个程序包的作者是Shmueli Englard,找到后安装即可。

 

使用下面的代码测试效果:

 

运行之,还是一样的效果:

 

不过这个程序包的作者在github上的项目介绍里非常严肃的说最好不要去使用它,除非你没得选。一个原因是.net Core可能会后期加入这个功能(确实后来加入了),第二个是因为它用了反射,所以运行速度并不是最快的,而且这样就不适于UWP程序了。

所以在.net Core 1.x上,如果真的没有什么非常深刻的理由,还是建议升级到.net Core2.x,毕竟.net Standard 2.0相比前一个版本(.net Standard 1.6),增加了20000多个新API呢。

 

五、Web程序全局异常捕获

在.net Core的Web开发中,首先最大的变化是踢掉了Asp.net WebForm(大快人心哈哈哈),然后相比于Asp.net MVC,还有一个非常大的变化就是Asp.net Core去除了先前的Global.asax,并引入了Program.cs。

是的!Asp.net Core有Main函数了!这一改动使得Asp.net Core可以彻底的从IIS中独立出来,可以独立的启动与运行。随着Global.asax一块离开的还有App_Start文件夹:先前Asp.net里路由的配置文件就是放在这个文件夹里的,在Asp.net Core中,使用Startup.cs替代了App_Start文件夹。

另外Asp.net Core默认自带了依赖注入框架,建议开发者们使用依赖注入的设计原则开发项目。

因为去除了Global.asax,所以Asp.net中,在Global.asax中编写Application_Error函数来拦截异常的方法是不行了。

不过我们依旧可以使用编写Filters并注册的方式去设置全局异常捕获,下面笔者将以Asp.net Core 2.1版本为例,为大家展示如何在Asp.net Core中新建自定义异常拦截器并注册:

新建一个MyExceptionHandler类,并实现接口Microsoft.AspNetCore.Mvc.Filters.IExceptionFilter:

 

拦截器这块和之前略有变化,但相差不多,主要是返回输出的地方和之前有点变化,感觉Asp.net Core的这个返回输出的方式更人性化点。

创建完拦截器后,就要找Asp.net Core注册了。之前我们是在App_Start目录下的FilterConfig.cs里注册筛选器的,不过刚刚说了,在Asp.net Core里,App_Start被替换成了Startup.cs,所以相应的,注册筛选器的位置也挪到了Startup.cs里。

在Startup.cs中找到ConfigureServices这个方法,如果这个方法中没有services.AddMvc();这样的代码,那么在方法最后面补上:

 

如果已经有了,则视情况修改代码,按上面的代码,将刚刚新建的筛选器注册到MVC中。比如笔者使用模版新建的Asp.net Core 2.1项目一开始就为笔者填上了这样的代码:

 

那就改成这样:

 

好了,启动一下项目看看吧,不过在此之前得先写个有问题的控制器:

 

OK,打开浏览器看看效果:

 

可以,完美。

六、写在最后

随着.net Core 2.0的推出,.net Core势必将打败.net Framework成为.net开发主流目标框架,希望各位.net开发者们齐心协力,让.net Core在国内能够被重视起来。

微软早已不是当年的微软,如果我们也不随之改变,拿被这股潮流轻松甩下。

小柊

2018年12月17日 23:33:20

相关文章

  2 条评论发布于 “C# 全局异常捕获(for .net Core)

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注