查看: 125|回复: 1

ASP.NET Core MVC/WebAPI中另辟蹊径的全局统一异常 ...

[复制链接]

2

主题

5

帖子

11

积分

新手上路

Rank: 1

积分
11
发表于 2022-12-9 20:42:12 | 显示全部楼层 |阅读模式
作为一名合格的.NET开发者,大家都知道在程序发生异常的时候,不应该将详细的异常堆栈信息抛给前台用户显示,我们应该对程序所有的不可预知的异常做统一处理,返回一个有好的提示给前台用户,并在程序里将错误信息以日志的形式记录下来,比如一个友好的错误页面,像我自己网站的404页面和503页面:




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

为了方便演示,我先搭建一个基础的http://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,其实这种在控制器中实现异常记录的方式个人感觉更简单且方便,既然微软已经为我们开放了这样的方式,我们为什么不用呢?

网站开源代码:
转自原文:
回复

使用道具 举报

2

主题

9

帖子

17

积分

新手上路

Rank: 1

积分
17
发表于 6 天前 | 显示全部楼层
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表