ASP.NETCore MVC中過濾器的使用和總結

 

 

本篇文章介紹過濾器以下幾點知識點

1、什麼是過濾器

2、過濾器的執行流程

3、過濾器的作用域

4、過濾器的工作原理

5、過濾器的5種類型

6、取消和短路

7、過濾器的注入寫法

8、同種過濾器自定義順序

1、什麼是過濾器?

.NET 中的過濾器(Filter)是 AOP(面向切面編程) 思想的一種實現,供我們在執行管道的特定階段執行代碼,通過使用過濾器可以實現 短路請求、緩存請求結果、日誌統一記錄、參數合法性驗證、異常統一處理、返回值格式化 等等,同時使業務代碼更加簡潔單純,避免很多重複代碼。

2、MVC中過濾器執行流程

 

 

 通過這個流程呢,我們可以看到它的執行順序

從網絡請求進來開始,先執行的是其他中間件(含自定義中間件,後面我們再另外寫篇關於中間件應用的)---》路由中間件(MVC自帶,路由選擇)--》控制器中的Action--》然後纔會執行過濾器。

反向的,當我們action中執行完業務代碼後,請求一層層原路返回。

所以在我們的過濾器中,大部分過濾器有開始執行action,即ing 狀態的方法,也有action業務代碼執行完後觸發的ed狀態的方法。後面看例子就明白了

3、過濾器作用域

過濾器作用域設置是非常靈活的,可以選擇爲:

  • 全局有效(整個 MVC 應用程序下的每一個 Action);
  • 僅對某些 Controller 有效 (控制器內所有的 Action );
  • 僅對某些 Action 有效;

4、過濾器的工作原理

過濾器一般在 Asp.Net Core MVC 管道內運行,一般在操作執行之前(befor) 或者執行之後(after) 執行,以供開發者可以選擇在不同的執行階段介入處理。

5、過濾器的類型

MVC中默認定義的有以下5中類型的過濾器

授權過濾器 AuthorizeAttribute

資源過濾器 IResourceFilter

異常過濾器 IExceptionFilter

操作過濾器 ActionFilterAttribute

結果過濾器 IResultFilter

5中過濾器的對比如下圖:

 

 

 同時,表中的過濾器從上到下,基本也是這5種過濾器的執行順序。

我們可以同時定義多個甚至是5個類型的過濾器,不同類型的過濾器之間也存在這執行順序的先後。

 

 

 

上圖,就是5種過濾器在一個完整請求到響應的執行個過程。

通過順序,我們可以看到它的執行先後(同一個過濾器,箭頭有出有入的,表示有執行前(ing)和執行後(ed)兩種狀態的方法)

請求--》中間件(Middleware)--》授權過濾器--》資源過濾器ing--》Action 操作過濾器ing--》執行Action中的業務代碼--》業務代碼執行完,執行Action操作過濾器ed--》執行Result 結果過濾器--》執行資源過濾器ed方法--》返回中間件--》響應請求。

這是一個完整的路徑。

還有另一條路徑,差別在於當在Action中拋出異常時,不再執行Action 操作過濾器ed方法,而是執行異常過濾器(只有一個方法)--》執行資源過濾器ed方法--返回中間件--》響應請求

也就是當拋異常時,就不會執行action的ed方法和result過濾器了。

下面我們介紹5中過濾器的具體代碼應用

MVC中過濾器都在命名空間:Microsoft.AspNetCore.Mvc.Filters 當中

所有過濾器我們都同時繼承Attribute,方便我們把它當做特性來使用。

A:授權過濾器

授權過濾器,主要用於權限驗證,驗證當前是否登錄,是否有權限訪問某個資源。

授權過濾器在過濾器管道中第一個被執行,通常用於驗證請求的合法性(通過實現接口 IAuthorizationFilter or IAsyncAuthorizationFilter) 在請求到達的時候最先執行,優先級最高,

主要作用是提供用戶請求權限過濾,對不滿足權限的用戶,可以在過濾器內執行拒絕操作,俗稱“管道短路”。

*注意:該過濾器只有執行之前(befor),沒有執行之後(after)的方法 通常情況下,不需要自行編寫過濾器,因爲該過濾器在 Asp.Net Core 內部已經有了默認實現,我們需要做的就是配置授權策略或者實現自己的授權策略,然後由系統內置的授權過濾器調用授權策略即可 必須將該過濾器內部可能出現的異常全部處理,因爲在授權過濾器之前,沒有任何組件能夠捕獲授權過濾器的異常,一旦授權管理器內部發生異常,該異常將直接輸出到結果中。

我們新建一個MVC項目,新建一個Filter 文件夾,存放我們定義的過濾器類。

項目環境,NETCore 3.1

