過濾器(Filters)
過濾器(Filters)向請求處理管道注入了額外的邏輯。他們提供了一種簡單而優雅的方式實作了橫切關注點,這個術語是針對整個應用程式使用的功能,并不能靈活的适用任何一個點,是以這個會打破分解關注點的模式。像日志,驗證和緩存都是經典的橫切關注點的例子。
之是以稱為過濾器(Filters),是因為這個術語同樣應用于其他web應用程式架構裡面,包括Ruby on Rails。然而,MVC架構裡面的過濾器完全不同于ASP.NET平台裡面的Request.Filters和Response.Filter對象,這兩個對象是實作請求和響應流的傳輸(一種進階的并很少發生的活動)。當然,我們能夠在MVC架構裡面使用這兩個對象,但通常我們談及過濾器是指MVC架構裡面的過濾器。本章裡面會介紹MVC架構所支援的不同的過濾器政策以及如何控制它們執行。
使用Filters
在前面的SportsStore項目裡面已經使用了過濾,就是将驗證應用到了Controller的action方法裡面,隻有驗證的使用者才能請求相應的action方法,這為我們提供了一種選擇的途徑。我們可以在每一個action方法裡面檢查請求的驗證狀态。如下所示:

View Code
通過上面的代碼,我們可以發現其實裡面有很多重複的地方,那使用Filters就能解決這個問題。如下代碼所示:

Filters是.NET裡面的特性(Attributes),這些是添加到請求處理管道額外的步驟。上面使用了Authorize過濾器實作同樣的效果,但是代碼顯然更加簡捷優雅。有四種基本的過濾器類型:Authorization, Action, Result, Exception對應接口為IAuthorizationFilter,IActionFilter,IResultFilter,IExceptionFilter。
在MVC架構調用一個Action之前,它會檢查方法的定義中是否有實作了上面接口的特性(Attributes).如果有的話,那麼在請求處理管道适當的位置,通過該接口定義的方法會被調用。MVC架構包含了預設已經實作了過濾器接口的Attributes類。
注:ActionFilterAttribute類實作了IActionFilter和IResultFilter接口,這個類是抽象類。其他的像AuthorizeAttribute和HandleErrorAttribute,包含了有用的功能,不需要派生出一個類就可以直接使用。
對Controllers和Action方法應用過濾器
Filters可以應用到單個的Action方法或者是整個Controller類,如下所示:

我們可以使用多個Filters,也可以混合使用Filters。如下所示:

有一些過濾器擷取幾個參數,後面會有探究其原理。
注:如果我們自定義了針對Controllers的基類,任何應用在基類的過濾器會對其所有的派生類起作用。
使用Authorization過濾器
Authorization過濾器是在其他的過濾器之前運作,并且也是在Action方法被調用之前。這個過濾器是用來授權認證的,確定隻有授權的使用者才能調用。看一下這樣一個場景,MVC架構接收一個來自浏覽器的請求,路由系統處理請求的URL并提取被命中的controller和action的名字。一個新的controller執行個體被建立,但是在action方法被調用之前,MVC架構會檢查是否有應用到該action方法上面的授權認證過濾器(authorization filters)。如果有,那麼定義在IAuthorizationFilter接口裡面的唯一的方法OnAuthorization會被調用。如果authentication filter準許了該請求,那麼請求管道裡面的下一步就會被執行,否則請求就會被拒絕。
建立一個授權認證過濾器(Creating an Authentication Filter)
下面是一個檢查使用者是否登入的過濾器,如下所示:

