ASP.NET CORE 学习之自定义异常处理
为什么异常处理选择中间件?
传统的asp.net可以采用异常过滤器的方式处理异常,在asp.net core中,是以多个中间件连接而成的管道形式处理请求的,不过常用的五大过滤器得以保留,同样可以采用异常过滤器处理异常,但是异常过滤器不能处理mvc中间件以外的异常,为了全局统一考虑,采用中间件处理异常更为合适
为什么选择自定义异常中间件?
先来看看asp.net core 内置的三个异常处理中间件 developerexceptionpagemiddleware , exceptionhandlermiddleware, statuscodepagesmiddleware
1.developerexceptionpagemiddleware
能给出详细的请求/返回/错误信息,因为包含敏感信息,所以仅适合开发环境
2.exceptionhandlermiddleware (蒋神博客 )
仅处理500错误
3.statuscodepagesmiddleware (蒋神博客 )
能处理400-599之间的错误,但需要response中不能包含内容(contentlength=0 && contenttype=null,经实验不能响应mvc里未捕获异常)
由于exceptionhandlermiddleware和statuscodepagesmiddleware的各自的限制条件,两者需要搭配使用。相比之下自定义中间件更加灵活,既能对各种错误状态进行统一处理,也能按照配置决定处理方式。
customexceptionmiddleware
首先声明异常中间件的配置类
1 /// <summary> 2 /// 异常中间件配置对象 3 /// </summary> 4 public class customexceptionmiddlewareoption 5 { 6 public customexceptionmiddlewareoption( 7 customexceptionhandletype handletype = customexceptionhandletype.jsonhandle, 8 ilist<pathstring> jsonhandleurlkeys = null, 9 string errorhandingpath = "") 10 { 11 handletype = handletype; 12 jsonhandleurlkeys = jsonhandleurlkeys; 13 errorhandingpath = errorhandingpath; 14 } 15 16 /// <summary> 17 /// 异常处理方式 18 /// </summary> 19 public customexceptionhandletype handletype { get; set; } 20 21 /// <summary> 22 /// json处理方式的url关键字 23 /// <para>仅handletype=both时生效</para> 24 /// </summary> 25 public ilist<pathstring> jsonhandleurlkeys { get; set; } 26 27 /// <summary> 28 /// 错误跳转页面 29 /// </summary> 30 public pathstring errorhandingpath { get; set; } 31 } 32 33 /// <summary> 34 /// 错误处理方式 35 /// </summary> 36 public enum customexceptionhandletype 37 { 38 jsonhandle = 0, //json形式处理 39 pagehandle = 1, //跳转网页处理 40 both = 2 //根据url关键字自动处理 41 }
声明异常中间件的成员
/// <summary> /// 管道请求委托 /// </summary> private requestdelegate _next; /// <summary> /// 配置对象 /// </summary> private customexceptionmiddlewareoption _option; /// <summary> /// 需要处理的状态码字典 /// </summary> private idictionary<int, string> exceptionstatuscodedic; public customexceptionmiddleware(requestdelegate next, customexceptionmiddlewareoption option) { _next = next; _option = option; exceptionstatuscodedic = new dictionary<int, string> { { 401, "未授权的请求" }, { 404, "找不到该页面" }, { 403, "访问被拒绝" }, { 500, "服务器发生意外的错误" } //其余状态自行扩展 }; }
异常中间件主要逻辑
1 public async task invoke(httpcontext context) 2 { 3 exception exception = null; 4 try 5 { 6 await _next(context); //调用管道执行下一个中间件 7 } 8 catch (exception ex) 9 { 10 context.response.clear(); 11 context.response.statuscode = 500; //发生未捕获的异常,手动设置状态码 12 exception = ex; 13 } 14 finally 15 { 16 if (exceptionstatuscodedic.containskey(context.response.statuscode) && 17 !context.items.containskey("exceptionhandled")) //预处理标记 18 { 19 var errormsg = string.empty; 20 if (context.response.statuscode == 500 && exception != null) 21 { 22 errormsg = $"{exceptionstatuscodedic[context.response.statuscode]}\r\n{(exception.innerexception != null ? exception.innerexception.message : exception.message)}"; 23 } 24 else 25 { 26 errormsg = exceptionstatuscodedic[context.response.statuscode]; 27 } 28 exception = new exception(errormsg); 29 } 30 31 if (exception != null) 32 { 33 var handletype = _option.handletype; 34 if (handletype == customexceptionhandletype.both) //根据url关键字决定异常处理方式 35 { 36 var requestpath = context.request.path; 37 handletype = _option.jsonhandleurlkeys != null && _option.jsonhandleurlkeys.count( 38 k => context.request.path.startswithsegments(k, stringcomparison.currentcultureignorecase)) > 0 ? 39 customexceptionhandletype.jsonhandle : 40 customexceptionhandletype.pagehandle; 41 } 42 43 if (handletype == customexceptionhandletype.jsonhandle) 44 await jsonhandle(context, exception); 45 else 46 await pagehandle(context, exception, _option.errorhandingpath); 47 } 48 } 49 } 50 51 /// <summary> 52 /// 统一格式响应类 53 /// </summary> 54 /// <param name="ex"></param> 55 /// <returns></returns> 56 private apiresponse getapiresponse(exception ex) 57 { 58 return new apiresponse() { issuccess = false, message = ex.message }; 59 } 60 61 /// <summary> 62 /// 处理方式:返回json格式 63 /// </summary> 64 /// <param name="context"></param> 65 /// <param name="ex"></param> 66 /// <returns></returns> 67 private async task jsonhandle(httpcontext context, exception ex) 68 { 69 var apiresponse = getapiresponse(ex); 70 var serialzestr = jsonconvert.serializeobject(apiresponse); 71 context.response.contenttype = "application/json"; 72 await context.response.writeasync(serialzestr, encoding.utf8); 73 } 74 75 /// <summary> 76 /// 处理方式:跳转网页 77 /// </summary> 78 /// <param name="context"></param> 79 /// <param name="ex"></param> 80 /// <param name="path"></param> 81 /// <returns></returns> 82 private async task pagehandle(httpcontext context, exception ex, pathstring path) 83 { 84 context.items.add("exception", ex); 85 var originpath = context.request.path; 86 context.request.path = path; //设置请求页面为错误跳转页面 87 try 88 { 89 await _next(context); 90 } 91 catch { } 92 finally 93 { 94 context.request.path = originpath; //恢复原始请求页面 95 } 96 }
使用扩展类进行中间件注册
1 public static class customexceptionmiddlewareextensions 2 { 3 4 public static iapplicationbuilder usecustomexception(this iapplicationbuilder app, customexceptionmiddlewareoption option) 5 { 6 return app.usemiddleware<customexceptionmiddleware>(option); 7 } 8 }
在startup.cs的configuref方法中注册异常中间件
1 app.usecustomexception(new customexceptionmiddlewareoption( 2 handletype: customexceptionhandletype.both, //根据url关键字决定处理方式 3 jsonhandleurlkeys: new pathstring[] { "/api" }, 4 errorhandingpath: "/home/error"));
接下来我们来进行测试,首先模拟一个将会进行页面跳转的未经捕获的异常
访问/home/about的结果
访问/home/test的结果 (该地址不存在)
ok异常跳转页面的方式测试完成,接下来我们测试返回统一格式(json)的异常处理,同样先模拟一个未经捕获的异常
访问/api/token/gettesterror的结果
访问/api/token/test的结果 (该地址不存在)
访问/api/token/getvalue的结果 (该接口需要身份验证)
测试完成,页面跳转和统一格式返回都没有问题,自定义异常中间件已按预期工作
需要注意的是,自定义中间件会响应每个http请求,所以处理逻辑一定要精简,防止发生不必要的性能问题
上一篇: 14Junit、反射、注解
下一篇: asp.net core 系列 1 概述
推荐阅读