在團隊設計BrnShop的web項目之初,咱們碰到了兩個問題,第一個是數據的複用和傳遞,第二個是大mvc框架和小mvc框架的選擇。下面我依次來講明下。javascript
首先是數據的複用和傳遞:對於BrnShop的每一次請求,程序都要分紅好幾個階段執行,例如驗證,執行動做方法等等,在各個階段咱們可能須要重複使用同一信息,而咱們的願景就是但願此信息只需獲取一次,而後沿着流程管道一直流動,這樣在後面的階段中就能夠直接使用,不用再從新獲取了,提升程序的性能。舉例來講:在受權驗證階段,咱們爲對用戶進行驗證,從而獲取了用戶信息,當驗證結束後,此用戶信息並不被拋棄,而是保留下來,這樣在後面的動做方法中咱們就不須要再次獲取用戶信息,而是直接使用剛纔在受權中保留下來的用戶信息就能夠了。css
具體實現是這樣的:首先咱們給這些須要公用的數據定義個上下文類,它們分別是BrnShop.Web.Framework項目中的WebWorkContext類和AdminWorkContext類,其中WebWorkContext是前臺項目使用的上下文,AdminWorkContext是後臺項目使用的上下文。代碼很簡單,就是定義了一些公共字段,具體以下:html
using System; using System.Collections.Generic; using BrnShop.Core; namespace BrnShop.Web.Framework { /// <summary> /// 商城前臺工做上下文類 /// </summary> public class WebWorkContext { public ShopConfigInfo ShopConfig = BSPConfig.ShopConfig;//商城配置信息 public bool IsHttpAjax;//當前請求是否爲ajax請求 public string IP;//用戶ip public RegionInfo Region;//區域信息 public string Url;//當前url public string UrlReferrer;//上一次訪問的url public string Sid;//用戶sid public int Uid = -1;//用戶id public string UserName;//用戶名 public string UserEmail;//用戶郵箱 public string UserMobile;//用戶手機號 public string NickName;//用戶暱稱 public string Avatar;//用戶頭像 public string Password;//用戶密碼 public string PayCreditName;//支付積分名稱 public int PayCreditCount = 0;//支付積分數量 public string RankCreditName;//等級積分名稱 public int RankCreditCount = 0;//等級積分數量 public PartUserInfo PartUserInfo;//用戶信息 public int UserRid = -1;//用戶等級id public UserRankInfo UserRank;//用戶等級信息 public string UserRTitle;//用戶等級標題 public int AdminGid = -1;//用戶管理員組id public AdminGroupInfo AdminGroup;//用戶管理員組信息 public string AdminGTitle;//管理員組標題 public string Controller;//控制器 public string Action;//動做方法 public string PageKey;//頁面標示符 public string ThemeName;//當前主題名稱 public string ImageDir;//圖片目錄 public string CSSDir;//css目錄 public string ScriptDir;//腳本目錄 public int OnlineUserCount = 0;//在線總人數 public int OnlineMemberCount = 0;//在線會員數 public int OnlineGuestCount = 0;//在線遊客數 public string SearchWord;//搜索詞 public int SCProductCount = 0;//購物車中商品數量 public List<CategoryInfo> CategoryList;//分類列表 public List<NavInfo> NavList;//導航列表 public FriendLinkInfo[] FriendLinkList;//友情連接列表 public List<HelpInfo> HelpList;//幫助列表 public DateTime StartExecuteTime;//頁面開始執行時間 public double ExecuteTime;//頁面執行時間 public int ExecuteCount = 0;//執行的sql語句數目 public string ExecuteDetail;//執行的sql語句細節 public string ShopVersion = BSPVersion.SHOP_VERSION;//商城版本 public string ShopCopyright = BSPVersion.SHOP_COPYRIGHT;//商城版權 } }
using System; using BrnShop.Core; namespace BrnShop.Web.Framework { /// <summary> /// 商城後臺工做上下文類 /// </summary> public class AdminWorkContext { public ShopConfigInfo ShopConfig = BSPConfig.ShopConfig;//商城配置信息 public bool IsHttpAjax;//當前請求是否爲ajax請求 public string IP;//用戶ip public RegionInfo Region;//區域信息 public string Url;//當前url public string UrlReferrer;//上一次訪問的url public string Sid;//用戶sid public int Uid = -1;//用戶id public string UserName;//用戶名 public string UserEmail;//用戶郵箱 public string UserMobile;//用戶手機號 public string NickName;//用戶暱稱 public string Avatar;//用戶頭像 public string Password;//用戶密碼 public PartUserInfo PartUserInfo;//用戶信息 public int UserRid = -1;//用戶等級id public UserRankInfo UserRank;//用戶等級信息 public string UserRTitle;//用戶等級標題 public int AdminGid = -1;//用戶管理員組id public AdminGroupInfo AdminGroup;//用戶管理員組信息 public string AdminGTitle;//管理員組標題 public string Controller;//控制器 public string Action;//動做方法 public string PageKey;//頁面標示符 } }
有了上下文類後,咱們須要找一個能夠保證上下文流動的地方。在翻看了asp.net mvc的源碼後,咱們找到一個好地方,這個地方就在控制器的基類Controller中。在Controller中微軟定義了六個方法,具體以下:java
這些都是虛方法,因此咱們能夠定義一個繼承自Controller的新控制器,而後重寫這些方法。因爲這些方法是在同一個類中,因此它們能夠共享同一個字段(這個字段就是上下文),並且其餘的控制器都是繼承自這個新控制器類,因此在動做方法中也是能夠訪問這個共享字段(父類的字段)。新控制器類分別是BrnShop.Web.Framework項目中BaseWebController類和BaseAdminController類,其中BaseWebController爲前臺控制器類,BaseAdminController爲後臺控制器類,具體實現以下:web
using System; using System.Text; using System.Web.Mvc; using System.Web.Routing; using System.Collections.Generic; using BrnShop.Core; using BrnShop.Services; namespace BrnShop.Web.Framework { /// <summary> /// 商城前臺基礎控制器類 /// </summary> public class BaseWebController : Controller { //工做上下午 public WebWorkContext WorkContext = new WebWorkContext(); protected override void Initialize(RequestContext requestContext) { base.Initialize(requestContext); WorkContext.IsHttpAjax = WebHelper.IsAjax(); WorkContext.IP = WebHelper.GetIP(); WorkContext.Region = Regions.GetRegionByIP(WorkContext.IP); WorkContext.Url = WebHelper.GetUrl(); WorkContext.UrlReferrer = WebHelper.GetUrlReferrer(); //得到用戶惟一標示符sid WorkContext.Sid = ShopUtils.GetSidCookie(); if (WorkContext.Sid.Length == 0) { //生成sid WorkContext.Sid = Sessions.GenerateSid(); //將sid保存到cookie中 ShopUtils.SetSidCookie(WorkContext.Sid); } PartUserInfo partUserInfo; //得到用戶id int uid = ShopUtils.GetUidCookie(); if (uid < 1)//當用戶爲遊客時 { //建立遊客 partUserInfo = Users.CreatePartGuest(); } else//當用戶爲會員時 { //得到保存在cookie中的密碼 string password = ShopUtils.GetPasswordCookie(); //防止用戶密碼被篡改成危險字符 if (password.Length == 0 || !SecureHelper.IsBase64String(password)) { //建立遊客 partUserInfo = Users.CreatePartGuest(); ShopUtils.SetUidCookie(-1); ShopUtils.SetPasswordCookie(""); } else { partUserInfo = Users.GetPartUserByUidAndPwd(uid, password); if (partUserInfo != null) { //發放登錄積分 Credits.SendLoginCredits(ref partUserInfo, DateTime.Now); } else//當會員的帳號或密碼不正確時,將用戶置爲遊客 { partUserInfo = Users.CreatePartGuest(); ShopUtils.SetUidCookie(-1); ShopUtils.SetPasswordCookie(""); } } } //設置用戶等級 if (UserRanks.IsBanUserRank(partUserInfo.UserRid) && partUserInfo.LiftBanTime <= DateTime.Now) { UserRankInfo userRankInfo = UserRanks.GetUserRankByCredits(partUserInfo.PayCredits); Users.UpdateUserRankByUid(partUserInfo.Uid, userRankInfo.UserRid); partUserInfo.UserRid = userRankInfo.UserRid; } WorkContext.PartUserInfo = partUserInfo; WorkContext.Uid = partUserInfo.Uid; WorkContext.UserName = partUserInfo.UserName; WorkContext.UserEmail = partUserInfo.Email; WorkContext.UserMobile = partUserInfo.Mobile; WorkContext.Password = partUserInfo.Password; WorkContext.NickName = partUserInfo.NickName; WorkContext.Avatar = partUserInfo.Avatar; WorkContext.PayCreditName = Credits.PayCreditName; WorkContext.PayCreditCount = partUserInfo.PayCredits; WorkContext.RankCreditName = Credits.RankCreditName; WorkContext.RankCreditCount = partUserInfo.RankCredits; WorkContext.UserRid = partUserInfo.UserRid; WorkContext.UserRank = UserRanks.GetUserRankById(partUserInfo.UserRid); WorkContext.UserRTitle = WorkContext.UserRank.Title; //設置用戶管理員組 WorkContext.AdminGid = partUserInfo.AdminGid; WorkContext.AdminGroup = AdminGroups.GetAdminGroupById(partUserInfo.AdminGid); WorkContext.AdminGTitle = WorkContext.AdminGroup.Title; //設置當前控制器類名 WorkContext.Controller = RouteData.Values["controller"].ToString().ToLower(); //設置當前動做方法名 WorkContext.Action = RouteData.Values["action"].ToString().ToLower(); WorkContext.PageKey = string.Format("/{0}/{1}", WorkContext.Controller, WorkContext.Action); //當前商城主題名稱 WorkContext.ThemeName = WorkContext.ShopConfig.ThemeName; //設置圖片目錄 WorkContext.ImageDir = string.Format("{0}/Themes/{1}/Images", WorkContext.ShopConfig.ImageCDN, WorkContext.ThemeName); //設置css目錄 WorkContext.CSSDir = string.Format("{0}/Themes/{1}/CSS", WorkContext.ShopConfig.CSSCDN, WorkContext.ThemeName); //設置腳本目錄 WorkContext.ScriptDir = string.Format("{0}/Scripts", WorkContext.ShopConfig.ScriptCDN); //在線總人數 WorkContext.OnlineUserCount = OnlineUsers.GetOnlineUserCount(); //在線遊客數 WorkContext.OnlineGuestCount = OnlineUsers.GetOnlineGuestCount(); //在線會員數 WorkContext.OnlineMemberCount = WorkContext.OnlineUserCount - WorkContext.OnlineGuestCount; //搜索詞 WorkContext.SearchWord = string.Empty; //購物車中商品數量 WorkContext.SCProductCount = Orders.GetShopCartProductCountCookie(); //分類列表 WorkContext.CategoryList = Categories.GetCategoryList(); //設置導航列表 WorkContext.NavList = Navs.GetNavList(); //設置友情連接列表 WorkContext.FriendLinkList = FriendLinks.GetFriendLinkList(); //設置幫助列表 WorkContext.HelpList = Helps.GetHelpList(); } protected override void OnAuthorization(AuthorizationContext filterContext) { //不能應用在子方法上 if (filterContext.IsChildAction) return; //商城已經關閉 if (WorkContext.ShopConfig.IsClosed == 1 && WorkContext.AdminGid == 1 && WorkContext.PageKey != "/account/login" && WorkContext.PageKey != "/account/logout") { filterContext.Result = PromptView(WorkContext.ShopConfig.CloseReason); return; } //當前時間爲禁止訪問時間 if (ValidateHelper.BetweenPeriod(WorkContext.ShopConfig.BanAccessTime) && WorkContext.AdminGid == 1 && WorkContext.PageKey != "/account/login" && WorkContext.PageKey != "/account/logout") { filterContext.Result = PromptView("當前時間不能訪問本商城"); return; } //當用戶ip在被禁止的ip列表時 if (ValidateHelper.InIPList(WorkContext.IP, WorkContext.ShopConfig.BanAccessIP)) { filterContext.Result = PromptView("您的IP被禁止訪問本商城"); return; } //當用戶ip不在容許的ip列表時 if (!string.IsNullOrEmpty(WorkContext.ShopConfig.AllowAccessIP) && !ValidateHelper.InIPList(WorkContext.IP, WorkContext.ShopConfig.AllowAccessIP)) { filterContext.Result = PromptView("您的IP被禁止訪問本商城"); return; } //當用戶IP被禁止時 if (BannedIPs.CheckIP(WorkContext.IP)) { filterContext.Result = PromptView("您的IP被禁止訪問本商城"); return; } //當用戶等級是禁止訪問等級時 if (WorkContext.UserRid == 1) { filterContext.Result = PromptView("您的帳號當前被鎖定,不能訪問"); return; } //判斷目前訪問人數是否達到容許的最大人數 if (WorkContext.OnlineUserCount > WorkContext.ShopConfig.MaxOnlineCount && WorkContext.AdminGid == 1 && (WorkContext.Controller != "account" && (WorkContext.Action != "login" || WorkContext.Action != "logout"))) { filterContext.Result = PromptView("商城人數達到訪問上限, 請稍等一會再訪問!"); return; } } protected override void OnActionExecuting(ActionExecutingContext filterContext) { //不能應用在子方法上 if (filterContext.IsChildAction) return; #if DEBUG //清空執行的sql語句數目 RDBSHelper.ExecuteCount = 0; //清空執行的sql語句細節 RDBSHelper.ExecuteDetail = ""; #endif //頁面開始執行時間 WorkContext.StartExecuteTime = DateTime.Now; //當用戶爲會員時,更新用戶的在線時間 if (WorkContext.Uid > 0) Users.UpdateUserOnlineTime(WorkContext.Uid); //更新在線用戶 Asyn.UpdateOnlineUser(WorkContext.Uid, WorkContext.Sid, WorkContext.IP, WorkContext.Region.RegionId); //更新PV統計 if (WorkContext.ShopConfig.UpdatePVStatTimespan != 0) Asyn.UpdatePVStat(WorkContext.Uid, WorkContext.Region.RegionId, WebHelper.GetBrowserType(), WebHelper.GetOSType()); } protected override void OnActionExecuted(ActionExecutedContext filterContext) { //不能應用在子方法上 if (filterContext.IsChildAction) return; #if DEBUG //執行的sql語句數目 WorkContext.ExecuteCount = RDBSHelper.ExecuteCount; //執行的sql語句細節 if (RDBSHelper.ExecuteDetail == string.Empty) WorkContext.ExecuteDetail = "當前頁面沒有和數據庫的任何交互"; else WorkContext.ExecuteDetail = "<div>數據查詢分析:</div>" + RDBSHelper.ExecuteDetail; #endif //頁面執行時間 WorkContext.ExecuteTime = DateTime.Now.Subtract(WorkContext.StartExecuteTime).TotalMilliseconds / 1000; } protected override void OnException(ExceptionContext filterContext) { ShopUtils.WriteLogFile(filterContext.Exception); if (WorkContext.IsHttpAjax) filterContext.Result = new ContentResult { Content = "error" }; else filterContext.Result = new ViewResult() { ViewName = "Error" }; } /// <summary> /// 得到路由中的值 /// </summary> /// <param name="key">鍵</param> /// <param name="defaultValue">默認值</param> /// <returns></returns> protected string GetRouteString(string key, string defaultValue) { object value = RouteData.Values[key]; if (value != null) return value.ToString(); else return defaultValue; } /// <summary> /// 得到路由中的值 /// </summary> /// <param name="key">鍵</param> /// <returns></returns> protected string GetRouteString(string key) { return GetRouteString(key, ""); } /// <summary> /// 得到路由中的值 /// </summary> /// <param name="key">鍵</param> /// <param name="defaultValue">默認值</param> /// <returns></returns> protected int GetRouteInt(string key, int defaultValue) { return TypeHelper.ObjectToInt(RouteData.Values[key], defaultValue); } /// <summary> /// 得到路由中的值 /// </summary> /// <param name="key">鍵</param> /// <returns></returns> protected int GetRouteInt(string key) { return GetRouteInt(key, 0); } /// <summary> /// 提示信息視圖 /// </summary> /// <param name="message">提示信息</param> /// <returns></returns> protected ViewResult PromptView(string message) { return View("Prompt", new PromptModel(message)); } /// <summary> /// 提示信息視圖 /// </summary> /// <param name="backUrl">返回地址</param> /// <param name="message">提示信息</param> /// <returns></returns> protected ViewResult PromptView(string backUrl, string message) { return View("Prompt", new PromptModel(backUrl, message)); } /// <summary> /// 得到驗證錯誤列表 /// </summary> /// <returns></returns> protected string GetVerifyErrorList() { if (ModelState.Count == 0) return "null"; StringBuilder errorList = new StringBuilder("["); foreach (KeyValuePair<string, ModelState> item in ModelState) { errorList.AppendFormat("{0}'key':'{1}','msg':'{2}'{3},", "{", item.Key, item.Value.Errors[0].ErrorMessage, "}"); } errorList.Remove(errorList.Length - 1, 1); errorList.Append("]"); return errorList.ToString(); } } }
using System; using System.Web; using System.Web.Mvc; using System.Web.Routing; using BrnShop.Core; using BrnShop.Services; namespace BrnShop.Web.Framework { /// <summary> /// 商城後臺基礎控制器類 /// </summary> public class BaseAdminController : Controller { //工做上下午 public AdminWorkContext WorkContext = new AdminWorkContext(); protected override void Initialize(RequestContext requestContext) { base.Initialize(requestContext); WorkContext.IsHttpAjax = WebHelper.IsAjax(); WorkContext.IP = WebHelper.GetIP(); WorkContext.Region = Regions.GetRegionByIP(WorkContext.IP); WorkContext.Url = WebHelper.GetUrl(); WorkContext.UrlReferrer = WebHelper.GetUrlReferrer(); //得到用戶惟一標示符sid WorkContext.Sid = ShopUtils.GetSidCookie(); if (WorkContext.Sid.Length == 0) { //生成sid WorkContext.Sid = Sessions.GenerateSid(); //將sid保存到cookie中 ShopUtils.SetSidCookie(WorkContext.Sid); } PartUserInfo partUserInfo; //得到用戶id int uid = ShopUtils.GetUidCookie(); if (uid < 1)//當用戶爲遊客時 { //建立遊客 partUserInfo = Users.CreatePartGuest(); } else//當用戶爲會員時 { //得到保存在cookie中的密碼 string password = ShopUtils.GetPasswordCookie(); //防止用戶密碼被篡改成危險字符 if (password.Length == 0 || !SecureHelper.IsBase64String(password)) { //建立遊客 partUserInfo = Users.CreatePartGuest(); ShopUtils.SetUidCookie(-1); ShopUtils.SetPasswordCookie(""); } else { partUserInfo = Users.GetPartUserByUidAndPwd(uid, password); if (partUserInfo != null) { //發放登錄積分 Credits.SendLoginCredits(ref partUserInfo, DateTime.Now); } else//當會員的帳號或密碼不正確時,將用戶置爲遊客 { partUserInfo = Users.CreatePartGuest(); ShopUtils.SetUidCookie(-1); ShopUtils.SetPasswordCookie(""); } } } //設置用戶等級 if (UserRanks.IsBanUserRank(partUserInfo.UserRid) && partUserInfo.LiftBanTime <= DateTime.Now) { UserRankInfo userRankInfo = UserRanks.GetUserRankByCredits(partUserInfo.PayCredits); Users.UpdateUserRankByUid(partUserInfo.Uid, userRankInfo.UserRid); partUserInfo.UserRid = userRankInfo.UserRid; } WorkContext.PartUserInfo = partUserInfo; WorkContext.Uid = partUserInfo.Uid; WorkContext.UserName = partUserInfo.UserName; WorkContext.UserEmail = partUserInfo.Email; WorkContext.UserMobile = partUserInfo.Mobile; WorkContext.Password = partUserInfo.Password; WorkContext.NickName = partUserInfo.NickName; WorkContext.Avatar = partUserInfo.Avatar; WorkContext.UserRid = partUserInfo.UserRid; WorkContext.UserRank = UserRanks.GetUserRankById(partUserInfo.UserRid); WorkContext.UserRTitle = WorkContext.UserRank.Title; //設置用戶管理員組 WorkContext.AdminGid = partUserInfo.AdminGid; WorkContext.AdminGroup = AdminGroups.GetAdminGroupById(partUserInfo.AdminGid); WorkContext.AdminGTitle = WorkContext.AdminGroup.Title; //設置當前控制器類名 WorkContext.Controller = RouteData.Values["controller"].ToString().ToLower(); //設置當前動做方法名 WorkContext.Action = RouteData.Values["action"].ToString().ToLower(); WorkContext.PageKey = string.Format("/{0}/{1}", WorkContext.Controller, WorkContext.Action); } protected override void OnAuthorization(AuthorizationContext filterContext) { //不能應用在子方法上 if (filterContext.IsChildAction) return; //當用戶ip不在容許的後臺訪問ip列表時 if (!string.IsNullOrEmpty(WorkContext.ShopConfig.AdminAllowAccessIP) && !ValidateHelper.InIPList(WorkContext.IP, WorkContext.ShopConfig.AdminAllowAccessIP)) { if (WorkContext.IsHttpAjax) filterContext.Result = new ContentResult { Content = "404" }; else filterContext.Result = new RedirectResult("/"); return; } //當用戶IP被禁止時 if (BannedIPs.CheckIP(WorkContext.IP)) { if (WorkContext.IsHttpAjax) filterContext.Result = new ContentResult { Content = "404" }; else filterContext.Result = new RedirectResult("/"); return; } //當用戶等級是禁止訪問等級時 if (WorkContext.UserRid == 1) { if (WorkContext.IsHttpAjax) filterContext.Result = new ContentResult { Content = "404" }; else filterContext.Result = new RedirectResult("/"); return; } //若是當前用戶沒有登陸 if (WorkContext.Uid < 1) { if (WorkContext.IsHttpAjax) filterContext.Result = new ContentResult { Content = "404" }; else filterContext.Result = new RedirectResult("/"); return; } //若是當前用戶不是管理員 if (WorkContext.AdminGid == 1) { if (WorkContext.IsHttpAjax) filterContext.Result = new ContentResult { Content = "404" }; else filterContext.Result = new RedirectResult("/"); return; } //判斷當前用戶是否有訪問當前頁面的權限 if (WorkContext.Controller != "home" && !AdminGroups.CheckAuthority(WorkContext.AdminGid, WorkContext.Controller, WorkContext.PageKey)) { if (WorkContext.IsHttpAjax) filterContext.Result = new ContentResult { Content = "notpermit" }; else filterContext.Result = PromptView("你沒有當前操做的權限!"); return; } } protected override void OnActionExecuting(ActionExecutingContext filterContext) { //不能應用在子方法上 if (filterContext.IsChildAction) return; //當用戶爲會員時,更新用戶的在線時間 if (WorkContext.Uid > 0) Users.UpdateUserOnlineTime(WorkContext.Uid); //更新在線用戶 Asyn.UpdateOnlineUser(WorkContext.Uid, WorkContext.Sid, WorkContext.IP, WorkContext.Region.RegionId); //更新PV統計 if (WorkContext.ShopConfig.UpdatePVStatTimespan != 0) Asyn.UpdatePVStat(WorkContext.Uid, WorkContext.Region.RegionId, WebHelper.GetBrowserType(), WebHelper.GetOSType()); } protected override void OnException(ExceptionContext filterContext) { ShopUtils.WriteLogFile(filterContext.Exception); if (WorkContext.IsHttpAjax) filterContext.Result = new ContentResult { Content = "error" }; else filterContext.Result = new ViewResult() { ViewName = "Error" }; } /// <summary> /// 提示信息視圖 /// </summary> /// <param name="message">提示信息</param> /// <returns></returns> protected ViewResult PromptView(string message) { return View("Prompt", new PromptModel(ShopUtils.GetAdminRefererCookie(), message)); } /// <summary> /// 提示信息視圖 /// </summary> /// <param name="backUrl">返回地址</param> /// <param name="message">提示信息</param> /// <returns></returns> protected ViewResult PromptView(string backUrl, string message) { return View("Prompt", new PromptModel(backUrl, message)); } /// <summary> /// 提示信息視圖 /// </summary> /// <param name="backUrl">返回地址</param> /// <param name="message">提示信息</param> /// <param name="isAutoBack">是否自動返回</param> /// <returns></returns> protected ViewResult PromptView(string backUrl, string message, bool isAutoBack) { return View("Prompt", new PromptModel(backUrl, message) { IsAutoBack = isAutoBack }); } /// <summary> /// 添加後臺操做日誌 /// </summary> /// <param name="operation">操做行爲</param> protected void AddAdminOperateLog(string operation) { AddAdminOperateLog(operation, ""); } /// <summary> /// 添加後臺操做日誌 /// </summary> /// <param name="operation">操做行爲</param> /// <param name="description">操做描述</param> protected void AddAdminOperateLog(string operation, string description) { AdminOperateLogs.CreateAdminOperateLog(WorkContext.Uid, WorkContext.UserName, WorkContext.AdminGid, WorkContext.AdminGTitle, WorkContext.IP, operation, description); } } }
到此事情還沒完,那就是這個上下文是控制器的字段,在視圖中若是想訪問它須要強制類型轉換下,代碼爲:((BaseWebController)(this.ViewContext.Controller)).WorkContext;試想一下咱們每次訪問上下文都須要這麼長的一段代碼那是怎樣的煎熬呀?不過幸虧有解決辦法,那就是重寫mvc的WebViewPage頁(若是你不知道WebViewPage和mvc的編譯過程請閱讀大神「Artech」的相關文章,地址以下:http://www.cnblogs.com/artech/)。具體代碼在BrnShop.Web.Framework項目中WebViewPage類和AdminViewPage類,其中WebViewPage爲前臺視圖類,AdminViewPage爲後臺視圖類:
using System; using System.Text; using System.Web.Mvc; using System.Collections.Generic; namespace BrnShop.Web.Framework { /// <summary> /// 前臺視圖頁面基類型 /// </summary> public abstract class WebViewPage<TModel> : System.Web.Mvc.WebViewPage<TModel> { public WebWorkContext WorkContext; public override void InitHelpers() { base.InitHelpers(); WorkContext = ((BaseWebController)(this.ViewContext.Controller)).WorkContext; } /// <summary> /// 得到驗證錯誤列表 /// </summary> /// <returns></returns> public MvcHtmlString GetVerifyErrorList() { ModelStateDictionary modelState = ((Controller)(this.ViewContext.Controller)).ModelState; if (modelState == null || modelState.Count == 0) return new MvcHtmlString("null"); StringBuilder errorList = new StringBuilder("["); foreach (KeyValuePair<string, ModelState> item in modelState) { errorList.AppendFormat("{0}'key':'{1}','msg':'{2}'{3},", "{", item.Key, item.Value.Errors[0].ErrorMessage, "}"); } errorList.Remove(errorList.Length - 1, 1); errorList.Append("]"); return new MvcHtmlString(errorList.ToString()); } } /// <summary> /// 前臺視圖頁面基類型 /// </summary> public abstract class WebViewPage : WebViewPage<dynamic> { } }
using System; namespace BrnShop.Web.Framework { /// <summary> /// 後臺視圖頁面基類型 /// </summary> public abstract class AdminViewPage<TModel> : System.Web.Mvc.WebViewPage<TModel> { public AdminWorkContext WorkContext; public override void InitHelpers() { base.InitHelpers(); Html.EnableClientValidation(true);//啓用客戶端驗證 Html.EnableUnobtrusiveJavaScript(true);//啓用非侵入式腳本 WorkContext = ((BaseAdminController)(this.ViewContext.Controller)).WorkContext; } } /// <summary> /// 後臺視圖頁面基類型 /// </summary> public abstract class AdminViewPage : AdminViewPage<dynamic> { } }
定義好新的視圖類後,咱們須要通知編譯器使用這個新類,通知方式在視圖文件的web.config中,具體見下圖:ajax
經過將"pageBaseType"的值設置爲咱們的新類名,咱們就能夠在視圖文件中直接使用上下文了。例:@WorkContext.ShopConfig.SEOKeywordsql
說完了數據的複用和傳遞,咱們再來講說大mvc框架和小mvc框架的問題。首先何爲大mvc框架,何爲小mvc框架?數據庫
你們可能以爲這有什麼難的?可是對於一個開源項目來講這確實是一個很重要的問題,由於開源項目的產品面向的是全國甚至是全世界的開發者,你們的技術良莠不齊,有的高,有個低。爲了保證儘量多的覆蓋開發者,只有原汁原味的mvc纔對開發者更親切和熟悉,因此應該使用大mvc框架。但是一款優秀的產品不僅是面向初級開發者,還須要面對高級開發者,對於高級開發者來講他們但願得到項目最大的可控權,因此框架應該儘可能只使用最核心的mvc部分,這樣留給開發者的空間才能更大,這樣這樣看來又應該使用小mvc框架。下面我從兩個方面來講明咱們是如何解決這個問題的。json
首先是mvc篩選器:看過咱們源碼的園友已經發現,咱們項目中沒有定義任何一個篩選器類。那咱們的篩選器在哪兒?答案就在上面的上下文流動中,在上面重寫的篩選器方法中咱們實現全部篩選。若是你想針對某個控制器A單獨篩選你能夠在A中再一次重寫篩選器方法添加本身的代碼。若是你想只針對某一方法進行篩選你只須要單獨在方法中篩選就能夠了。這樣經過使用內置在controller中的篩選方法咱們實現了和第三方篩選器的隔離,也減小了反射獲取篩選器的次數。cookie
其次是模型綁定和校驗:咱們首先經過手動獲取request集合的方式去除全部模型綁定,以登錄代碼爲例:
/// <summary> /// 登陸 /// </summary> public ActionResult Login()//注意此方面沒有任何參數 { string returnUrl = WebHelper.GetQueryString("returnUrl"); if (returnUrl.Length == 0) returnUrl = "/"; if (WorkContext.ShopConfig.LoginType == "") return PromptView(returnUrl, "商城目前已經關閉登錄功能!"); if (WorkContext.Uid > 0) return PromptView(returnUrl, "您已經登陸,無須重複登陸!"); if (WorkContext.ShopConfig.LoginFailTimes != 0 && LoginFailLogs.GetLoginFailTimesByIp(WorkContext.IP) >= WorkContext.ShopConfig.LoginFailTimes) return PromptView(returnUrl, "您已經輸入錯誤" + WorkContext.ShopConfig.LoginFailTimes + "次密碼,請15分鐘後再登錄!"); //get請求 if (WebHelper.IsGet()) { ViewData.Add("oAuthPluginList", Plugins.GetOAuthPluginList()); return View(new LoginModel()); } //post請求 LoginModel model = new LoginModel(); //模型綁定 手動綁定 model.AccountName = WebHelper.GetFormString(WorkContext.ShopConfig.ShadowName).Trim(); model.Password = WebHelper.GetFormString("password"); model.IsRemember = WebHelper.GetFormInt("isRemember"); model.VerifyCode = WebHelper.GetFormString("verifyCode"); //模型驗證 PartUserInfo partUserInfo = VerifyLogin(model); if (!ModelState.IsValid)//驗證失敗時 { ViewData.Add("oAuthPluginList", Plugins.GetOAuthPluginList()); return View(model); } else//驗證成功時 { //當用戶等級是禁止訪問等級時 if (partUserInfo.UserRid == 1) return PromptView("您的帳號當前被鎖定,不能訪問"); //刪除登錄失敗日誌 LoginFailLogs.DeleteLoginFailLogByIP(WorkContext.IP); //更新用戶最後訪問 int regionId = WorkContext.Region != null ? WorkContext.Region.RegionId : -1; Users.UpdateUserLastVisit(partUserInfo.Uid, WorkContext.IP, regionId, DateTime.Now); //更新購物車中用戶id Orders.UpdateShopCartUidBySid(partUserInfo.Uid, WorkContext.Sid); //將用戶信息寫入cookie中 ShopUtils.SetUserCookie(partUserInfo, (WorkContext.ShopConfig.IsRemember == 1 && model.IsRemember == 1) ? 30 : -1); return Redirect(returnUrl); } }
其次是模型校驗,校驗又分爲兩部分。第一部分是驗證,對此咱們也是採用手動校驗的方式,一樣以登錄爲例:
/// <summary> /// 登陸驗證 /// </summary> private PartUserInfo VerifyLogin(LoginModel model) { PartUserInfo partUserInfo = null; //驗證帳戶名 if (string.IsNullOrWhiteSpace(model.AccountName)) { ModelState.AddModelError(WorkContext.ShopConfig.ShadowName, "帳戶名不能爲空"); } else if (model.AccountName.Length < 4 || model.AccountName.Length > 50) { ModelState.AddModelError(WorkContext.ShopConfig.ShadowName, "帳戶名必須大於3且不大於50個字符"); } else if ((!SecureHelper.IsSafeSqlString(model.AccountName))) { ModelState.AddModelError(WorkContext.ShopConfig.ShadowName, "帳戶名不存在"); } //驗證密碼 if (string.IsNullOrWhiteSpace(model.Password)) { ModelState.AddModelError("password", "密碼不能爲空"); } else if (model.Password.Length < 4 || model.Password.Length > 32) { ModelState.AddModelError("password", "密碼必須大於3且不大於32個字符"); } //驗證驗證碼 if (CommonHelper.IsInArray(WorkContext.PageKey, WorkContext.ShopConfig.VerifyPages)) { if (string.IsNullOrWhiteSpace(model.VerifyCode)) { ModelState.AddModelError("verifyCode", "驗證碼不能爲空"); } else if (model.VerifyCode.ToLower() != Sessions.GetValueString(WorkContext.Sid, "verifyCode")) { ModelState.AddModelError("verifyCode", "驗證碼不正確"); } } //當以上驗證所有經過時 if (ModelState.IsValid) { if (BSPConfig.ShopConfig.LoginType.Contains("2") && ValidateHelper.IsEmail(model.AccountName))//郵箱登錄 { partUserInfo = Users.GetPartUserByEmail(model.AccountName); if (partUserInfo == null) ModelState.AddModelError(WorkContext.ShopConfig.ShadowName, "郵箱不存在"); } else if (BSPConfig.ShopConfig.LoginType.Contains("3") && ValidateHelper.IsMobile(model.AccountName))//手機登錄 { partUserInfo = Users.GetPartUserByMobile(model.AccountName); if (partUserInfo == null) ModelState.AddModelError(WorkContext.ShopConfig.ShadowName, "手機不存在"); } else if (BSPConfig.ShopConfig.LoginType.Contains("1"))//用戶名登錄 { partUserInfo = Users.GetPartUserByName(model.AccountName); if (partUserInfo == null) ModelState.AddModelError(WorkContext.ShopConfig.ShadowName, "用戶名不存在"); } //判斷密碼是否正確 if (partUserInfo != null && Users.CreateUserPassword(model.Password, partUserInfo.Salt) != partUserInfo.Password) { LoginFailLogs.AddLoginFailTimes(WorkContext.IP, DateTime.Now);//增長登錄失敗次數 ModelState.AddModelError("password", "密碼不正確"); } } return partUserInfo; }
經過上面代碼你們能夠看出全部的驗證都是手動進行的。
校驗的第二部分是驗證信息顯示,在mvc中你們常用Html.ValidationMessageFor之類的方法來顯示驗證信息,因此爲了保證上述方法還可以正常使用,咱們須要將全部驗證信息都添加到ModelState中(由於Html.ValidationMessageFor之類的方法實現本質就是經過獲取ModelState指定鍵值的內容來判斷是否顯示和顯示什麼內容)。到此咱們已經有了校驗數據,剩下的就是在視圖中顯示了。關於顯示咱們仍然可使用Html.ValidationMessageFor之類的方法;若是你想得到更大的靈活性你可使用視圖頁面的「GetVerifyErrorList」方法,此方法在咱們新定義的視圖基類中,它的功能就是將校驗信息構建成一個json對象。代碼以下:
/// <summary> /// 得到驗證錯誤列表 /// </summary> /// <returns></returns> public MvcHtmlString GetVerifyErrorList() { ModelStateDictionary modelState = ((Controller)(this.ViewContext.Controller)).ModelState; if (modelState == null || modelState.Count == 0) return new MvcHtmlString("null"); StringBuilder errorList = new StringBuilder("["); foreach (KeyValuePair<string, ModelState> item in modelState) { errorList.AppendFormat("{0}'key':'{1}','msg':'{2}'{3},", "{", item.Key, item.Value.Errors[0].ErrorMessage, "}"); } errorList.Remove(errorList.Length - 1, 1); errorList.Append("]"); return new MvcHtmlString(errorList.ToString()); }
下面給出一個使用例子,代碼是登錄視圖的代碼:
//腳本代碼 <script type="text/javascript"> var verifyErrorList= @GetVerifyErrorList(); $(function(){ if (verifyErrorList != null) { for(var i = 0; i < verifyErrorList.length; i++){ $("#"+verifyErrorList[i].key+"Error").html(verifyErrorList[i].msg) } } }) </script> //html代碼 <tr> <td>密碼:</td> <td> <input type="password" name="password" id="password" value="@Model.Password"/> </td> <td><span style="color: Red;" id="passwordError"></span></td> </tr>經過以上實現咱們既保證框架可以兼容mvc各個功能,又爲高級開發者提供了足夠的擴展空間。PS:團隊中有位同事曾經將asp.net mvc源碼中有關模型綁定和模型校驗的代碼所有刪除,並完美運行實例,性能和開銷都少了很多,有興趣的朋友能夠去試試!