1. 首页
  2. 文章列表
  3. ASP.NET Core MVC/WebAPI中另辟蹊径的全局统一异常处理方式

作为一名合格的.NET开发者,大家都知道在程序发生异常的时候,不应该将详细的异常堆栈信息抛给前台用户显示,我们应该对程序所有的不可预知的异常做统一处理,返回一个有好的提示给前台用户,并在程序里将错误信息以日志的形式记录下来,比如一个友好的错误页面,像本站的404页面和503页面:

懒得勤快的博客_互联网分享精神懒得勤快的博客_互联网分享精神

相信大家对统一异常处理都比较熟悉,可以通过自己实现一个异常拦截的中间件实现,也可以实现一个FilterAttribute对所有的控制器进行异常拦截,这都是比较常见的异常处理方式,但以上两种,如果想在程序出现错误时,不发生路由跳转直达错误页,有时候还是不太方便,所以,今天给大家另辟蹊径分享一个全局异常的同一拦截处理方式,发生错误时,页面路由不会发生跳转。

那就是微软自己提供的——UseExceptionHandler中间件。

开始打码实现吧

为了方便演示,我先搭建一个基础的ASP.NET Core项目,并创建了一个HomeController:

    public class HomeController : Controller
    {
        // GET
        public IActionResult Index()
        {
            return Ok("这是首页");
        }
        [HttpGet("test")]
        public ActionResult Test()
        {
            throw new Exception("手动发生一个异常");
        }
    }

再创建一个ErrorController控制器,并写一个Action来作为我们的错误页面:

    [Route("error")]
    public class ErrorController : Controller
    {
        [HttpGet]
        public IActionResult Index()
        {
            return Ok("假装是一个错误页面");
        }
    }

接下来实现异常处理中间件,该中间件在Startup的Configure方法中注册,需要传入一个路由,而错误页的路由是“/error”,所以注册中间件:

app.UseExceptionHandler("/error");

然后我们运行项目,触发一次异常:

懒得勤快的博客_互联网分享精神

发生异常的时候,确实已经到我们的错误页了,而且路由没有改变,但是,我们没有记录下发生错误时的堆栈信息,为了后期的诊断,我们肯定要记录详细的异常信息才行,那如何将异常信息记录下来呢?

UseExceptionHandler中间件中拦截到异常时,会将异常信息保存在请求上下文中,所以我们可以从HttpContext中拿到ExceptionHandler的异常信息:

var feature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();

如果程序有异常,那么feature是一个不为null的对象,其属性Error便是我们需要的Exception,所以,我们改造一下刚才的ErrorController:

    [Route("error")]
    public class ErrorController : Controller
    {
        [HttpGet]
        public IActionResult Index()
        {
            var feature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
            if (feature != null)
            {
                var exception = feature.Error;
                // todo:调用日志组件记录异常信息,或对异常做多路判断
            }
            return Ok("假装是一个错误页面");
        }
    }

比如本站的异常多路处理源码如下:

        [Route("ServiceUnavailable")]
        public ActionResult ServiceUnavailable()
        {
            var feature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
            if (feature != null)
            {
                string err;
                var req = HttpContext.Request;
                var ip = HttpContext.Connection.RemoteIpAddress.MapToIPv4().ToString();
                switch (feature.Error)
                {
                    case DbUpdateConcurrencyException ex:
                        err = $"异常源:{ex.Source},异常类型:{ex.GetType().Name},\n请求路径:{req.Scheme}://{req.Host}{HttpUtility.UrlDecode(req.Path)},客户端用户代理:{req.Headers["User-Agent"]},客户端IP:{ip}\t{ex.InnerException?.Message}\t";
                        LogManager.Error(err, ex);
                        break;
                    case DbUpdateException ex:
                        err = $"异常源:{ex.Source},异常类型:{ex.GetType().Name},\n请求路径:{req.Scheme}://{req.Host}{HttpUtility.UrlDecode(req.Path)},客户端用户代理:{req.Headers["User-Agent"]},客户端IP:{ip}\t{ex?.InnerException?.Message}\t";
                        LogManager.Error(err, ex);
                        break;
                    case AggregateException ex:
                        LogManager.Debug("↓↓↓" + ex.Message + "↓↓↓");
                        ex.Handle(e =>
                        {
                            LogManager.Error($"异常源:{e.Source},异常类型:{e.GetType().Name},\n请求路径:{req.Scheme}://{req.Host}{HttpUtility.UrlDecode(req.Path)},客户端用户代理:{req.Headers["User-Agent"]},客户端IP:{ip}\t", e);
                            return true;
                        });
                        break;
                    case NotFoundException ex:
                        Response.StatusCode = 404;
                        return Request.Method.ToLower().Equals("get") ? (ActionResult)View("Index") : Json(new
                        {
                            StatusCode = 404,
                            Success = false,
                            ex.Message
                        });
                    default:
                        LogManager.Error($"异常源:{feature.Error.Source},异常类型:{feature.Error.GetType().Name},\n请求路径:{req.Scheme}://{req.Host}{HttpUtility.UrlDecode(req.Path)},客户端用户代理:{req.Headers["User-Agent"]},客户端IP:{ip}\t", feature.Error);
                        break;
                }
            }
            Response.StatusCode = 503;
            if (Request.Method.ToLower().Equals("get"))
            {
                return View();
            }
            return Json(new
            {
                StatusCode = 503,
                Success = false,
                Message = "服务器发生错误!"
            });
        }

至此,全局异常处理已经完整实现。

总结

相较于自己实现中间件和FilterAttribute,其实这种在控制器中实现异常记录的方式个人感觉更简单且方便,既然微软已经为我们开放了这样的方式,我们为什么不用呢?

本站开源代码:https://github.com/ldqk/Masuit.MyBlogs

分享到:

优云666高速全隧道机场,每日签到免费领流量 [推广]

优云666高速全隧道机场,每日签到免费领流量

真正大鸡场,100多个节点,V2ray节点50多个。港台美日新均有白嫖节点,每日签到送1-7G流量。多条BGP中转/Azure/Dmit/HKT/Hinet/多点IPLC/保证高端用户使用需求。

相关推荐:

一些小众冷门但却非常实用的.NET(Core)开源库推荐 .NET Office组件神器——Aspose.Total 20.6学习版下载
浅谈http断点续传的原理以及.NET代码实现,看似挺高端,其实很简单 零度分享.NET Core2.2微服务入门实战教程
奉献两套Asp.Net Core最新视频教程 RedGate全家桶SQL ToolBelt 3.1/.NET Reflector 10.3.1/SQL Prompt 8.2破解版下载
.NET代码混淆器——Dotfuscator Professional for .NET 6.0.1破解版 C#高级编程(第10版)C# 6 & .NET Core 1.0 中文完整pdf扫描版[229MB]
.NET数据库建模神器——Devart Entity Developer 6.8.1019 Professional完美学习版 本站开源项目——.NET万能框架:Masuit.Tools_2.4.1版本发布

版权声明:

🈲⚠本文为作者原创,仅用于本站访客学习、研究和交流目的,未经授权禁止转载。️⚠🈲

评论区: