欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

ASP.NET Core 3.1 WebAPI 自定义ActionFilter过滤器

程序员文章站 2022-05-03 16:53:53
ASP.NET Core Web API 自定义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

ASP.NET Core 3.1 WebAPI 自定义ActionFilter过滤器

 

 ASP.NET Core 3.1 WebAPI 自定义ActionFilter过滤器

 

 

 添加了自定义header,返回正常:

ASP.NET Core 3.1 WebAPI 自定义ActionFilter过滤器

 

 ASP.NET Core 3.1 WebAPI 自定义ActionFilter过滤器

 

至此,一个简单的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头:

ASP.NET Core 3.1 WebAPI 自定义ActionFilter过滤器

 

 needversionfilter接口添加version头:

ASP.NET Core 3.1 WebAPI 自定义ActionFilter过滤器

 

 noneedversionfilter不添加version头:

ASP.NET Core 3.1 WebAPI 自定义ActionFilter过滤器

 

 可以看到确实生效了。至此,带排除项的过滤器就完成了。

在过滤器中获取服务

上面的过滤器代码为了方便起见,判断版本号是否正确时直接用了 "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