BrnShop开源网上商城第二讲:ASP.NET MVC框架
在團隊設計BrnShop的web項目之初,我們碰到了兩個問題,第一個是數據的復用和傳遞,第二個是大mvc框架和小mvc框架的選擇。下面我依次來說明下。
? ? ? 首先是數據的復用和傳遞:對于BrnShop的每一次請求,程序都要分成好幾個階段執行,例如驗證,執行動作方法等等,在各個階段我們可能需要重復使用同一信息,而我們的愿景就是希望此信息只需獲取一次,然后沿著流程管道一直流動,這樣在后面的階段中就可以直接使用,不用再重新獲取了,提高程序的性能。舉例來說:在授權驗證階段,我們為對用戶進行驗證,從而獲取了用戶信息,當驗證結束后,此用戶信息并不被拋棄,而是保留下來,這樣在后面的動作方法中我們就不需要再次獲取用戶信息,而是直接使用剛才在授權中保留下來的用戶信息就可以了。
具體實現是這樣的:首先我們給這些需要公用的數據定義個上下文類,它們分別是BrnShop.Web.Framework項目中的WebWorkContext類和AdminWorkContext類,其中WebWorkContext是前臺項目使用的上下文,AdminWorkContext是后臺項目使用的上下文。代碼很簡單,就是定義了一些公共字段,具體如下:
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;//用戶ippublic RegionInfo Region;//區域信息public string Url;//當前urlpublic string UrlReferrer;//上一次訪問的urlpublic string Sid;//用戶sidpublic int Uid = -1;//用戶idpublic 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;//用戶等級idpublic UserRankInfo UserRank;//用戶等級信息public string UserRTitle;//用戶等級標題public int AdminGid = -1;//用戶管理員組idpublic 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;//商城版權 } } View Code 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;//用戶ippublic RegionInfo Region;//區域信息public string Url;//當前urlpublic string UrlReferrer;//上一次訪問的urlpublic string Sid;//用戶sidpublic int Uid = -1;//用戶idpublic string UserName;//用戶名public string UserEmail;//用戶郵箱public string UserMobile;//用戶手機號public string NickName;//用戶昵稱public string Avatar;//用戶頭像public string Password;//用戶密碼public PartUserInfo PartUserInfo;//用戶信息public int UserRid = -1;//用戶等級idpublic UserRankInfo UserRank;//用戶等級信息public string UserRTitle;//用戶等級標題public int AdminGid = -1;//用戶管理員組idpublic AdminGroupInfo AdminGroup;//用戶管理員組信息public string AdminGTitle;//管理員組標題public string Controller;//控制器public string Action;//動作方法public string PageKey;//頁面標示符 } } View Code? 有了上下文類后,我們需要找一個可以保證上下文流動的地方。在翻看了asp.net mvc的源碼后,我們找到一個好地方,這個地方就在控制器的基類Controller中。在Controller中微軟定義了六個方法,具體如下:
- protected override void Initialize(RequestContext requestContext);說明:初始化調用構造函數后可能不可用的數據。
- protected virtual void OnAuthorization(AuthorizationContext filterContext);說明:在進行授權時調用。
- protected virtual void OnActionExecuted(ActionExecutedContext filterContext);說明:在調用操作方法后調用。
- protected virtual void OnActionExecuting(ActionExecutingContext filterContext);說明:在調用操作方法前調用。
- protected virtual void OnResultExecuted(ResultExecutedContext filterContext);說明:在執行由操作方法返回的操作結果后調用。
- protected virtual void OnResultExecuting(ResultExecutingContext filterContext);說明:在執行由操作方法返回的操作結果前調用。
這些都是虛方法,所以我們可以定義一個繼承自Controller的新控制器,然后重寫這些方法。由于這些方法是在同一個類中,所以它們可以共享同一個字段(這個字段就是上下文),而且其他的控制器都是繼承自這個新控制器類,所以在動作方法中也是可以訪問這個共享字段(父類的字段)。新控制器類分別是BrnShop.Web.Framework項目中BaseWebController類和BaseAdminController類,其中BaseWebController為前臺控制器類,BaseAdminController為后臺控制器類,具體實現如下:
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();//獲得用戶唯一標示符sidWorkContext.Sid = ShopUtils.GetSidCookie();if (WorkContext.Sid.Length == 0){//生成sidWorkContext.Sid = Sessions.GenerateSid();//將sid保存到cookie中 ShopUtils.SetSidCookie(WorkContext.Sid);}PartUserInfo partUserInfo;//獲得用戶idint 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 = "當前頁面沒有和數據庫的任何交互";elseWorkContext.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" };elsefilterContext.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();elsereturn 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();}} } View Code 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();//獲得用戶唯一標示符sidWorkContext.Sid = ShopUtils.GetSidCookie();if (WorkContext.Sid.Length == 0){//生成sidWorkContext.Sid = Sessions.GenerateSid();//將sid保存到cookie中 ShopUtils.SetSidCookie(WorkContext.Sid);}PartUserInfo partUserInfo;//獲得用戶idint 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" };elsefilterContext.Result = new RedirectResult("/");return;}//當用戶IP被禁止時if (BannedIPs.CheckIP(WorkContext.IP)){if (WorkContext.IsHttpAjax)filterContext.Result = new ContentResult { Content = "404" };elsefilterContext.Result = new RedirectResult("/");return;}//當用戶等級是禁止訪問等級時if (WorkContext.UserRid == 1){if (WorkContext.IsHttpAjax)filterContext.Result = new ContentResult { Content = "404" };elsefilterContext.Result = new RedirectResult("/");return;}//如果當前用戶沒有登錄if (WorkContext.Uid < 1){if (WorkContext.IsHttpAjax)filterContext.Result = new ContentResult { Content = "404" };elsefilterContext.Result = new RedirectResult("/");return;}//如果當前用戶不是管理員if (WorkContext.AdminGid == 1){if (WorkContext.IsHttpAjax)filterContext.Result = new ContentResult { Content = "404" };elsefilterContext.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" };elsefilterContext.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" };elsefilterContext.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);}} } View Code到此事情還沒完,那就是這個上下文是控制器的字段,在視圖中如果想訪問它需要強制類型轉換下,代碼為:((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>{} } View Code 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>{} } View Code? 定義好新的視圖類后,我們需要通知編譯器使用這個新類,通知方式在視圖文件的web.config中,具體見下圖:
通過將"pageBaseType"的值設置為我們的新類名,我們就可以在視圖文件中直接使用上下文了。例:@WorkContext.ShopConfig.SEOKeyword
說完了數據的復用和傳遞,我們再來說說大mvc框架和小mvc框架的問題。首先何為大mvc框架,何為小mvc框架?
- 大mvc框架指的是盡量完整的一套asp.net mvc框架,包含路由,控制器,模型綁定,模型校驗,篩選器等等。
- 小mvc框架指的是只包含項目所必須使用的mvc部分,對于使用不到的部分盡量不用或移除。
大家可能覺得這有什么難的?但是對于一個開源項目來說這確實是一個很重要的問題,因為開源項目的產品面向的是全國甚至是全世界的開發者,大家的技術參差不齊,有的高,有個低。為了保證盡可能多的覆蓋開發者,只有原汁原味的mvc才對開發者更親切和熟悉,所以應該使用大mvc框架。可是一款優秀的產品不只是面向初級開發者,還需要面對高級開發者,對于高級開發者來說他們希望獲得項目最大的可控權,所以框架應該盡量只使用最核心的mvc部分,這樣留給開發者的空間才能更大,這樣這樣看來又應該使用小mvc框架。下面我從兩個方面來說明我們是如何解決這個問題的。
首先是mvc篩選器:看過我們源碼的園友已經發現,我們項目中沒有定義任何一個篩選器類。那我們的篩選器在哪兒?答案就在上面的上下文流動中,在上面重寫的篩選器方法中我們實現所有篩選。如果你想針對某個控制器A單獨篩選你可以在A中再一次重寫篩選器方法添加自己的代碼。如果你想只針對某一方法進行篩選你只需要單獨在方法中篩選就可以了。這樣通過使用內置在controller中的篩選方法我們實現了和第三方篩選器的隔離,也減少了反射獲取篩選器的次數。
其次是模型綁定和校驗:我們首先通過手動獲取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源碼中有關模型綁定和模型校驗的代碼全部刪除,并完美運行實例,性能和開銷都少了不少,有興趣的朋友可以去試試!
如果想下載商城源碼可以點此下載。有對網上商城程序設計感興趣的朋友,歡迎加入QQ群:235274151,大家可以交流下!
posted on 2014-06-26 17:51 NET未來之路 閱讀(...) 評論(...) 編輯 收藏轉載于:https://www.cnblogs.com/lonelyxmas/p/3810565.html
總結
以上是生活随笔為你收集整理的BrnShop开源网上商城第二讲:ASP.NET MVC框架的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MFC 错误异常,用vs添加资源并为资源
- 下一篇: Android给TextView和Edi