定義一個授權過濾器,我們需要繼承 IAuthorizationFilter

    public class MyAuthorizationFilter :Attribute,IAuthorizationFilter
    {
        public void OnAuthorization(AuthorizationFilterContext context)
        {
            var reqeust = context.HttpContext.Request;
            var userId = reqeust.Cookies["userId"];
            if(string.IsNullOrWhiteSpace(userId))
            {
                throw new Exception("授權過濾器,您還沒有登錄");
            }
           
        }
    }

授權過濾器只有一個方法,通過context對象我們可以獲取當前請求的上下文對象,獲取我們所需要的參數。

示例中,我們直接拋出異常,僅是爲了方便,在實際項目中,我們還是需要友好的處理,此部分內容在下面的過濾器短路中介紹。

B:資源過濾器

資源過濾器在過濾器管道中第二個被執行,通常用於請求結果的緩存和短路過濾器管道(通過實現接口 IResourceFilter or IAsyncResourceFilter)

資源過濾器在實現緩存或短路過濾器管道尤其有用。

    //我們以緩存爲例
    public class MyResourceFilter : Attribute, IResourceFilter
    {
        private readonly IMemoryCache _memoryCache;

        public MyResourceFilter(IMemoryCache memoryCache)
        {
            _memoryCache = memoryCache;
        }
        public void OnResourceExecuted(ResourceExecutedContext context)
        {
            //我們可以將當前的結果context.Result緩存起來,當執行ing時,直接返回,爲了方便示例演示,我們用時間表示。


            string content = "第一次的時間:" + DateTime.Now;
            _memoryCache.Set("key", content); 
           
        }

        public void OnResourceExecuting(ResourceExecutingContext context)
        {
            var content = _memoryCache.Get("key"); //判斷內存中是否有內容,有就直接返回,不再執行action過程。
            if (content != null)
            {
                context.Result = new ContentResult() { Content= content.ToString()};
            }
        }
    }

在實際的項目中,資源過濾器我們比較少會用到。

一般緩存我們都在action中用redis來實現。那在資源過濾器中做資源緩存,有啥好處呢?
節省資源開支,提高性能。

因爲resourceing後面的就不用再執行了,通過context.Result直接返回,把整個請求短路了,後面的過程就不再執行了。

C:操作過濾器

Action 過濾器 要麼實現 IActionFilter 接口,要麼實現 IAsyncActionFilter 接口,它們可以在 action 方法執行的前後被執行。

Action 過濾器非常適合放置諸如查看模型綁定結果、或是修改控制器或輸入到 action 方法的邏輯。

另外,action 過濾器可以查看並直接修改 action 方法的結果,即,可以對輸出結果進行修改。

Action過濾器有兩個方法,一個是action執行之前,一個是之後。他的應用 場景也很多。

比如我們寫請求日誌吧,一般我們需要記錄時間、請求的客戶端信息、請求的路由,當前控制器名稱、action名稱等。那就在action中實現吧。

    public class MyActionFilter : Attribute, IActionFilter
    {
        public void OnActionExecuted(ActionExecutedContext context)
        {
            //有時我們視圖頁上需要顯示當前登錄賬號的名稱,我們可以通過下面代碼獲取,設置到ViewData對象中,視圖中直接使用ViewData即可顯示,替代傳統的定義基類,每個控制器都要繼承基類的寫法。這也就是AOP啊
            var controller = context.Controller as Controller;
            controller.ViewData["name"] = "張三";
           
        }

        public void OnActionExecuting(ActionExecutingContext context)
        {
            //下面是演示怎麼獲取路由信息,context 對象裏面還有更多驚喜,慢慢發掘吧
            var ad = context.ActionDescriptor;
           //var ar= context.ActionArguments;//請求的參數信息
           var str= ad.RouteValues["controller"] + "/" + ad.RouteValues["action"];

            //爲了方便演示,這邊直接短路,輸出路由名稱和aciton名稱
           // context.Result = new ContentResult() { Content=str};
        }
    }

D:結果過濾器

結果過濾器適用於任何需要直接環繞 View 或格式化處理的邏輯。結果過濾器可以替換或更改 Action 結果(而後者負責產生響應)

一般項目中很少用,這邊不多介紹。

E:異常過濾器

用於實現常見的錯誤處理策略,沒有之前和之後事件,處理 Razor 頁面或控制器創建、模型綁定、操作過濾器或操作方法中發生的未經處理的異常。 但無法捕獲資源過濾器、結果過濾器或 MVC 結果執行中發生的異常 。

這裏很重要的一點就是,異常過濾器它步驟的異常範圍有限,在中間件、或者授權、資源、結果過濾器中產生的異常,是步驟不到的。

也可以簡單的理解爲,它能捕捉的基本就是業務異常。

    public class MyExceptionFilter : Attribute, IExceptionFilter
    {
        public void OnException(ExceptionContext context)
        {
            var ex = context.Exception;
            //這裏可以寫入異常日誌
        }
    }

 

6、取消和短路

