本次主要分享幾個場景的處理代碼,有更好處理方式多多交流,相互促進進步;代碼由來主要是這幾天使用前端Ace框架做後臺管理系統,這Ace是H5框架裡面的控制項效果挺多的,做相容也很好,有點遺憾是控制項效果基本都是寫一起的,分離起來挺麻煩的;這次主要說的是後端代碼,以後可以分享下這個框架的使用。 以上是個人的 ...
本次主要分享幾個場景的處理代碼,有更好處理方式多多交流,相互促進進步;代碼由來主要是這幾天使用前端Ace框架做後臺管理系統,這Ace是H5框架裡面的控制項效果挺多的,做相容也很好,有點遺憾是控制項效果基本都是寫一起的,分離起來挺麻煩的;這次主要說的是後端代碼,以後可以分享下這個框架的使用。
以上是個人的看法,下麵來正式分享今天的文章吧:
. 擴展HtmlHelper,枚舉轉化select下拉框效果
. 自定義ActionFilter,驗證登陸和許可權訪問
. 擴展HtmlHelper,無限遞歸生成菜單欄
. Global中增加全局Application_Error監控404異常
. 實現及使用緩存工廠(最新緩存工廠代碼在上一篇分享的緩存工廠之Redis緩存)
下麵一步一個腳印的來分享:
. 擴展HtmlHelper,枚舉轉化select下拉框效果
枚舉轉化select下拉框效果,這個效果估計很多同學都遇到過,也一定有自己的想法與實踐;因為這裡是MVC框架,所以這裡我直接擴展HtmlHelper,這樣在頁面使用起來也很方便;這裡瞭解一下這樣的場景,通常枚舉在方法中傳遞都只能是定義好的某一個枚舉,這樣來生成select標簽擴展性就不強;這個時候有朋友就想到如果使用enum來當做方法的參數呢,這樣是不行的,enum不能直接用來當做方法參數(看官們可以試試),所以這樣看就沒法定義一個公共的參數來傳遞不同枚舉了,當然萬能的Type給了我們一點曙光,下麵我們就使用Type當做參數來傳遞枚舉;
首先,我們既然要擴展HtmlHelper,那必須遵循一定的規則:
1.定義擴展類的類名通常使用Extension結尾,這裡咋們定義個名稱為HtmlHelperExtension的擴展類
2.擴展方法參數中使用this HtmlHelper html作為第一個參數
3.擴展方法返回MvcHtmlString把內容輸出到試圖View中
再來,自定義方法如:public static MvcHtmlString DrpDownByEnum(this HtmlHelper html, Type ty, string name = "Status", bool isAll = true);第二個參數就是上面說到的Type,她針對說有類型不僅僅局限於枚舉,因為這裡說的是使用枚舉所以這裡的職責就是負責枚舉類型的傳入,第三個參數是每個html標簽都應該具備的name屬性值,第四個參數是是否增加全選的選項;下麵再來分享下具體代碼:
1 /// <summary> 2 /// 根據枚舉獲取DrpDownList 3 /// </summary> 4 /// <param name="ty"></param> 5 /// <param name="name"></param> 6 /// <param name="isAll"></param> 7 /// <returns></returns> 8 public static MvcHtmlString DrpDownByEnum(this HtmlHelper html, Type ty, string name = "Status", bool isAll = true) 9 { 10 var sbHtml = new StringBuilder(string.Empty); 11 sbHtml.AppendFormat("<select class='form-control' name='{0}'>", name); 12 if (isAll) 13 { 14 15 sbHtml.AppendFormat("<option value='{0}'>{1}</option>", 16 "-1", 17 "==全部=="); 18 } 19 var vals = Enum.GetValues(ty); 20 for (int i = 0; i < vals.Length; i++) 21 { 22 var val = vals.GetValue(i); 23 var text = Enum.Parse(ty, val.ToString()).ToString(); 24 sbHtml.AppendFormat("<option value='{0}'>{1}</option>", 25 (int)val, 26 text); 27 } 28 sbHtml.Append("</select>"); 29 return MvcHtmlString.Create(sbHtml.ToString()); 30 }View Code
最後,定義一個枚舉名稱為ComStatus,然後在試圖View中@using引用咋們自定義的擴展方法命名空間,這樣咋們就能直接使用@html.DrpDownByEnum()我們定義的方法了,使用代碼如下:
@Html.DrpDownByEnum(typeof(StageEnumHelper.ComStatus))
. 自定義ActionFilter,驗證登陸和許可權訪問
首先,自定義個ActionFilter類名稱為CheckActionLoginAttribute並且繼承ActionFilterAttribute,重寫OnActionExecuting方法,先來看如下整個實現的代碼:
1 /// <summary> 2 /// action驗證登陸 + 菜單許可權 3 /// </summary> 4 public class CheckActionLoginAttribute : ActionFilterAttribute 5 { 6 7 private bool _IsAuthor = false; 8 9 /// <summary> 10 /// 是否驗證訪問許可權(預設需要) 11 /// </summary> 12 /// <param name="IsAuthor"></param> 13 public CheckActionLoginAttribute(bool IsAuthor = true) 14 { 15 _IsAuthor = IsAuthor; 16 } 17 18 public override void OnActionExecuting(ActionExecutingContext filterContext) 19 { 20 filterContext.Result = CheckLoginAndMenu(filterContext.ActionDescriptor, filterContext, _IsAuthor); 21 } 22 23 /// <summary> 24 /// 驗證登陸 + 菜單許可權 方法 25 /// </summary> 26 /// <param name="descript">Action文檔描述</param> 27 /// <param name="context">訪問上下文</param> 28 /// <param name="IsAuthor">是否需要驗證訪問許可權</param> 29 /// <returns></returns> 30 public static ActionResult CheckLoginAndMenu(ActionDescriptor descript, ControllerContext context, bool IsAuthor) 31 { 32 ActionResult result = null; 33 var returnUrl = context.HttpContext.Request.Path; 34 var session = CacheRepository.Current(CacheType.BaseCache).GetCache<StageModel.MoUserData>(); 35 if (session == null) 36 { 37 38 //跳轉登錄頁面 39 result = new RedirectResult( 40 string.Format("{0}?returnUrl={1}", 41 "/User/Login", 42 returnUrl 43 ) 44 ); 45 } 46 else if (IsAuthor) 47 { 48 //獲取請求的Action路徑 49 returnUrl = string.Format("/{0}/{1}", 50 descript.ControllerDescriptor.ControllerName, 51 descript.ActionName); 52 //驗證是否有訪問許可權 53 var isAllow = session.Menus.Any(b => b.Link.ToUpper().IndexOf(returnUrl.ToUpper()) == 0); 54 if (!isAllow) 55 { 56 //無訪問許可權 57 result = new RedirectResult( 58 string.Format("{0}", 59 "/Error" 60 ) 61 ); 62 } 63 } 64 return result; 65 } 66 }View Code
然後,分析代碼可以看到我們提取了一個方法
public static ActionResult CheckLoginAndMenu(ActionDescriptor descript, ControllerContext context, bool IsAuthor)
參數說明:ActionDescriptor:用來獲取路由請求的Action和Controller的名稱信息;ControllerContext:用來獲取當前請求的路由地址,方便後面登陸成功後直接跳轉到該次訪問的路由地址;bool參數:用來控制是否需要做菜單訪問許可權的驗證;
邏輯說明:
1.咋們做登陸驗證的時候使用緩存工廠CacheRepository獲取Session數據,如果為null不存在直接通過ActionResult返回跳轉登錄頁面結果,這個結果傳遞給OnActionExecuting重寫時的參數ActionExecutingContext的Result屬性,這樣就能驗證沒有登錄跳轉到登陸頁面去並且通過returnUrl參數傳遞給登陸頁面,登錄成功後跳轉此路由地址
2.由自定義構造函數傳入是否需要驗證菜單訪問許可權,這樣方便配置管理,有人會問為什麼不直接都驗證訪問許可權呢,這個看不通的需求吧;菜單許可權驗證主要是根據用戶訪問的路由地址Controller+Action與登陸用戶session保存的菜單許可權的路徑地址相互對比,以此來做菜單許可權驗證,這裡要獲取Action,Controller路由名稱主要是通過ActionExecutingContext.ActionDescriptor參數獲取到的;
最後,來看下CheckActionLogin過濾器怎麼調用:
/// <summary> /// 用戶中心 /// </summary> /// <returns></returns> [CheckActionLogin] public ActionResult UserCenter() { var userData = CacheRepository.Current(CacheType.BaseCache).GetCache<StageModel.MoUserData>(); return PartialView(userData); } /// <summary> /// 修改密碼 /// </summary> /// <returns></returns> [CheckActionLogin(false)] public ActionResult ChangeUser() { var userData = CacheRepository.Current(CacheType.BaseCache).GetCache<StageModel.MoUserData>(); return PartialView(userData); }
效果圖:
. 擴展HtmlHelper,無限遞歸生成菜單欄
首先,因為這裡和第一節點講述的內容都使用了HtmlHelper來擴展,擴展步奏就不多說了,主要來看遞歸的方法,遞歸名稱解釋就是自己可以調用自己,無限的深入層級,我們先來看整體方法:
1 /// <summary> 2 /// 獲取menu菜單html格式 3 /// </summary> 4 /// <param name="menusId">角色對應的許可權Id集合</param> 5 /// <param name="listMenu">第一次全部菜單數據</param> 6 /// <param name="parentName">父級菜單的名稱,多個層級關係名稱使用‘|’拼接</parentName> 7 /// <param name="ulClass"></param> 8 /// <returns></returns> 9 public static string GetMenuHtml(List<StageModel.MoMenu> menus, List<StageModel.MoMenu> listMenu = null, string parentName = "", string ulClass = "nav nav-list") 10 { 11 12 //全部菜單數據,第一次全部菜單數據 13 listMenu = listMenu ?? StageClass.GetAllMenus(); 14 if (listMenu == null || listMenu.Count <= 0 || menus.Count <= 0) { return ""; } 15 16 //獲取當前請求路徑 17 var currentPath = HttpContext.Current.Request.Path; 18 //html結構 19 var sbHtml = new StringBuilder(string.Empty); 20 21 sbHtml.AppendFormat("<ul class='submenu {0}'>", ulClass); 22 foreach (var item in listMenu) 23 { 24 25 //查詢是否有對應Id的菜單 26 var isExists = menus.Any(b => b.Id == item.Id); 27 var nowName = parentName + "|" + item.Name; 28 if (!isExists) 29 { 30 //不存在 31 if ((item.ListMenu == null || item.ListMenu.Count <= 0)) { continue; } 32 //還有子級,繼續遞歸 33 var currentNav = currentPath.Contains(item.Link) ? "nav-show" : "nav-hide"; 34 35 sbHtml.AppendFormat(@" 36 37 <li class=''> 38 <a href='#' data-url='{0}' class='dropdown-toggle'> 39 <i class='menu-icon fa {3}'></i> 40 <span class='menu-text'> {1} </span> 41 <b class='arrow fa fa-angle-down'></b> 42 </a> 43 <b class='arrow'></b> 44 {2} 45 </li> 46 ", 47 "", 48 item.Name, 49 GetMenuHtml(menus, item.ListMenu, nowName, currentNav), 50 string.IsNullOrEmpty(item.Icon) ? StageModel.MoIcon.Default : item.Icon); 51 } 52 else 53 { 54 55 //存在 56 //無子級,本級已經是最後一級 57 sbHtml.AppendFormat(@" 58 59 <li class=''> 60 <a href='#' data-url='{0}' data-menus='{3}' data-des='{4}'> 61 <i class='menu-icon fa {2}'></i> 62 <span class='menu-text'> {1} </span> 63 </a> 64 65 <b class='arrow'></b> 66 </li> 67 ", 68 item.Link, 69 item.Name, 70 string.IsNullOrEmpty(item.Icon) ? StageModel.MoIcon.Default : item.Icon, 71 nowName, 72 item.Des); 73 } 74 75 } 76 sbHtml.Append("</ul>"); 77 78 return sbHtml.ToString(); 79 }View Code
參數說明:
第一個List<StageModel.MoMenu> menus:這裡傳遞的是登陸用戶具有的菜單許可權集合
第二個List<StageModel.MoMenu> listMenu:本次迴圈的菜單集合(第一次進入方法的時候,這裡傳遞的是系統所有菜單的集合數據)
string parentName:父級菜單的名稱,多個層級關係名稱使用‘|’拼接,主要用處是在頁面點擊節點的時候可以直接獲取此節點的層級節點名稱,方便展示
string ulClass = "nav nav-list":css樣式控制參數(這裡使用的是Ace樣式)
方法體裡面的代碼,每個關鍵點和思路都有備註,大家可以查看下
最後,咋們來看一下效果截圖:
. Global中增加全局Application_Error監控404異常
首先,這裡Application_Error監控對於mvc就好增加了,直接在Global裡面增加代碼如:
/// <summary> /// 捕獲不到達Action的錯誤 /// </summary> protected void Application_Error() { var lastError = Server.GetLastError(); if (lastError != null) { var httpError = lastError as HttpException; if (httpError != null) { switch (httpError.GetHttpCode()) { case 404: Response.Redirect("/Error"); break; } } } }
這樣就能獲取出用戶訪問不存在路由時候提示的404code錯誤,然後跳轉到自定義路由Error中去,我們這裡測試訪問一個我這裡存在的地址:http://localhost:5050/home 和不存在的地址:http://localhost:5050/home1,後者會自動跳轉到我定義的Error路由對應的試圖中去,效果大家可以自行體驗;
上面是全局的404錯誤,那麼Action出錯怎麼獲取信息呢,我們可以重寫HandleErrorAttribute中的void OnException(ExceptionContext filterContext)方法,這裡直接給出具體代碼:
/// <summary> /// 捕獲Action錯誤 /// </summary> public class ExceptionPageAttribute : HandleErrorAttribute { public override void OnException(ExceptionContext filterContext) { var code = 505; var lastError = filterContext.Exception; if (lastError != null) { var httpError = lastError as HttpException; if (httpError != null) { code = httpError.GetHttpCode(); } } filterContext.Result = new RedirectResult("/Error?code=" + code); } }
自定義完後,我們需要在項目根目錄下的App_Start/FilterConfig.cs文件中的void RegisterGlobalFilters(GlobalFilterCollection filters)方法中添加我們的自定義錯誤攔截器,這樣如果Action或者Controller提示異常的時候會自動進入自定義ExceptionPage攔截器方法中,跳轉到我們的Error路由視圖中去;
. 實現及使用緩存工廠(最新緩存工廠代碼在上一篇分享的緩存工廠之Redis緩存)
在做後臺系統的時候用到了前面封裝的緩存工廠,這裡主要是拿過來修改了部分信息,最新代碼已經更新到上篇緩存工廠之Redis緩存文章中,如果有需要的朋友可以點擊鏈接;
本次的分享就到這裡了,不知不覺凌晨了,該說睡覺的時候了,謝謝各位觀賞,歡迎多多點贊。