原文地址: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