ASP.NET Core 3.1 WebAPI 自定义ActionFilter过滤器
原文地址:https://www.cnblogs.com/jingjiangtao/p/14711003.html
准备工作
为了演示自定义过滤器,需要新建一个 asp.net core web api 项目,项目配置可以按照自己的习惯来,也可以参考下面的配置,总之能让项目跑起来就可以。
startup类:
public class startup { public startup(iconfiguration configuration) { configuration = configuration; } public iconfiguration configuration { get; } // this method gets called by the runtime. use this method to add services to the container. public void configureservices(iservicecollection services) { services.addcontrollers(); } // this method gets called by the runtime. use this method to configure the http request pipeline. public void configure(iapplicationbuilder app, iwebhostenvironment env) { if (env.isdevelopment()) { app.usedeveloperexceptionpage(); } app.userouting(); app.useauthorization(); app.useendpoints(endpoints => { endpoints.mapcontrollers(); }); } }
launchsettings.json
{ "$schema": "http://json.schemastore.org/launchsettings.json", "profiles": { "customizeactionfilter": { "commandname": "project", "dotnetrunmessages": "true", "launchbrowser": true, "launchurl": "", "applicationurl": "http://localhost:5000", "environmentvariables": { "aspnetcore_environment": "development" } } } }
在controllers目录下新建samplecontroller控制器类。这个类只是用来演示,没有业务上的意义:
public class samplecontroller : controllerbase { [httpget("needversionfilter")] public iactionresult needversionfilter() { return ok("ok: need version"); } [httpget("noneedversionfilter")] public iactionresult noneedversionfilter() { return ok("ok: no need version"); } }
自定义过滤器
接下来开始编写自定义action filter。在项目根目录下新建actionfilters目录,在此目录下新建类versioncheckattribute,该类继承自attribute并实现了iactionfilter接口。继承attribute可以让自定义过滤器以特性的方式使用,也就是用方括号括起来的形式,也有人叫标签;实现iactionfilter接口可以在请求的不同阶段添加处理逻辑。
[serializable, attributeusage(attributetargets.method | attributetargets.class)] public class versioncheckattribute : attribute, iactionfilter { public void onactionexecuted(actionexecutedcontext context) { } public void onactionexecuting(actionexecutingcontext context) { } }
其中onactionexecuting方法在请求进入控制器action之前执行,拦截代码也在这个方法中实现。
为做演示,假设需要实现这样一种拦截器:每个http请求的header中都应该带有自定义的参数version,如果version的值正确,则请求正常进入控制器执行,如果不正确,则直接返回,不再进入控制器。代码如下:
[serializable, attributeusage(attributetargets.method | attributetargets.class)] public class versioncheckattribute : attribute, iactionfilter { public void onactionexecuted(actionexecutedcontext context) { } public void onactionexecuting(actionexecutingcontext context) { if (!context.httpcontext.request.headers.containskey("version")) { context.result = new badrequestobjectresult("error: incorrect version"); return; } string headversionstr = context.httpcontext.request.headers["version"].firstordefault(); if (headversionstr != "1.0.0") { context.result = new badrequestobjectresult("error: incorrect version"); return; } } }
首先判断header中有没有version参数,没有的话直接返回错误。如果有version参数,则判断值是否正确,如果不正确也直接返回错误,如果正确,则继续向下执行,请求会进入控制器的action。给context.result赋值可以将请求短路,不会再进入控制器的action中执行,而是直接返回。
接下来在samplecontroller上应用versioncheck过滤器,让它过滤控制器中的所有请求:
[route("[controller]")] [apicontroller] [versioncheck] // 自定义过滤器 public class samplecontroller : controllerbase { [httpget("needversionfilter")] public iactionresult needversionfilter() { return ok("ok: need version"); } [httpget("noneedversionfilter")] public iactionresult noneedversionfilter() { return ok("ok: no need version"); } }
运行项目,用postman请求看看结果:
没有添加自定义header,返回 error: incorrect version
添加了自定义header,返回正常:
至此,一个简单的action filter已经完成了。
排除指定的action
上面实现的控制器有两个action,假设有这样一种需求:needversionfilter接口需要过滤版本号,noneedversionfilter接口不需要过滤版本号。这样的话,[versioncheck]放到samplecontroller类上就不行了,可以删掉控制器上的[versioncheck]特性,转而放到needversionfilter方法上,这样就实现了这个需求。
[route("[controller]")] [apicontroller] public class samplecontroller : controllerbase { [versioncheck] // 将过滤器放到方法上 [httpget("needversionfilter")] public iactionresult needversionfilter() { return ok("ok: need version"); } [httpget("noneedversionfilter")] public iactionresult noneedversionfilter() { return ok("ok: no need version"); } }
但是,如果controller中的action非常多,而大部分action都需要版本过滤器,只有少数几个不需要,用这种形式就要在每个方法上应用[versioncheck]特性,有点麻烦,还可能漏加。这时候如果把[versioncheck]应用到controller上,同时可以排除几个不需要过滤器的方法,写起来会更简洁。这是可以做到的,通过给不需要过滤器的方法做标记,就可以在过滤器中跳过有标记的方法了。
在actionfilters目录下新建类ignoreversioncheckattribute,继承自attribute类和ifiltermetadata接口。ifiltermetadata接口没有需要实现的方法,仅作为标记:
[serializable, attributeusage(attributetargets.method)] public class ignoreversioncheckattribute : attribute, ifiltermetadata { }
修改versioncheckattribute类的代码,让过滤器跳过标记为ignoreversioncheck的方法:
[serializable, attributeusage(attributetargets.method | attributetargets.class)] public class versioncheckattribute : attribute, iactionfilter { public void onactionexecuted(actionexecutedcontext context) { } public void onactionexecuting(actionexecutingcontext context) { if (hasignoreversioncheck(context)) { return; } if (!context.httpcontext.request.headers.containskey("version")) { context.result = new badrequestobjectresult("error: incorrect version"); return; } string headversionstr = context.httpcontext.request.headers["version"].firstordefault(); if (headversionstr != "1.0.0") { context.result = new badrequestobjectresult("error: incorrect version"); return; } } private bool hasignoreversioncheck(actionexecutingcontext context) { ilist<ifiltermetadata> filters = context.filters; foreach (ifiltermetadata filter in filters) { if (filter is ignoreversioncheckattribute) { return true; } } return false; } }
可以看到,在刚进入action时就判断是否有ignoreversioncheck,如果有,则直接退出过滤器,继续执行controller中的代码,如果没有则继续执行过滤器。hasignoreversioncheck方法从actionexecutingcontext中拿到当前action上的所有filter,遍历查找有没有ignoreversioncheckattribute,有则返回true,没有则返回false。
修改samplecontroller的代码,把[versioncheck]放到控制器上,在noneedversionfilter方法上添加[ignoreversioncheck]
[route("[controller]")] [apicontroller] [versioncheck] public class samplecontroller : controllerbase { [httpget("needversionfilter")] public iactionresult needversionfilter() { return ok("ok: need version"); } [ignoreversioncheck] [httpget("noneedversionfilter")] public iactionresult noneedversionfilter() { return ok("ok: no need version"); } }
测试一下是否生效。
needversionfilter接口不添加version头:
needversionfilter接口添加version头:
noneedversionfilter不添加version头:
可以看到确实生效了。至此,带排除项的过滤器就完成了。
在过滤器中获取服务
上面的过滤器代码为了方便起见,判断版本号是否正确时直接用了 "1.0.0" 这种硬编码的字符串,实际项目中这个字符串可能是会变化的,最好写在配置文件中。在appsetting.development.json中添加字段 "versionfilter" 并赋值:
{ "logging": { "loglevel": { "default": "information", "microsoft": "warning", "microsoft.hosting.lifetime": "information" } }, "versionfilter": "1.0.0" }
修改versioncheckattribute的代码,通过actionexecutedcontext中的属性获取iconfiguration服务,再从iconfiguration实例中获取字符串:
[serializable, attributeusage(attributetargets.method | attributetargets.class)] public class versioncheckattribute : attribute, iactionfilter { public void onactionexecuted(actionexecutedcontext context) { } public void onactionexecuting(actionexecutingcontext context) { if (hasignoreversioncheck(context)) { return; } if (!context.httpcontext.request.headers.containskey("version")) { context.result = new badrequestobjectresult("error: incorrect version"); return; } string headversionstr = context.httpcontext.request.headers["version"].firstordefault();
// 获取配置服务 var configuration = context.httpcontext.requestservices.getrequiredservice<iconfiguration>(); string confversionstr = configuration.getvalue<string>("versionfilter"); if (headversionstr != confversionstr) { context.result = new badrequestobjectresult("error: incorrect version"); return; } } private bool hasignoreversioncheck(actionexecutingcontext context) { ilist<ifiltermetadata> filters = context.filters; foreach (ifiltermetadata filter in filters) { if (filter is ignoreversioncheckattribute) { return true; } } return false; } }
这样,一个比较灵活的自定义actionfilter就完成了。
完整代码:https://github.com/jingjiangtao/practicecollection/tree/master/customizeactionfilter
推荐阅读
-
ASP.NET Core 3.1 WebAPI 自定义ActionFilter过滤器
-
ASP.NET Core实现自定义WebApi模型验证详解
-
asp.net core MVC 过滤器之ActionFilter过滤器(2)
-
asp.net core MVC 过滤器之ActionFilter过滤器(2)
-
ASP.NET Core 3.1 WebAPI 自定义ActionFilter过滤器
-
ASP.NET Core实现自定义WebApi模型验证详解
-
asp.net core MVC 过滤器之ActionFilter过滤器(2)
-
asp.net core MVC 过滤器之ActionFilter过滤器(2)