我們定義的過濾器需要一個數組參數,數組裡面包含了被授權認證的使用者,我們的過濾器裡面包含了PerformAuthenticationCheck方法,確定請求是被認證的并使用者也是在授權的集合裡面的。這個類裡面最有趣的部分是OnAuthorization方法的實作,傳遞給該方法的參數是AuthorizationContext類的執行個體,這個類是從ControllerContext派生。這讓我們可以通路一些有用的對象,這些對象包含:Controller,HttpContext,IsChildAction,RequestContext,RouteData。
回想下,當我們判斷請求是否通過驗證用到的方法:filterContext.HttpContext.Request.IsAuthenticated。使用Context對象,我們能擷取需要對請求做決斷的所有資訊。
AuthorizationContext定義了兩個額外的屬性:
1.ActionDescriptor(類型是ActionDescriptor)提供Action方法的詳情
2.Result(類型是ActionResult)Action方法的傳回結果,是一個通過設定這個屬性值為non-null來取消請求的過濾器
第一個屬性ActionDescriptor傳回的是System.Web.Mvc.ActionDescriptor類的執行個體,我們能夠用來擷取應用了過濾器的action方法的有關資訊。
第二個屬性Result,是讓過濾器工作的關鍵。當我們給一個針對action請求授權,然後我們在OnAuthorization裡面什麼都不做,這會讓MVC架構預設請求應該被處理。然而如果我們設定Result屬性為一個ActionResult對象,MVC架構會使用它作為所有請求的結果,在請求管道裡剩餘的步驟也不會執行,并且我們提供的結果會為使用者生成輸出。
我們的例子裡面,如果PerformAuthenticationCheck傳回false(表明請求沒有被驗證或使用者沒有被授權),那麼我們建立一個HttpUnauthorizedResult結果并賦給Result屬性,如:filterContext.Result = new HttpUnauthorizedResult();
使用我們自定義的過濾器如下:

使用内置的授權過濾器(Using the Built-in Authorization Filter)
MVC架構裡面包含了一個非常有用的内置的授權過濾器稱為AuthorizeAttribute。我們可以具體通過兩個屬性來設定驗證政策。
兩個公開的屬性:1.Users(類型String) 2.Roles(類型String)。示例代碼如下:

上面的過濾器表示隻有使用者是 adam, steve, 和bob的,并且使用者有管理者權限的才能請求該action方法。
對大多數應用程式,AuthorizeAttribute提供的驗證政策已經足夠使用。如果我們想實作比較特殊的過濾器,可以從這個類派生。相對于直接實作IAuthorizationFilter接口的方式,這種風險更低。AuthorizeAttribute類提供兩個可以自己現實的點。分别如下:
1.AuthorizeCore方法:被OnAuthorization的AuthorizeAttribute實作調用以及實作授權檢查
2.HandleUnauthorizedRequest方法:當授權檢查失敗時調用
注:我們能夠重寫的第三個方法是OnAuthorization,推薦不要這樣做。因為這個方法預設的實作包含了對内容緩存使用輸出緩存過濾器的安全地處理。後面會介紹
實作一個自定義授權政策
下面建立一個自己的AuthorizeAttribute子類。這個政策将要授權任何從伺服器桌面直接運作浏覽器(Request.IsLocal為true),或者遠端使用者的使用者名能夠比對normalAuthorizeAttribute的規則。這對于伺服器管理者繞開網站的登入過程非常有用。代碼實作如下:

可以這樣使用這個過濾器:[OrAuthorization(Users = "adam, steve, bob", Roles = "admin")]
實作一個自定義的授權失敗政策
預設的處理為通過授權的做法是重定向使用者到登入頁面。但是我不想一直這麼做,例如,如果我們使用AJAX,發送一個重定向能讓登入頁出現在目前頁面的中間(類似模态視窗這樣的效果)。這時我們可以重寫HandleUnauthorizedRequest方法來實作,如下所示:

使用異常過濾器(Using Exception Filters)
異常過濾器是在調用一個action方法抛出了無法處理的異常時運作。
建立一個異常過濾器(Creating an Exception Filter)
異常過濾器必須實作IExceptionFilter接口:public interface IExceptionFilter {void OnException(ExceptionContext filterContext);} OnException方法在異常抛出的時候調用。參數是ExceptionContext對象。下面是一個示例:

