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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM