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

栏目: ASP.NET · 发布时间: 5年前

内容简介:在16年,笔者曾在博客里写了一篇《C# 全局异常捕获》的文章,里面讲了一下如何在整个项目中捕获未处理的异常,只不过当时写的时候还是以.net Framework和Asp.net为目标写的。然而这两年里整个.net的圈子发生了非常大的变化,比如16年刚发布的还不温不火的.net Core,终于在这两年间熊熊的燃烧起来,现在去Nuget上面看,这两年内更新过的项目,基本都提供了对.net Standard的支持,而曾经的.net Framework因为各种各样的历史包袱,开始显得有些力不从心,甚至在今年.ne

序、背景

在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下,全局异常捕获的方式。

C# 全局异常捕获(for .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 》):

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

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

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

using System;
using System.Threading;
 
class Program
{
    static void Main(string[] args)
    {
        AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
 
        Thread thread = new Thread(() => throw new Exception());
        thread.Start();
 
        Console.ReadKey();
    }
 
    private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        //下面的代码使用了C# 7中新增的模式匹配语法
        if (e.ExceptionObject is Exception ex)
        {
            Console.WriteLine($"捕获到未处理异常:{ex.GetType()}");
            Console.WriteLine($"异常信息:{ex.Message}");
            Console.WriteLine($"异常堆栈:{ex.StackTrace}");
            Console.WriteLine($"CLR即将退出:{e.IsTerminating}");
        }
 
        Console.ReadKey();
    }
}

运行一下查看效果:

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

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

(二)在.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,找到后安装即可。

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

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

using System;
using System.Text;
using System.Threading;
 
class Program
{
    static void Main(string[] args)
    {
        Console.OutputEncoding = Encoding.Unicode;  //.net Core 1.x 防乱码
        AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
 
        Thread thread = new Thread(() => throw new Exception());
        thread.Start();
 
        Console.ReadKey();
    }
 
    private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        if (e.ExceptionObject is Exception ex)
        {
            Console.WriteLine($"捕获到未处理异常:{ex.GetType()}");
            Console.WriteLine($"异常信息:{ex.Message}");
            Console.WriteLine($"异常堆栈:{ex.StackTrace}");
            Console.WriteLine($"CLR即将退出:{e.IsTerminating}");
        }
 
        Console.ReadKey();
    }
}

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

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

不过这个程序包的作者在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:

using System;
using System.Net;
 
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
 
 
public class MyExceptionHandler : IExceptionFilter
{
    public void OnException(ExceptionContext context)
    {
        Exception ex = context.Exception;
 
        //这里可以写一些日志记录的代码
 
        context.Result = new ContentResult()
        {
            Content = $"捕捉到未处理的异常:{ex.GetType()}\nFilter已进行错误处理。",
            ContentType = "text/plain",
            StatusCode = (int)HttpStatusCode.InternalServerError
        };
        context.ExceptionHandled = true;//设置异常已经处理
    }
}

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

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

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

services.AddMvc(options =>
{
    options.Filters.Add<MyExceptionHandler>();
})

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

services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

那就改成这样:

services.AddMvc(options =>
{
    options.Filters.Add<MyExceptionHandler>();
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

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

using System;
 
using Microsoft.AspNetCore.Mvc;
 
public class HomeController : Controller
{
    public IActionResult Test()
    {
        throw new Exception();
    }
}

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

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

可以,完美。

六、写在最后

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

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

小柊

2018年12月17日 23:33:20

关于 小柊

就是一个简单的孩子。

活在梦里的程序员。

本站所有文章转载时请注明原出处,谢谢合作。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

以奋斗者为本

以奋斗者为本

黄卫伟 / 中信出版社 / 2014-11-1 / 68.00元

《以奋斗者为本:华为公司人力资源管理纲要》传承于《华为公司基本法》,华为管理层25年人力资源管理思想精髓,5年整理,华为公司内训教材,首次大公开!作为华为公司内部培训教材,原汁原味,是继《华为基本法》之后华为的标志性著作,对国内外企业管理者&研究者具有高度的研究及借鉴价值。《以奋斗者为本:华为公司人力资源管理纲要》由华为公司首席管理科学家主编,华为高管及顾问参与编著,华为管理层25年实践,权威出品......一起来看看 《以奋斗者为本》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具