利用过滤器Filter和特性Attribute实现对Web API返回结果的封装和统一异常处理
在我们开发web api应用的时候,我们可以借鉴abp框架的过滤器filter和特性attribute的应用,实现对web api返回结果的封装和统一异常处理,本篇随笔介绍利用authorizeattribute实现web api身份认证,利用actionfilterattribute实现对常规web api返回结果进行统一格式的封装,利用exceptionfilterattribute实现对接口异常的统一处理,实现我们web api常用到的几个通用处理过程。
1、asp.net的web api过滤器介绍
过滤器主要有这么几种:authorizationfilterattribute 权限验证、actionfilterattribute 日志参数验证等、exceptionfilterattribute 异常处理捕获。
actionfilter 主要实现执行请求方法体之前(覆盖基类方法onactionexecuting),和之后的事件处理(覆盖基类方法onactionexecuted);exceptionfilter主要实现触发异常方法(覆盖基类方法onexception)。
actionfilterattrubute提供了两个方法进行拦截:
- onactionexecuting和onactionexecuted,他们都提供了同步和异步的方法。
- onactionexecuting方法在action执行之前执行,onactionexecuted方法在action执行完成之后执行。
在使用mvc的时候,actionfilter提供了一个order属性,用户可以根据这个属性控制filter的调用顺序,而web api却不再支持该属性。web api的filter有自己的一套调用顺序规则:
所有filter根据注册位置的不同拥有三种作用域:global、controller、action:
-
通过httpconfiguration类实例下filters.add()方法注册的filter(一般在app_start\webapiconfig.cs文件中的register方法中设置)就属于global作用域;
-
通过controller上打的attribute进行注册的filter就属于controller作用域;
-
通过action上打的attribute进行注册的filter就属于action作用域;
他们遵循了以下规则:
- 在同一作用域下,authorizationfilter最先执行,之后执行actionfilter
- 对于authorizationfilter和actionfilter.onactionexcuting来说,如果一个请求的生命周期中有多个filter的话,执行顺序都是global->controller->action;
- 对于actionfilter,onactionexecuting总是先于onactionexecuted执行;
- 对于exceptionfilter和actionfilter.onactionexcuted而言执行顺序为action->controller->global;
- 对于所有filter来说,如果阻止了请求:即对response进行了赋值,则后续的filter不再执行。
另外,值得注意的是,由于web api的过滤器无法改变其顺序,那么它是按照 authorizationfilterattribute -> actionfilterattribute -> exceptionfilterattribute 这个执行顺序来处理的,也就是说授权过滤器执行在前面,再次到自定义的actionfilter,最后才是异常的过滤器处理。
2、web api的身份授权过滤器处理
我们通过authorizationfilterattribute 过滤器来处理用户web api接口身份,比每次在代码上进行验证省事很多。
一般情况下,我们只要定义类继承于authorizeattribute即可,由于authorizeattribute是继承于authorizationfilterattribute,如下所示。
/// <summary> /// 验证web api接口用户身份 /// </summary> public class apiauthorizeattribute : authorizeattribute { ........... }
而一般情况下,我们只需要重写bool isauthorized(httpactioncontext actioncontext) 方法,实现授权处理逻辑即可。
我们在checktoken的主要逻辑里面,主要对token令牌进行反向解析,并判断用户身份是否符合,如果不符合抛出异常,就会切换到异常处理器里面了。
然后在web api控制器中,需要授权访问的api控制器定义即可,我们可以把它放到基类里面声明这个过滤器特性。
那么所有api接口的访问,都会检查用户的身份了。
2、自定义过滤器特性actionfilterattribute 的处理
这个actionfilterattribute 主要用于拦截用户访问控制器方法的处理过程,前面说到,onactionexecuting方法在action执行之前执行,onactionexecuted方法在action执行完成之后执行。
那么我们可以利用它进行函数aop的处理了,也就是在执行前,执行后进行日志记录等,还有就是常规的参数检查、结果封装等,都可以在这个自定义过滤器中实现。
我们定义一个类wrapresultattribute来标记封装结果,定义一个类dontwrapresultattribute来标记不封装结果。
/// <summary> /// 用于判断web api需要包装返回结果. /// </summary> [attributeusage(attributetargets.class | attributetargets.interface | attributetargets.method)] public class wrapresultattribute : actionfilterattribute { } /// <summary> /// 用于判断web api不需要包装返回结果. /// </summary> [attributeusage(attributetargets.class | attributetargets.interface | attributetargets.method)] public class dontwrapresultattribute : wrapresultattribute { }
这个处理方式是借用abp框架中这两个特性的处理逻辑。
利用,对于获取用户身份令牌的基础操作接口,我们可以不封装返回结果,如下标记所示。
那么执行后,返回的结果如下所示,就是正常的tokenresult对象的json信息
{ "access_token": "eyj0exaioijkv1qilcjhbgcioijiuzi1nij9.eyjpc3mioiixiiwiawf0ijoxnje3mjy0mdq4lcjqdgkioii0ntbjzmy3oc01otewltqwyzutymjjmc01otq0yznjmjhjntuilcjuyw1lijoiywrtaw4ilcjjb3jwawqioii2iiwiy2hhbm5lbci6ijailcjzagfyzwrrzxkioiixmjm0ywjjzcj9.umv4j80sj6bnoccgo5lrnyddwtfqu5a8jii92sjpapw", "expires_in": 604800 }
如果取消这个dontwrapresult的标记,那么它就继承基类baseapicontroller的wrapresult的标记定义了。
/// <summary> /// 所有接口基类 /// </summary> [exceptionhandling] [wrapresult] public class baseapicontroller : apicontroller
那么接口定义不变,但是返回的okenresult对象的json信息已经经过包装了。
{ "result": { "access_token": "eyj0exaioijkv1qilcjhbgcioijiuzi1nij9.eyjpc3mioiixiiwiawf0ijoxnje3mjy0ndq5lcjqdgkioijmztazyzhlni03ngvjltrjnmetymmyzi01ntu3mjfiotm1ndeilcjuyw1lijoiywrtaw4ilcjjb3jwawqioii2iiwiy2hhbm5lbci6ijailcjzagfyzwrrzxkioiixmjm0ywjjzcj9.9b4dyoe9ytisl36a-w_evls2o8raopwvduir2lxho1c", "expires_in": 604800 }, "targeturl": null, "success": true, "error": null, "unauthorizedrequest": false, "__api": true }
这个json格式是我们一个通用的接口返回,其中result里面定义了返回的信息,而error里面则定义一些错误信息(如果有错误的话),而success则用于判断是否执行成功,如果有错误异常信息,那么success返回为false。
这个通用返回的定义,是依照abp框架的返回格式进行调整的,可以作为我们普通web api的一个通用返回结果的处理。
前面提到过actionfilterattribute自定义处理过程,在控制器方法完成后,我们对返回的结果进行进一步的封装处理即可。
我们需要重写逻辑实现onactionexecuted的函数
在做包装返回结果之前,我们需要判断是否方法或者控制器设置了不包装的标记dontwrapresultattribute。
public override void onactionexecuted(httpactionexecutedcontext actionexecutedcontext) { //如果有异常,则退出交给exception的通用处理 if (actionexecutedcontext.exception != null) return; //正常完成,那么判断是否需要包装结果输出,如果不需要则返回 var dontwrap = false; var actioncontext = actionexecutedcontext.actioncontext; if (actioncontext.actiondescriptor is reflectedhttpactiondescriptor actiondesc) { //判断方法是否包含dontwrapresultattribute dontwrap = actiondesc.methodinfo.getcustomattributes(inherit: false) .any(a => a.gettype().equals(typeof(dontwrapresultattribute))); if (dontwrap) return; } if (actioncontext.controllercontext.controllerdescriptor is httpcontrollerdescriptor controllerdesc) { //判断控制器是否包含dontwrapresultattribute dontwrap = controllerdesc.getcustomattributes<attribute>(inherit: true) .any(a => a.gettype().equals(typeof(dontwrapresultattribute))); if (dontwrap) return; }
上述代码也就是如果找到方法或者控制器有定义dontwrapresultattribute的,就不要包装结果,否则下一步就是对结果进行封装了
//需要包装,那么就包装输出结果 ajaxresponse result = new ajaxresponse(); // 状态代码 var statuscode = actioncontext.response.statuscode; // 读取返回的内容 var content = actioncontext.response.content.readasasync<object>().result; // 请求是否成功 result.success = actioncontext.response.issuccessstatuscode; // 重新封装回传格式 actionexecutedcontext.response = new httpresponsemessage(statuscode) { content = new objectcontent<ajaxresponse>( new ajaxresponse(content), jsonfomatterhelper.getformatter()) };
其中ajaxresponse是参考abp框架里面返回结果的类定义处理的。
public abstract class ajaxresponsebase { public string targeturl { get; set; } public bool success { get; set; } public errorinfo error { get; set; } public bool unauthorizedrequest { get; set; } public bool __api { get; } = true; }
[serializable] public class ajaxresponse<tresult> : ajaxresponsebase { public tresult result { get; set; } }
3、异常处理过滤器exceptionfilterattribute
前面介绍到,web api的过滤器无法改变其顺序,它是按照 authorizationfilterattribute -> actionfilterattribute -> exceptionfilterattribute 这个执行顺序来处理的,也就是说授权过滤器执行在前面,再次到自定义的actionfilter,最后才是异常的过滤器处理。
异常处理过滤器,我们定义后,可以统一处理和封装异常信息,而我们只需要实现onexception的方法即可。
/// <summary> /// 自定义异常处理 /// </summary> public class exceptionhandlingattribute : exceptionfilterattribute { /// <summary> /// 统一对调用异常信息进行处理,返回自定义的异常信息 /// </summary> /// <param name="context">http上下文对象</param> public override void onexception(httpactionexecutedcontext context) { } }
完整的处理过程代码如下所示。
/// <summary> /// 自定义异常处理 /// </summary> public class exceptionhandlingattribute : exceptionfilterattribute { /// <summary> /// 统一对调用异常信息进行处理,返回自定义的异常信息 /// </summary> /// <param name="context">http上下文对象</param> public override void onexception(httpactionexecutedcontext context) { //获取方法或控制器对应的wrapresultattribute属性 var actiondescriptor = context.actioncontext.actiondescriptor; var wrapresult = actiondescriptor.getcustomattributes<wrapresultattribute>(inherit: true).firstordefault() ?? actiondescriptor.controllerdescriptor.getcustomattributes<wrapresultattribute>(inherit: true).firstordefault(); //如设置,记录异常信息 if (wrapresult != null && wrapresult.logerror) { loghelper.error(context.exception); } var statuscode = getstatuscode(context, wrapresult.wraponerror); if (!wrapresult.wraponerror) { context.response = new httpresponsemessage(statuscode) { content = new stringcontent(context.exception.message.tojson()) }; context.exception = null; //handled! return; } //使用ajaxresponse包装结果 var content = new errorinfo(context.exception.message/*, context.exception.stacktrace*/); var isauth = context.exception is authorizationexception; context.response = new httpresponsemessage(statuscode) { content = new objectcontent<ajaxresponse>( new ajaxresponse(content, isauth), jsonfomatterhelper.getformatter()) }; context.exception = null; //handled! }
这样我们在baseapicontroller里面声明即可。
这样,一旦程序处理过程中,有错误抛出,都会统一到这里进行处理,有异常的返回json如下所示。
本篇随笔介绍利用authorizeattribute实现web api身份认证,利用actionfilterattribute实现对常规web api返回结果进行统一格式的封装,利用exceptionfilterattribute实现对接口异常的统一处理,实现我们web api常用到的几个通用处理过程。