這個過濾器響應NullReferenceException異常的執行個體,并在沒有其他異常過濾器顯示異常被處理的情況下執行。我們重定向使用者到錯誤頁面。
使用内置的異常過濾器
HandleErrorAttribute就是内置的實作了IExceptionFilter接口的過濾器,并是建立異常過濾器變得容易。
當一個未處理的并指定了ExceptionType的異常發生時,過濾器會設定HTTP結果代碼為500(伺服器錯誤),并呈現在View屬性裡面指定的視圖,如下所示:
[HandleError(ExceptionType=typeof(NullReferenceException), View="SpecialError")]
注意:HandleErrorAttribute隻有在web.config檔案裡面添加了<customErrors mode="On" />才能工作。預設的為RemoteOnly,這意味着在開發過程中HandleErrorAttribute不會攔截異常,當部署到伺服器并從另外的計算機發送請求是才起作用。
當呈現一個視圖時,HandleErrorAttribute過濾器會傳遞一個HandleErrorInfo視圖模型對象,如下所示:

使用Action和Result過濾器
Action和Result過濾器是過多用途的過濾器,可用于任何意圖。為了建立這些過濾器類型的内置類,IActionFilter,實作了以上接口。如下:

接口定義了兩個方法,MVC架構在調用Action方法之前調用OnActionExecuting方法,OnActionExecuted方法則是在Action方法調用完成後被調用。
實作OnActionExecuting方法
OnActionExecuting方法在Action方法之前調用,是以,我們可以利用這個機會來檢查請求,取消請求,修改請求,或者啟用一些活動來跨越action方法。參數類型ActionExecutingContext是ControllerContext的子類并定義了兩個屬性:ActionDescriptor和Result。我們可以通過Result屬性選擇性的取消請求,如下所示:

上面的例子用來檢查請求是否使用了SSL,如果沒有則傳回404.
實作OnActionExecuted方法
我們也可以使用這個過濾器來執行一些跨越action方法執行的任務,下面的示例計算action方法執行的時間,如下:

實作結果過濾器(Implementing a Result Filter)
Action filters和Result filters有很多共同點。它要實作IResultFilter接口,如下:

一旦action方法傳回了一個action result,OnResultExecuting方法就會被調用,但這是在action result執行之前,OnResultExecuted方法是在action result執行之後。
示例如下:

使用内置的Action和Result過濾器類
MVC架構包含了能夠建立action和result過濾器的内置類,但是沒有提供任何有用的功能,這個ActionFilterAttribute類如下所示:

下面是一個從它派生的過濾器,如下:

使用其他過濾功能
除了上面介紹的過濾器以外,還有一些非常有趣但是用的并不那麼廣泛的過濾功能。如下:
不使用特性的過濾
使用過濾器正常的方式是建立和使用特性(attributes),然而有一種可以替代的方式。Controller類實作IAuthorizationFilter, IActionFilter, IResultFilter, IExceptionFilter這些接口,同時也提供了每一種OnXXX方法的虛拟實作。下面是計算action方法執行時間的實作,如下:

這種技術在我們建立一個基類,該基類從項目裡面多個控制器派生出來。過濾的重點就是把跨應用程式所需的代碼放在一個可重複使用的位置。對我們的項目而言,我們甯願使用特性。這樣能夠将控制邏輯和過濾邏輯分開。
使用全局過濾
全局過濾是應用到所有的action方法,通過如下的方式實作:
過濾執行排序
前面解釋了過濾器按類型被執行,順序是:authorization過濾器,action過濾器,result過濾器。如果在執行的任何一步出現未處理的異常,異常過濾器都會被執行。然而在每一個類型裡面,我們能夠控制個别的過濾器的順序。如下:

在action裡面使用如下:

運作結果如下:
如果我們要指定順序可以這樣:

運作程式如下:
如果我們不給order指定一個具體的值,預設會是-1。如果我們混淆了過濾器以至于一些有其他的值,一些沒有,這些沒有值的将會首先被執行。如果多個filters有同樣的值,那麼MVC架構根據它們被使用的地方來決定執行順序。全局過濾器最先執行,然後應用在controller類上面的,最後是action上面的過濾器執行。
使用内置的過濾器
MVC架構提供了一些内置的過濾器,以備我們在程式裡面直接使用。如下所示:
好了,本章的筆記到這就結束了,祝大家愉快!