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

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

内容简介:在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

关于 小柊

就是一个简单的孩子。

活在梦里的程序员。

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


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

查看所有标签

猜你喜欢:

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

Practical JavaScript, DOM Scripting and Ajax Projects

Practical JavaScript, DOM Scripting and Ajax Projects

Frank Zammetti / Apress / April 16, 2007 / $44.99

http://www.amazon.com/exec/obidos/tg/detail/-/1590598164/ Book Description Practical JavaScript, DOM, and Ajax Projects is ideal for web developers already experienced in JavaScript who want to ......一起来看看 《Practical JavaScript, DOM Scripting and Ajax Projects》 这本书的介绍吧!

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

各进制数互转换器

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器