Asp.Net Core 進階(四)—— 過濾器 Filters


一、介紹  

  Asp.Net Core Filter 使得可以在請求處理管道的特定階段的前后執行代碼,我們可以創建自定義的 filter 用於處理橫切關注點。 橫切關注點的示例包括錯誤處理、緩存、配置、授權和日志記錄。 filter 使得可以避免重復代碼。

  Asp.Net Core 提供了5中過濾器類型,分別是:

  1、Authorization filters,授權過濾器是最先執行並且決定請求用戶是否經過授權認證,如果請求未獲授權,授權過濾器可以讓管道短路。

  2、Resource filters,資源過濾器在Authorization filter執行完后執行,它有兩個方法,OnResourceExecuting可以在filter管道的其余階段之前運行代碼,例如可以在模型綁定之前運行。OnResourceExecuted則可以在filter管道的其余階段之后運行代碼

  3、Action filters,操作過濾器可以在調用單個操作方法之前和之后立即運行代碼,它可以用於處理傳入某個操作的參數以及從該操作返回的結果。 需要注意的是,Aciton filter不可以在 Razor Pages 中使用。

  4、Exception filters,異常過濾器常常被用於在向響應正文寫入任何內容之前,對未經處理的異常應用全局策略。

  5、Result filters,結果過濾器可以在執行單個操作結果之前和之后運行代碼。 但是只有在方法成功執行時,它才會運行。

  至於它們在filter管道中的交互方式可以看下圖。

    

二、實現

  Asp.Net Core提供了同步和異步兩種filter接口定義,通過實現不同的接口可以實現同步或異步的過濾器,但是如果一個類同時實現了同步和異步的接口,Asp.Net Core的filter 管道只會調用異步的方法,即異步優先。具體的做法可以參考微軟官方文檔 https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-2.2

  以下是IActionFilter和IAsyncActionFilter的實現

public class MySampleActionFilter : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
    }
}

public class SampleAsyncActionFilter : IAsyncActionFilter
{
    public async Task OnActionExecutionAsync(
        ActionExecutingContext context,
        ActionExecutionDelegate next)
    {
        // Do something before the action executes.

        // next() calls the action method.
        var resultContext = await next();
        // resultContext.Result is set.
        // Do something after the action executes.
    }
}

  除了直接實現Filter接口,Asp.Net Core同時提供了一些基於特性的過濾器,我們可以繼承相應的特性來實現自定義的過濾器。這些特性包括ActionFilterAttributeExceptionFilterAttributeResultFilterAttributeFormatFilterAttributeServiceFilterAttributeTypeFilterAttribute

  下面是微軟文檔的一個例子,在Result Filter給響應添加Header。

public class AddHeaderAttribute : ResultFilterAttribute
{
    private readonly string _name;
    private readonly string _value;

    public AddHeaderAttribute(string name, string value)
    {
        _name = name;
        _value = value;
    }

    public override void OnResultExecuting(ResultExecutingContext context)
    {
        context.HttpContext.Response.Headers.Add( _name, new string[] { _value });
        base.OnResultExecuting(context);
    }
}

[AddHeader("Author", "Joe Smith")]
public class SampleController : Controller
{
    public IActionResult Index()
    {
        return Content("Examine the headers using the F12 developer tools.");
    }

    [ShortCircuitingResourceFilter]
    public IActionResult SomeResource()
    {
        return Content("Successful access to resource - header is set.");
    }
}

 

三、Filter 的作用域和執行順序

  可以將我們自定義的filter添加到我們的代碼中進行調用,添加的方式有三種,對應其三個作用域:在Action上添加特性、在Controller上添加特性和添加全局filter。

  下面先來看下添加全局filter的做法,我們知道,在Asp.Net MVC中,filter都是在控制器實例化之后才能生效的,其Filter應該是確定的,不能被注入參數,但是到了Asp.Net Core,全局注冊是在ConfigureServices方法中完成的,它可以被注入參數。

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        // This lambda determines whether user consent for non-essential cookies is needed for a given request.
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });


    services.AddMvc(options =>
    {
        options.Filters.Add(new AddHeaderAttribute("name", "Jesen"));
        options.Filters.Add(typeof(AddLogActionAttribute));
        options.Filters.Add(typeof(ExceptionHandlerAttribute));
    }).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

  上述代碼在addMvc中將我們自定義的 filter 添加到全局當中,這樣對所有的控制器和action都產生了作用。而其他兩種作用域的用法就是直接將特性添加到其上面。

[AddHeader("", "")]
public class HomeController : Controller
{
    [AddLogAction]
    public IActionResult Index()
    {
        return View();
    }
}

  那么這三種作用域的默認執行順序是怎樣的,下圖很好的進行了展示。

  那么我們是否可以改變其默認執行順序呢,答案當然是肯定的。我們可以通過實現 IOrderFilter來重寫默認執行序列。 IOrderedFilter 公開了Order 屬性來確定執行順序,該屬性優先於作用域。 具有較低的 Order 值的 filter 在具有較高的 Order 值的filter之前運行 before 代碼,在具有較高的 Order 值的 filter 之后運行 after 代碼。具體做法可以在使用構造函數參數時設置Order 屬性。

