[一步一步MVC]第一回:使用ActionSelector控制Action的選擇
系列文章導航:
[一步一步MVC]第一回:使用ActionSelector控制Action的選擇
[一步一步MVC]第二回:還是ActionFilter,實現對業務邏輯的統一Authorize處理
[一步一步MVC]第四回:漫談ActionLink,有時“胡攪蠻纏”
[一步一步MVC]第五回:讓TagBuilder豐富你的HtmlHelper
ActionFilter一定是MVC控制中對于Action控制中最值得研究的玩意,在項目實際中我們不可避免的使用例如:
- HandleError
- Authorized
- OutputCache
在本文中,我們應用Action Selector方式進行Action的選擇,想要闡述清楚這個問題,我們從實際的問題出發來關注。
實際的問題,從和老趙的對話了解
我們有個業務室這樣的:系統有不同的角色,例如Admin、Client、Agent。假設有個功能叫Book/List,那么就對應了一個List這樣的View和action為List這樣的BookController,現在,我們的情況是對于不同的角色,所對應的List是不同的。Admin看到的Book/List和Client看到的Book/List是不同的,那么通過Url:http://anytao.com/Book/List/123,不同的角色如何處理,差不多就這樣,是否清楚。
我:那么對于同一Action如何更好的return到不同的view?
老趙:具體問題是什么呢?
我: 我現在能想到的是在Action中根據角色Return到不同的View,簡單的辦法就是在List Action根據角色Return到不同的View。問題是,還有什么更好的辦法。
老趙:準備n各action,分別加上自定的ActionSelector,不要用一個Action,不用一個Action然后在里面if。
[OnlyInRole("admin")] [ActionName("List")] ListForAdmin() { } [OnlyInRole("user")] [ActionName("List")] ListForUser() { }
OnlyInRole需要自己寫,不過就幾行話
我: 哈哈,差不多了,謝啦。
根據老趙的指導,我對此思路進行了必要的探討,感受果然不同凡響,很好很暴力。
解決方案
為了實現對于Action進行Selector的具體實現,我選擇對ActionNameSelectorAttribute 進行擴展,參考ActionName的實現方式,對于按照RoleType進行過濾的需求顯然有很好的借鑒價值,以ActionNameAttribute為例,其具體實現為:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public sealed class ActionNameAttribute : ActionNameSelectorAttribute { public ActionNameAttribute(string name) { if (String.IsNullOrEmpty(name)) { throw new ArgumentException(MvcResources.Common_NullOrEmpty, "name"); } Name = name; } public string Name { get; private set; } public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo) { return String.Equals(actionName, Name, StringComparison.OrdinalIgnoreCase); } }
再來了解ActionNameSelectorAttribute抽象類的定義,其主要提供了對Action進行Select時的IsValidName判斷約定,例如ActionNameSelectorAttribute的定義:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public abstract class ActionNameSelectorAttribute : Attribute { public abstract bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo); }
所以,對ActionNameSelecterAttribute進行擴展變得異常簡單,下面是一個最簡單的實現,肯定讓我們耳目一新:
// Release : code01, 2009/04/17 // Author : Anytao, http://www.anytao.com public class ActionInRoleAttribute : ActionNameSelectorAttribute { public ActionInRoleAttribute(RoleType role) { this.role = role; } public override bool IsValidName(ControllerContext controllerContext, string actionName, System.Reflection.MethodInfo methodInfo) { if (controllerContext.HttpContext.User.IsInRole(role.ToString())) { return true; } else { return false; } } private RoleType role; }
我們的邏輯其實很簡單,借助于IPrincipal的IsInRole方法,可以很簡單的根據role的定義進行IsValidName過濾,例如我們的應該可以就是:
// Release : code02, 2009/04/17 // Author : Anytao, http://www.anytao.com [AcceptVerbs(HttpVerbs.Get)] [ActionName("List")] [ActionInRole(RoleType.Client)] public ActionResult ClientList(int id) { return View( "ClientBookList", new Book { ID = id, Name = string.Empty }); } // Release : code03, 2009/04/17 // Author : Anytao, http://www.anytao.com [AcceptVerbs(HttpVerbs.Get)] [ActionName("List")] [ActionInRole(RoleType.Admin)] public ActionResult AdminList(int id) { return View( "AdminBookList", new Book { ID = id, Name = string.Empty }); }
調用List Action時,根據登陸用戶的角色來決定具體執行的Action(ClientList或者AdminList),并由不同的Action導航到不同的View(ClientBookList或者AdminBookList),而對于不同Action訪問的URL都是一樣的(http://anytao.com/Book/List/123),同時避免了在服務層對角色的判斷,某種程度上按照RoleType對于Controller層進行了“注入”,使得Controller層的邏輯不在關心Action過濾的問題。
不過,在應用上還有一些值得注意的問題:
- 使用非泛型ActionLink方法調用應用ActionInRole 的Action
一般而言,我們提倡應用強類型ModelData在View層進行操作,那么泛型方法ActionLink值得推薦,
<p> <%= Html.ActionLink<BookController>(c => c.List(Model.ID), "List") %> </p>
然而,應用被ActionInRole標記的,同時被ActionName重命名的Action,將不被識別,我們只好以非泛型方式實現對于一名多用的Action來調用:
<p> <%= Html.ActionLink("List", "List", new { id=Model.ID }) %> </p>
- 在return View中通過制定ViewName進行返回,來選擇適合的View,例如
return View( "AdminBookList", new Book { ID = id, Name = string.Empty });
因為默認情況下,MVC引擎是以ActionName進行返回的,在我們的應用中必須以這種方式進行。
結論
本文著重于應用,而沒有特別對什么是ActionFilter進行探討,我們在合適的時間再次與MVC握手,對此進行進一步進行討論。