【ASP.NET Core】处理异常(下篇)

上一篇中,老周给大伙伴们扯了有关 ASP.NET Core 中异常处理的简单方法。按照老周的优良做风,咱们应该顺着这个思路继续挖掘。html

本文老周就蚍蜉撼树地介绍一下如何使用 MVC Filter 来处理异常。MVC 模型(固然适用于 Razor Page 、Web API 模型)能够用一系列的 Filter 来对请求与回应消息进行过滤处理。其中,在 Microsoft.AspNetCore.Mvc.Filters 命名空间下,你会发现有两个接口,它们跟异常处理有关:api

IExceptionFilter:实现 OnException 方法,能够自定义回传给客户端的异常信息。浏览器

IAsyncExceptionFilter:跟上面的同样的,只不过这厮支持异步等待而已。服务器

 

在实现处理异常的 Filter 时,传给 OnException / OnExceptionAsync 方法的有一个 ExceptionContext 类型参数,咱们能够经过它来设置自定义的返回结果。app

访问 Exception 属性,你能够获得相关的异常实例,固然这个属性是可写的,因此你能够获取异常实例后,将它改成其余异常实例,再从新赋给这个属性,好比,你用你本身编写的异常类来从新封装。经过 Result 属性设置返回结果,这个与 MVC Action 方法的返回方法同样,不一样的是,在 Action 方法中,你能够调用 Controller 基类的方法来返回对应的 Result ,而对于 Result 属性,你必须显式地去建立实现了 IActionResult 接口的类型实例。异步

另外,值得注意的是,ExceptionContext 类还有一个 ExceptionHandled 属性,该属性值可读可写,主要是用于标识当前发生的异常是否已通过处理。这主要是应对 Filter 的执行顺序的,一种状况是你可能使用了多个 Filter 来处理异常,在处理过程当中你就能够将这个属性值设为 true 以表示这个错误已处理过了,后面的就没必要处理了;另外一种状况是,以 Attribute 方式使用的 Filter 的优先级会比全局使用的 Filter 高,也许在 Attribute 上我没有对异常进行处理,那么到了全局 Filter 执行的时候,我就能够检查一下这个属性,若是没有处理就进行一下处理。关于 Attribute 方式使用 Filter 老周随后会说的,这里先提一下。ide

 

好了,我们先说说如何实现本身的异常处理 Filter,其实很简单,看下面代码。测试

    public class MyExceptionFilter : IExceptionFilter, IFilterMetadata
    {
        public void OnException(ExceptionContext context)
        {
            if(context.ExceptionHandled == false)
            {
                string msg = context.Exception.Message;
                context.Result = new ContentResult
                {
                    Content = msg,
                    StatusCode = StatusCodes.Status200OK,
                    ContentType = "text/html;charset=utf-8"
                };
            }
            context.ExceptionHandled = true; //异常已处理了
        }

在 OnException 方法中,我直接获取异常信息,而后用一个 ContentResult 对象来返回,这个是相似于 MVC 中 Controller . Action 方法返回结果,我这里简单地以 HTML 文本形式返回,一旦处理到异常,应用程序会自动把这个 Result 返回给客户端。ui

你可能发现了,我除了实现 IExceptionFilter 接口外,还实现了一个 IFilterMetadata 接口,这个接口是必须的,否则待会儿咱们没法应用这个 Filter 了,为何呢,等一下你就会明白了。spa

这里实现的这个是同步调用的,若是你但愿有一个可异步等待的版本,那么,你就顺便实现一下 IAsyncExceptionFilter 接口。把上面的代码改成:

    public class MyExceptionFilter : IExceptionFilter, IAsyncExceptionFilter, IFilterMetadata
    {
        public void OnException(ExceptionContext context)
        {
            if(context.ExceptionHandled == false)
            {
                string msg = context.Exception.Message;
                context.Result = new ContentResult
                {
                    Content = msg,
                    StatusCode = StatusCodes.Status200OK,
                    ContentType = "text/html;charset=utf-8"
                };
            }
            context.ExceptionHandled = true; //异常已处理了
        }

        public Task OnExceptionAsync(ExceptionContext context)
        {
 OnException(context); return Task.CompletedTask;
        }
    }

 

好了,接下来我们得考虑怎么用它了。在 Startup.ConfigureServices 方法中,添加 MVC 功能后能够把我们本身写的 Filter 添加进去。

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc(opt =>
            {
                opt.Filters.Add<MyExceptionFilter>();             });
        }

上面代码添加 Filter 后,是用于全局的,说白了,当应用程序内无论哪一个 Controller 里面发生的异常,都会通过我们添加的 Filter 处理。

 

如今咱们测试一下这个异常处理的 Filter 起到什么做用。为了避免影响测试,请把 Configure 方法中这段代码删除。

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseMvc();
        }

变成这样

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseMvc();
        }

 

而后,随便弄段代码来测试。

        [HttpPost("/code")]
        public IActionResult SubmitSome(int val)
        {
            if(val <= 0) { throw new ArgumentException("号码不能小于或等于 0。"); } return Content($"恭喜你,中奖了。\n中奖号码为:{val}", "text/html;charset=utf-8");
        }

这个逻辑很简单,就是在前台页面输入一个数值,而后 POST 上来,若是数值不是大于 0 的值就抛异常。

 

而后我故意输入一个 -10。

 

 POST 后在服务器上引起异常。

继续执行,让 Filter 对异常进行处理。

最后,异常信息就返回给浏览器了。

 

 这样说明我们写的 Filter 起做用了。

刚刚说过,在 ConfigureServices 方法中添加的 Filter 是用于全局的,若是咱们的项目中有个别的 Controller 或者 Controller 中的个别方法,但愿使用专门的 Filter 去处理异常,这时候就能够考虑以 Attribute 的方式去处理。

要用 Attribute 方式处理异常,须要实现 ExceptionFilterAttribute 抽象类。该抽象类已实现了我们上面提到过的几个接口。

这个类还实现了 IOrderedFilter 接口,能够用来安排多个 Attribute 实例在处理异常上的顺序(假设你用了多个实例来处理)。

 

下面我们本身实现一个 Attribute ,用来处理异常。

    public class MyExceptionFilterAttribute : ExceptionFilterAttribute
    {
        public override void OnException(ExceptionContext context)
        {
            var ex = context.Exception;
            // 构建错误信息对象
            var dic = new Dictionary<string, object>
            {
                ["err_code"] = 80250,
                ["err_msg"] = ex.Message,
                ["err_sol"] = "建议携带你的数据到医院作检查。"
            };
            // 设置结果
            context.Result = new JsonResult(dic);
            context.ExceptionHandled = true;
        }

        public override Task OnExceptionAsync(ExceptionContext context)
        {
            OnException(context);
            return Task.CompletedTask;
        }
    }

上面代码中,我以 JSON 格式返回错误数据。

 

这个 Attribute 能够用于类与方法,而后我们用 Web API 来测试。

    [Route("api/[controller]")]
    public class DemoController : Controller
    {
        [HttpGet]
        [MyExceptionFilter] public IActionResult Compute(int m, int n)
        {
            if (m < 0 || n < 0)
            {
                throw new Exception("数值不能小于 0。");
            }
            return Json(new { num1 = m, num2 = n, result = m + n });
        }
    }

此处把 attrbute 用到方法上。

 

运行应用程序,而后请出 Postman 大叔来帮咱们测试 Web API。为参数 m 和 n 赋值,而后以 GET 方式发送请求。

得到正确的结果,如今我们提交小于 0 的参数。就会返回刚刚自定义的错误。

 

 好了,今天的内容就说到这里,下次有空继续扯。

示例源代码下载地址

相关文章
相关标签/搜索