[CustomFilter(Name = "Controller Level Attribute", Order=1)]

  在改變了Order屬性后的執行順序如下圖所示

 四、依賴注入

  在前面添加filter事,我們在全局添加方式中知道了 filter 可以通過類型添加,也可以通過實例添加,通過實例添加,則該實例會被應用於所有的請求,按照類型添加則將激活該類型,這意味着它會為每個請求創建一個實例,依賴注入將注入所有構造函數的依賴項。

  但是如果通過特性添加到Controller或者Action上時,該 filter 不能由依賴注入提供構造函數依賴項,這是因為特性在添加時必須提供它的構造函數參數,這是由於特性的原理決定的。那是不是通過Controller或Action的特性不能有構造函數參數呢,肯定不是的,可以通過以下特性來獲得依賴注入:ServiceFilterAttribute、TypeFilterAttribute和在特性上實現 IFilterFactory。

namespace FilterDemo.Filter
{
    public class AddLogActionAttribute : ActionFilterAttribute
    {
        private readonly ILogger _logger;

        public AddLogActionAttribute(ILoggerFactory loggerFactory)
        {
            this._logger = loggerFactory.CreateLogger<AddLogActionAttribute>();
        }

        public override void OnActionExecuting(ActionExecutingContext context)
        {
            string controllerName = (string)context.RouteData.Values["controller"];
            string actionName = (string)context.RouteData.Values["action"];

            this._logger.LogInformation($"{controllerName}的{actionName}開始執行了...")
            base.OnActionExecuting(context);
        }


        public override void OnActionExecuted(ActionExecutedContext context)
        {
            base.OnActionExecuted(context);
        }
    }
}

  上述代碼我們定義了帶Logger參數的AddLogActionAttribute Filter,接下來實現怎么在Controller或Action上使用,首先在Startup中添加注入

services.AddScoped<AddLogActionAttribute>();

  然后在Controller或Action上使用 ServiceFilter

[ServiceFilter(typeof(AddLogActionAttribute))]
public class HomeController : Controller

  TypeFilterAttribute與ServiceFilterAttribute類似,但不會直接從 DI 容器解析其類型。 它使用 Microsoft.Extensions.DependencyInjection.ObjectFactory 對類型進行實例化。因為不會直接從 DI 容器解析 TypeFilterAttribute 類型,所以使用 TypeFilterAttribute 引用的類型不需要注冊在 DI 容器中。

 [TypeFilter(typeof(AddLogActionAttribute))]
 public IActionResult Index()
 {
      return View();
 }

五、Resource Filter  

  最后,我們來看一下Asp.Net Core不同於之前Asp.Net MVC的 ResourceFilter,在上述介紹中,我們知道了Resource Filter在Authorization Filter執行之后執行,然后才會去實例化控制器,那么Resource Filter 就比較適合用來做緩存,接下來我們自定義一個Resource Filter。

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace FilterDemo.Filter
{
    public class CacheResourceFilterAttribute : Attribute, IResourceFilter
    {
        private static readonly Dictionary<string, object> _Cache = new Dictionary<string, object>();
        private string _cacheKey;
        /// <summary>
        /// 控制器實例化之前
        /// </summary>
        /// <param name="context"></param>
        public void OnResourceExecuting(ResourceExecutingContext context)
        {
            _cacheKey = context.HttpContext.Request.Path.ToString();
            if (_Cache.ContainsKey(_cacheKey))
            {
                var cachedValue = _Cache[_cacheKey] as ViewResult;
                if (cachedValue != null)
                {
                    context.Result = cachedValue; //設置該Result將是filter管道短路,阻止執行管道的其他階段
                }
            }
        }

        /// <summary>
        /// 把請求都處理完后執行
        /// </summary>
        /// <param name="context"></param>
        public void OnResourceExecuted(ResourceExecutedContext context)
        {
            if (!String.IsNullOrEmpty(_cacheKey) &&
                !_Cache.ContainsKey(_cacheKey))
            {
                var result = context.Result as ViewResult;
                if (result != null)
                {
                    _Cache.Add(_cacheKey, result);
                }
            }
        }
    }
}

  將其添加到HomeController的Index上

[CacheResourceFilter]
[TypeFilter(typeof(AddLogActionAttribute))]
public IActionResult Index()
{
    return View();
}

  運行可以發現第一次請求按照默認順序執行,第二次請求會在Cache中查找該請求路徑是否已經在Cache當中,存在則直接返回到Result,中斷了請求進入管道的其他階段。


免責聲明!

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



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