這裏的取消和短路是同一個意思,不同表達而已。

在過濾器中,我們可以做一些處理,但有時候,我們不希望請求繼續往下走。

比如在授權過濾器中,我們既然知道了該用戶未登錄,是不能做任何處理的,那我們就可以給他短路,直接返回。這樣可以提高我們的代碼性能。

短路過濾器,有兩種方式,其實在上面的代碼示例中,都有用到了

第一種,就是直接拋出異常。但是這種很不友好。

如:

 public void OnAuthorization(AuthorizationFilterContext context)
        {
            var reqeust = context.HttpContext.Request;
            var userId = reqeust.Cookies["userId"];
            if(string.IsNullOrWhiteSpace(userId))
            {
                throw new Exception("授權過濾器,您還沒有登錄");
            }
           
        }

 

第二種就是使用context.Result直接返回結果。

如:

  public void OnResourceExecuting(ResourceExecutingContext context)
        {
            var content = _memoryCache.Get("key"); //判斷內存中是否有內容,有就直接返回,不再執行action過程。
            if (content != null)
            {
                context.Result =  new ContentResult() { Content= content.ToString()};
            }
        }

 

這裏的Result是指MVC中自帶的幾種視圖的Result類型,包含如下圖的類型

 

 

7、過濾器的注入方式 

過濾器應該說有兩種注入方式

一種是針對Controller控制器或者Action行爲,直接加特性。表示控制範圍爲該控制器或者該Action

一種是全局的,直接在stratup.cs中全局注入。

第一種,就需要我們上面說的,過濾器要繼承Attribute 特性,這樣我們纔可以當做特性使用。

比如

    [MyResultFilter]
        public IActionResult Privacy()
        {
            return View();
        }

第二種呢,直接上代碼

   public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews(options=> {
                options.Filters.Add<Demo3.Filter.MyActionFilter>();
            });
        }

當時在實際使用中,我們會碰到一些情況,就是過濾器中帶有帶參的構造參數。

這時候的參數,就需要由注入端進行寫入,通過DI依賴注入的方式獲取。

我們定義一個Student學生類爲例,然後將這個類注入。

  public class Student
    {
        public string Name { get; set; }
    }
  services.AddTransient<Student>();
    public class MyResultFilter : Attribute, IResultFilter
    {
        private readonly Student _stu;
        /// <summary>
        /// 帶參構造函數,實現對name的賦值
        /// </summary>
        /// <param name="name"></param>
        public MyResultFilter(Student stu)
        {
            _stu = stu;
        }
        public void OnResultExecuted(ResultExecutedContext context)
        {
        }

        public void OnResultExecuting(ResultExecutingContext context)
        {
            context.Result = new ContentResult() { Content = _stu.Name };
        }
    }

這時候,我們想在action上寫MyResultFilter ,按上面的方式就不行了

也不能如下這麼寫

 

 

因爲這個變量可能在其他業務邏輯中獲取的

那這時,我們需要這麼寫

  [TypeFilter(typeof(MyResultFilter))]
        public IActionResult Privacy()
        {

            return View();
        }

後者全局,這麼寫

 services.AddControllersWithViews(options=> {
                options.Filters.Add(typeof(Demo3.Filter.MyResultFilter));
            });

8、同種過濾器的自定義順序

上面我們講到了,不同過濾器之間,有執行先後順序,這個是MVC自帶的

但是對於我們自定義的過濾器,也存在執行先後順序的問題

比如我們引用了某個第三方組件,裏面有一個action 過濾器了,這時候我們想優先執行我們自己定義的過濾器,或者都是我們自定義的過濾器,要有先後順序,應該怎麼寫呢?

其實也很簡單,定義過濾器時,繼承 IOrderedFilter

  public class MyResultFilter : Attribute, IResultFilter,IOrderedFilter
    {
        private readonly Student _stu;
        /// <summary>
        /// 帶參構造函數,實現對name的賦值
        /// </summary>
        /// <param name="name"></param>
        public MyResultFilter(Student stu)
        {
            _stu = stu;
        }

        public int Order { get; set; }

        public void OnResultExecuted(ResultExecutedContext context)
        {
        }

        public void OnResultExecuting(ResultExecutingContext context)
        {
            context.Result = new ContentResult() { Content = _stu.Name };
        }
    }

在控制器或視圖中我們可以這麼寫

        [TypeFilter(typeof(MyResultFilter),Order =1)]
        public IActionResult Privacy()
        {

            return View();
        }
services.AddControllersWithViews(options=> {
                options.Filters.Add(typeof(Demo3.Filter.MyResultFilter),1);
            });

 

這樣就可以實現了過濾器的順序配置。

 

以上就是介紹的全部內容。

 

其他類似文章,這篇還不錯:https://blog.csdn.net/cplvfx/article/details/118118104  

更多分享,請大家關注我的個人公衆號:

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章