999
技術社區[雲棲]
包含在ASP.NET MVC中的過濾器
在深入研究如何編寫過濾器之前,首先看看包含在ASP.NET MVC中的過濾器。
ASP.NET MVC包括了如下3種即開即用的動作過濾器:
Authorize:該過濾器用於限製對控製器或控製器動作的訪問。
HandleError:該過濾器用來指定一個處理異常的動作,這個異常是從動作方法的內部拋出的。
OutputCache:該過濾器用來為動作方法提供輸出的緩存。
接下來將依次深入討論這3個過濾器。
1 Authorize
AuthorizeAttribute是包含在ASP.NET MVC中默認的授權過濾器。可以使用它來限製對動作方法的訪問。將該特性運用到控製器上可以迅速將其運用到每個動作方法中。
在運用該過濾器時需要牢記如下內容:
在運用該特性時,可以指定一個逗號來劃分角色(Role)或用戶(User)的列表。如果指定了一個角色的列表,那麼為了動作方法的執行,用戶必須是其中一個角色中的成員。同樣的,如果指定了一個用戶的列表,那麼當前用戶的名稱必須在該列表中。
為什麼不使用已有的、構建在ASP.NET中的URL授權
保護使用了Web Forms的應用程序的常見方式是使用URL授權。例如,如果具有一個管理部分,而且希望將其限製為那些位於管理員(Admins)角色中的用戶,那麼可能會把所有管理頁麵都放在管理文件夾中並拒絕除位於管理員角色中以外的任何人訪問子文件夾。
對於MVC而言,這種方法不會很好地運轉,原因有兩個:
請求不再映射到物理目錄中。
進入到同一個控製器的路由可能不止一個。
對於MVC而言,理論上講,是可以使用AdminController封裝應用程序的管理功能,然後在web.config根文件中設置URL授權,進而阻止訪問任何以/Admin開頭的請求。然而,這並不是萬無一失的,也有可能存在另外一個路由,它可能在不經意間映射到AdminController中。
例如,後麵將會討論的,如果決定切換默認路由中的{controller}和{action}的順序,那麼此時,/Index/Admin是默認的管理頁麵的URL,而且URL授權也不再阻止它。
安全性較好的一個方法是總是盡可能緊緊地將安全檢查放到需要保護的對象上。雖然可能會在更高層次的堆棧上具有其他檢查,但是最終希望保護的是實際的資源。在這種情況下,不希望依靠路由選擇和URL授權來保護控製器;真正想要保護的是控製器自身。AuthorizeAttribute正好用於此用途。
如果沒有指定任何角色或用戶,那麼為了調用動作方法,必須隻驗證當前的用戶。這是阻止非驗證用戶訪問特殊控製器動作的一個簡單的方法。
如果用戶試圖訪問運用了該特性的動作方法且在授權檢查中失敗,那麼過濾器將引發服務器返回一個401 Unauthorized的HTTP狀態代碼。
對於啟用了表單驗證且在web.config中指定了注冊URL的情形,ASP.NET將處理該響應代碼並將用戶重新引向注冊頁麵。這是ASP.NET已有的行為,對於ASP.NET MVC也不是新鮮事物。
產品小組的話:
一開始,我們將PrincipalPermissionAttribute看作是保護控製器動作的一個可行的解決方案,但是碰到了很多問題。首先,在將其運用到類時,PrincipalPermissionAttribute將在安全檢查失敗而試圖實例化Controller類時導致一個異常。因為我們希望當安全檢查失敗時可能會有其他過濾器運行(例如,日誌記錄過濾器),所以這並不是我們想要的行為。
其次,我們希望能控製產生的狀態代碼。而PrincipalPermissionAttribute隻是拋出一個SecurityException。
下麵看一個使用的簡單示例。在下麵的代碼中,管理控製器隻限製於Admins和SuperAdmins角色的成員。注意,角色是通過逗號隔開的。過濾器將忽略逗號之間的空白以允許提高在運用該特性時的可讀性。
- [Authorize(Roles="Admins, SuperAdmins")]
- public class AdminController
- {
- //Only admins should see this.
- public ActionResult Index()
- {
- return View();
- }
- //Only admins should see this.
- public ActionResult DeleteAllUsers()
- {
- //Thankfully, this is secured by the Authorize attribute.
- }
- }
在認真思考上述示例之後,本書作者認識到我們並不希望任何管理員或係統管理員(superadmin)能夠調用DeleteAllUsers的動作。事實上,我們隻信任由Phil完成該動作,所以這裏運用了一個更為具體的授權,即隻允許用戶Phil來調用該動作。當運用了多個授權過濾器時,用戶為了調用動作必須滿足所有的授權過濾器。因此,在這種情況下,Phil必須同時是管理員(Admins)角色的成員也是係統管理員(SuperAdmins)角色的成員。
注意:
另一件需要注意的事情是,一般來說:甚至在類似情況下,使用角色而不是具體的用戶名將更有意義。更好的方法可能是創建一個名為CanDeleteAllUsers的角色並將Phil添加到該角色中,然後運用一個指定了該角色的授權過濾器。
- [Authorize(Roles="Admins, SuperAdmins")]
- public class AdminController
- {
- //Only admins should see this.
- public ActionResult Index()
- {
- return View();
- }
- //Only Phil should do this.
- [Authorize(Users="Phil")]
- public ActionResult DeleteAllUsers()
- {
- //…
- }
- }
在最後的示例中,切換到一個為個人用戶提供用戶管理的控製器中;不需要指定用戶和角色。在這種情況下,控製器可能向所有通過驗證的人打開大門。
- [Authorize]
- public class UsersController
- {
- public ActionResult ManageProfile()
- {
- //…
- return View();
- }
- }
OutputCacheAttribute用來緩存動作方法的輸出。該特性是到ASP.NET輸出緩存功能內的連接,而且提供了在使用@OutputCache頁麵指令時得到的大部分相同的API和行為。本章後麵將討論兩者之間微小的區別。
因為輸出緩存是ASP.NET非常著名的功能,所以本節沒有深入討論緩存行為自身的細節,而是將注意力集中於MVC實現上。此時,可能會有一個問題,"為什麼不在視圖中隻使用@OutputCache指令呢?"
對於MVC而言,首先在選中視圖之前,執行控製器動作。因此,將輸出緩存放到視圖上,這實際上將緩存視圖的輸出,而動作方法自身將仍然在每個請求上執行,這樣就否定了緩存輸出的大部分優點。
通過將該特性運用到動作方法中,過濾器隨後可以確定緩存是否有效並跳過動作方法的調用,直接呈現緩存的內容。
API
表8-1列出了OutputCacheAttribute類的屬性。這些設置用來控製OutputCache過濾器如何執行其緩存動作。
表8-1 OutputCacheAttribute類的屬性
屬 性 |
說 明 |
CacheProfile |
即將使用的緩存設置的名稱。它允許將緩 存的配置放到web.config文件中而不是 特性中。隨後,該特性可以通過該屬性 來引用配置設置 |
Duration |
指定了將輸出存儲到緩存中的秒數 |
Location |
指定了可能緩存內容的地方。枚舉 OutputCacheLocation包含了允許的位置: Any、Client、Downstream、Server、 None、ServerAndClient等 |
NoStore |
將HTTP題頭“Cache-Control: Private, no-store”設置為阻止瀏覽器緩存響應。 它等價於調用Response.Cache.SetNoStore |
(續表)
屬 性 |
說 明 |
SqlDependency |
特殊格式化的字符串值,包含了一組 輸出緩存所依賴的數據庫和表的名稱 對。當這些表中的數據發生了更改, 則緩存失效 |
VaryByContentEncoding |
在ASP.NET 3.5中引入,這是以逗號 分隔的內容編碼的列表,用於變更緩存 |
VaryByCustom |
確定是否基於對Global.asax.cs文件中 的GetVaryByCustomString的調用緩存 新版本的輸出。這為開發人員提供了 在緩存時完全的控製 |
VaryByHeader |
基於http題頭改變緩存。例如,可能使 用它基於Accept-Language題頭緩存 不同版本的輸出 |
VaryByParam |
用於指定哪一個QueryString參數導 致了一個新版本的輸出被緩存 |
與@OutputCache指令之間的區別
由於應用程序開發的ASP.NET MVC模型與Web Forms模型之間的區別,所以還有一個選項不會翻譯成輸出緩存的過濾器,即VaryByControl屬性。因此,該屬性在OutputCacheAttribute中是沒有的。
使用示例
下麵將討論輸出緩存過濾器常見的使用模式。在很多情況下,可能希望僅緩存動作的輸出一段很短的時間。在下麵的代碼示例中,緩存了默認的About動作的輸出,持續時間為60秒。修改方法的實現以顯示時間:
- [OutputCache(Duration=60, VaryByParam="none")]
- public ActionResult About()
- {
- ViewData["Title"] = "This was cached at " + DateTime.Now;
- return View();
- }
在考慮前麵的示例時,我們希望不必每次修改持續時間時就重新編譯代碼。相反,可能希望在配置文件中設置持續時間。
幸運的是,在web.config中已經存在一個輸出緩存的設置部分。下麵的樣本闡述了如何將緩存設置添加到web.config中:
- <system.web>
- <caching>
- <outputCacheSettings>
- <outputCacheProfiles>
- <add name="MyProfile" duration="60" varyByParam="none" />
- </outputCacheProfiles>
- </outputCacheSettings>
- </caching>
- </system.web>
注意,添加了一個名為MyProfile的緩存配置文件,現在可以修改輸出緩存過濾器來通過CacheProfile屬性讀取該配置文件的設置:
- [OutputCache(CachePro)]
- public ActionResult About()
- {
- ViewData["Title"] = "This was cached at " + DateTime.Now;
- return View();
- }
HandleErrorAttribute是包含在ASP.NET MVC中的默認異常過濾器。如果動作方法拋出一個未處理的異常,且該異常與指定的異常類型匹配或源自它,那麼就可以使用該過濾器指定需要處理的異常類型以及需要顯示的視圖(如果需要的話,還包括主視圖)。
默認情況下,如果沒有指定異常類型,那麼過濾器將處理所有的異常。如果沒有指定視圖,那麼過濾器將默認處理名為Error的視圖。默認的ASP.NET MVC項目在Shared文件夾中包含了一個名為Error.aspx的視圖。
請看下麵的示例:
- [HandleError(ExceptionType = typeof (ArgumentException), View="ArgError")]
- public ActionResult GetProduct(string name)
- {
- if(name == null)
- {
- throw new ArgumentNullException("name");
- }
- return View();
- }
因為ArgumentNullException繼承自ArgumentException,所以將null傳遞到該動作方法中將導致顯示ArgError視圖。
在一些情況下,可能希望將多個異常過濾器運用到同一個動作方法中。那麼,為了能將最具體的異常類型放在最前麵而將較不明確的放在後麵,就需要指定這些情況的順序,這是很重要的。例如,在下麵的代碼片斷中:
- //This is WRONG!
- [HandleError(Order=1, ExceptionType=typeof(Exception))
- [HandleError(Order=2, ExceptionType=typeof(ArgumentException),
- View="ArgError")]
- public ActionResult GetProduct(string name)
- {
- …
- }
第一個過濾器要比其後的過濾器更為通用,它將處理所有的異常,而始終不會給第二個過濾器任何機會來處理異常。為了修複此問題,隻需將過濾器從最具體到最不具體進行排序。
- //This is BETTER!
- [HandleError(Order=1, ExceptionType=typeof(ArgumentException),
- View="ArgError")
- [HandleError(Order=2, ExceptionType=typeof(Exception)]
- public ActionResult GetProduct(string name)
- {
- …
- }
當該異常過濾器處理一個異常時,它創建了一個HandleErrorInfo類的實例並設置在呈現Error視圖時ViewDataDictionary實例的Model屬性。
表8-2展示了HandleErrorInfo類的屬性:
表8-2 HandleErrorInfo類的屬性
屬 性 |
說 明 |
Action |
拋出異常的動作的名稱 |
Controller |
在其中拋出異常的控製器的名稱 |
Exception |
拋出的異常 |
注意,該過濾器沒有捕捉到在沒有啟用自定義錯誤時在調試構建中的異常。過濾器隻是檢查HttpContext.IsCustomErrorEnabled以確定是否處理該異常。這樣做的理由是允許通過信息更豐富的Yellow Screen of Death來顯示開發階段與異常有關的信息。
最後更新:2017-04-04 07:03:03