BrnShop开源网上商城第二讲:ASP.NET MVC框架
在團(tuán)隊(duì)設(shè)計(jì)BrnShop的web項(xiàng)目之初,我們碰到了兩個(gè)問題,第一個(gè)是數(shù)據(jù)的復(fù)用和傳遞,第二個(gè)是大mvc框架和小mvc框架的選擇。下面我依次來說明下。
? ? ? 首先是數(shù)據(jù)的復(fù)用和傳遞:對于BrnShop的每一次請求,程序都要分成好幾個(gè)階段執(zhí)行,例如驗(yàn)證,執(zhí)行動(dòng)作方法等等,在各個(gè)階段我們可能需要重復(fù)使用同一信息,而我們的愿景就是希望此信息只需獲取一次,然后沿著流程管道一直流動(dòng),這樣在后面的階段中就可以直接使用,不用再重新獲取了,提高程序的性能。舉例來說:在授權(quán)驗(yàn)證階段,我們?yōu)閷τ脩暨M(jìn)行驗(yàn)證,從而獲取了用戶信息,當(dāng)驗(yàn)證結(jié)束后,此用戶信息并不被拋棄,而是保留下來,這樣在后面的動(dòng)作方法中我們就不需要再次獲取用戶信息,而是直接使用剛才在授權(quán)中保留下來的用戶信息就可以了。
具體實(shí)現(xiàn)是這樣的:首先我們給這些需要公用的數(shù)據(jù)定義個(gè)上下文類,它們分別是BrnShop.Web.Framework項(xiàng)目中的WebWorkContext類和AdminWorkContext類,其中WebWorkContext是前臺項(xiàng)目使用的上下文,AdminWorkContext是后臺項(xiàng)目使用的上下文。代碼很簡單,就是定義了一些公共字段,具體如下:
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;//當(dāng)前請求是否為ajax請求public string IP;//用戶ippublic RegionInfo Region;//區(qū)域信息public string Url;//當(dāng)前urlpublic string UrlReferrer;//上一次訪問的urlpublic string Sid;//用戶sidpublic int Uid = -1;//用戶idpublic string UserName;//用戶名public string UserEmail;//用戶郵箱public string UserMobile;//用戶手機(jī)號public string NickName;//用戶昵稱public string Avatar;//用戶頭像public string Password;//用戶密碼public string PayCreditName;//支付積分名稱public int PayCreditCount = 0;//支付積分?jǐn)?shù)量public string RankCreditName;//等級積分名稱public int RankCreditCount = 0;//等級積分?jǐn)?shù)量public PartUserInfo PartUserInfo;//用戶信息public int UserRid = -1;//用戶等級idpublic UserRankInfo UserRank;//用戶等級信息public string UserRTitle;//用戶等級標(biāo)題public int AdminGid = -1;//用戶管理員組idpublic AdminGroupInfo AdminGroup;//用戶管理員組信息public string AdminGTitle;//管理員組標(biāo)題public string Controller;//控制器public string Action;//動(dòng)作方法public string PageKey;//頁面標(biāo)示符public string ThemeName;//當(dāng)前主題名稱public string ImageDir;//圖片目錄public string CSSDir;//css目錄public string ScriptDir;//腳本目錄public int OnlineUserCount = 0;//在線總?cè)藬?shù)public int OnlineMemberCount = 0;//在線會員數(shù)public int OnlineGuestCount = 0;//在線游客數(shù)public string SearchWord;//搜索詞public int SCProductCount = 0;//購物車中商品數(shù)量public List<CategoryInfo> CategoryList;//分類列表public List<NavInfo> NavList;//導(dǎo)航列表public FriendLinkInfo[] FriendLinkList;//友情鏈接列表public List<HelpInfo> HelpList;//幫助列表public DateTime StartExecuteTime;//頁面開始執(zhí)行時(shí)間public double ExecuteTime;//頁面執(zhí)行時(shí)間public int ExecuteCount = 0;//執(zhí)行的sql語句數(shù)目public string ExecuteDetail;//執(zhí)行的sql語句細(xì)節(jié)public string ShopVersion = BSPVersion.SHOP_VERSION;//商城版本public string ShopCopyright = BSPVersion.SHOP_COPYRIGHT;//商城版權(quán) } } View Code using System;using BrnShop.Core;namespace BrnShop.Web.Framework {/// <summary>/// 商城后臺工作上下文類/// </summary>public class AdminWorkContext{public ShopConfigInfo ShopConfig = BSPConfig.ShopConfig;//商城配置信息public bool IsHttpAjax;//當(dāng)前請求是否為ajax請求public string IP;//用戶ippublic RegionInfo Region;//區(qū)域信息public string Url;//當(dāng)前urlpublic string UrlReferrer;//上一次訪問的urlpublic string Sid;//用戶sidpublic int Uid = -1;//用戶idpublic string UserName;//用戶名public string UserEmail;//用戶郵箱public string UserMobile;//用戶手機(jī)號public string NickName;//用戶昵稱public string Avatar;//用戶頭像public string Password;//用戶密碼public PartUserInfo PartUserInfo;//用戶信息public int UserRid = -1;//用戶等級idpublic UserRankInfo UserRank;//用戶等級信息public string UserRTitle;//用戶等級標(biāo)題public int AdminGid = -1;//用戶管理員組idpublic AdminGroupInfo AdminGroup;//用戶管理員組信息public string AdminGTitle;//管理員組標(biāo)題public string Controller;//控制器public string Action;//動(dòng)作方法public string PageKey;//頁面標(biāo)示符 } } View Code? 有了上下文類后,我們需要找一個(gè)可以保證上下文流動(dòng)的地方。在翻看了asp.net mvc的源碼后,我們找到一個(gè)好地方,這個(gè)地方就在控制器的基類Controller中。在Controller中微軟定義了六個(gè)方法,具體如下:
- protected override void Initialize(RequestContext requestContext);說明:初始化調(diào)用構(gòu)造函數(shù)后可能不可用的數(shù)據(jù)。
- protected virtual void OnAuthorization(AuthorizationContext filterContext);說明:在進(jìn)行授權(quán)時(shí)調(diào)用。
- protected virtual void OnActionExecuted(ActionExecutedContext filterContext);說明:在調(diào)用操作方法后調(diào)用。
- protected virtual void OnActionExecuting(ActionExecutingContext filterContext);說明:在調(diào)用操作方法前調(diào)用。
- protected virtual void OnResultExecuted(ResultExecutedContext filterContext);說明:在執(zhí)行由操作方法返回的操作結(jié)果后調(diào)用。
- protected virtual void OnResultExecuting(ResultExecutingContext filterContext);說明:在執(zhí)行由操作方法返回的操作結(jié)果前調(diào)用。
這些都是虛方法,所以我們可以定義一個(gè)繼承自Controller的新控制器,然后重寫這些方法。由于這些方法是在同一個(gè)類中,所以它們可以共享同一個(gè)字段(這個(gè)字段就是上下文),而且其他的控制器都是繼承自這個(gè)新控制器類,所以在動(dòng)作方法中也是可以訪問這個(gè)共享字段(父類的字段)。新控制器類分別是BrnShop.Web.Framework項(xiàng)目中BaseWebController類和BaseAdminController類,其中BaseWebController為前臺控制器類,BaseAdminController為后臺控制器類,具體實(shí)現(xiàn)如下:
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>/// 商城前臺基礎(chǔ)控制器類/// </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();//獲得用戶唯一標(biāo)示符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)//當(dāng)用戶為游客時(shí) {//創(chuàng)建游客partUserInfo = Users.CreatePartGuest();}else//當(dāng)用戶為會員時(shí) {//獲得保存在cookie中的密碼string password = ShopUtils.GetPasswordCookie();//防止用戶密碼被篡改為危險(xiǎn)字符if (password.Length == 0 || !SecureHelper.IsBase64String(password)){//創(chuàng)建游客partUserInfo = Users.CreatePartGuest();ShopUtils.SetUidCookie(-1);ShopUtils.SetPasswordCookie("");}else{partUserInfo = Users.GetPartUserByUidAndPwd(uid, password);if (partUserInfo != null){//發(fā)放登陸積分Credits.SendLoginCredits(ref partUserInfo, DateTime.Now);}else//當(dāng)會員的賬號或密碼不正確時(shí),將用戶置為游客 {partUserInfo = Users.CreatePartGuest();ShopUtils.SetUidCookie(-1);ShopUtils.SetPasswordCookie("");}}}//設(shè)置用戶等級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;//設(shè)置用戶管理員組WorkContext.AdminGid = partUserInfo.AdminGid;WorkContext.AdminGroup = AdminGroups.GetAdminGroupById(partUserInfo.AdminGid);WorkContext.AdminGTitle = WorkContext.AdminGroup.Title;//設(shè)置當(dāng)前控制器類名WorkContext.Controller = RouteData.Values["controller"].ToString().ToLower();//設(shè)置當(dāng)前動(dòng)作方法名WorkContext.Action = RouteData.Values["action"].ToString().ToLower();WorkContext.PageKey = string.Format("/{0}/{1}", WorkContext.Controller, WorkContext.Action);//當(dāng)前商城主題名稱WorkContext.ThemeName = WorkContext.ShopConfig.ThemeName;//設(shè)置圖片目錄WorkContext.ImageDir = string.Format("{0}/Themes/{1}/Images", WorkContext.ShopConfig.ImageCDN, WorkContext.ThemeName);//設(shè)置css目錄WorkContext.CSSDir = string.Format("{0}/Themes/{1}/CSS", WorkContext.ShopConfig.CSSCDN, WorkContext.ThemeName);//設(shè)置腳本目錄WorkContext.ScriptDir = string.Format("{0}/Scripts", WorkContext.ShopConfig.ScriptCDN);//在線總?cè)藬?shù)WorkContext.OnlineUserCount = OnlineUsers.GetOnlineUserCount();//在線游客數(shù)WorkContext.OnlineGuestCount = OnlineUsers.GetOnlineGuestCount();//在線會員數(shù)WorkContext.OnlineMemberCount = WorkContext.OnlineUserCount - WorkContext.OnlineGuestCount;//搜索詞WorkContext.SearchWord = string.Empty;//購物車中商品數(shù)量WorkContext.SCProductCount = Orders.GetShopCartProductCountCookie();//分類列表WorkContext.CategoryList = Categories.GetCategoryList();//設(shè)置導(dǎo)航列表WorkContext.NavList = Navs.GetNavList();//設(shè)置友情鏈接列表WorkContext.FriendLinkList = FriendLinks.GetFriendLinkList();//設(shè)置幫助列表WorkContext.HelpList = Helps.GetHelpList();}protected override void OnAuthorization(AuthorizationContext filterContext){//不能應(yīng)用在子方法上if (filterContext.IsChildAction)return;//商城已經(jīng)關(guān)閉if (WorkContext.ShopConfig.IsClosed == 1 && WorkContext.AdminGid == 1 && WorkContext.PageKey != "/account/login" && WorkContext.PageKey != "/account/logout"){filterContext.Result = PromptView(WorkContext.ShopConfig.CloseReason);return;}//當(dāng)前時(shí)間為禁止訪問時(shí)間if (ValidateHelper.BetweenPeriod(WorkContext.ShopConfig.BanAccessTime) && WorkContext.AdminGid == 1 && WorkContext.PageKey != "/account/login" && WorkContext.PageKey != "/account/logout"){filterContext.Result = PromptView("當(dāng)前時(shí)間不能訪問本商城");return;}//當(dāng)用戶ip在被禁止的ip列表時(shí)if (ValidateHelper.InIPList(WorkContext.IP, WorkContext.ShopConfig.BanAccessIP)){filterContext.Result = PromptView("您的IP被禁止訪問本商城");return;}//當(dāng)用戶ip不在允許的ip列表時(shí)if (!string.IsNullOrEmpty(WorkContext.ShopConfig.AllowAccessIP) && !ValidateHelper.InIPList(WorkContext.IP, WorkContext.ShopConfig.AllowAccessIP)){filterContext.Result = PromptView("您的IP被禁止訪問本商城");return;}//當(dāng)用戶IP被禁止時(shí)if (BannedIPs.CheckIP(WorkContext.IP)){filterContext.Result = PromptView("您的IP被禁止訪問本商城");return;}//當(dāng)用戶等級是禁止訪問等級時(shí)if (WorkContext.UserRid == 1){filterContext.Result = PromptView("您的賬號當(dāng)前被鎖定,不能訪問");return;}//判斷目前訪問人數(shù)是否達(dá)到允許的最大人數(shù)if (WorkContext.OnlineUserCount > WorkContext.ShopConfig.MaxOnlineCount && WorkContext.AdminGid == 1 && (WorkContext.Controller != "account" && (WorkContext.Action != "login" || WorkContext.Action != "logout"))){filterContext.Result = PromptView("商城人數(shù)達(dá)到訪問上限, 請稍等一會再訪問!");return;}}protected override void OnActionExecuting(ActionExecutingContext filterContext){//不能應(yīng)用在子方法上if (filterContext.IsChildAction)return; #if DEBUG//清空執(zhí)行的sql語句數(shù)目RDBSHelper.ExecuteCount = 0;//清空執(zhí)行的sql語句細(xì)節(jié)RDBSHelper.ExecuteDetail = ""; #endif//頁面開始執(zhí)行時(shí)間WorkContext.StartExecuteTime = DateTime.Now;//當(dāng)用戶為會員時(shí),更新用戶的在線時(shí)間if (WorkContext.Uid > 0)Users.UpdateUserOnlineTime(WorkContext.Uid);//更新在線用戶 Asyn.UpdateOnlineUser(WorkContext.Uid, WorkContext.Sid, WorkContext.IP, WorkContext.Region.RegionId);//更新PV統(tǒng)計(jì)if (WorkContext.ShopConfig.UpdatePVStatTimespan != 0)Asyn.UpdatePVStat(WorkContext.Uid, WorkContext.Region.RegionId, WebHelper.GetBrowserType(), WebHelper.GetOSType());}protected override void OnActionExecuted(ActionExecutedContext filterContext){//不能應(yīng)用在子方法上if (filterContext.IsChildAction)return; #if DEBUG//執(zhí)行的sql語句數(shù)目WorkContext.ExecuteCount = RDBSHelper.ExecuteCount;//執(zhí)行的sql語句細(xì)節(jié)if (RDBSHelper.ExecuteDetail == string.Empty)WorkContext.ExecuteDetail = "當(dāng)前頁面沒有和數(shù)據(jù)庫的任何交互";elseWorkContext.ExecuteDetail = "<div>數(shù)據(jù)查詢分析:</div>" + RDBSHelper.ExecuteDetail; #endif//頁面執(zhí)行時(shí)間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">默認(rèn)值</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">默認(rèn)值</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>/// 獲得驗(yàn)證錯(cuò)誤列表/// </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>/// 商城后臺基礎(chǔ)控制器類/// </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();//獲得用戶唯一標(biāo)示符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)//當(dāng)用戶為游客時(shí) {//創(chuàng)建游客partUserInfo = Users.CreatePartGuest();}else//當(dāng)用戶為會員時(shí) {//獲得保存在cookie中的密碼string password = ShopUtils.GetPasswordCookie();//防止用戶密碼被篡改為危險(xiǎn)字符if (password.Length == 0 || !SecureHelper.IsBase64String(password)){//創(chuàng)建游客partUserInfo = Users.CreatePartGuest();ShopUtils.SetUidCookie(-1);ShopUtils.SetPasswordCookie("");}else{partUserInfo = Users.GetPartUserByUidAndPwd(uid, password);if (partUserInfo != null){//發(fā)放登陸積分Credits.SendLoginCredits(ref partUserInfo, DateTime.Now);}else//當(dāng)會員的賬號或密碼不正確時(shí),將用戶置為游客 {partUserInfo = Users.CreatePartGuest();ShopUtils.SetUidCookie(-1);ShopUtils.SetPasswordCookie("");}}}//設(shè)置用戶等級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;//設(shè)置用戶管理員組WorkContext.AdminGid = partUserInfo.AdminGid;WorkContext.AdminGroup = AdminGroups.GetAdminGroupById(partUserInfo.AdminGid);WorkContext.AdminGTitle = WorkContext.AdminGroup.Title;//設(shè)置當(dāng)前控制器類名WorkContext.Controller = RouteData.Values["controller"].ToString().ToLower();//設(shè)置當(dāng)前動(dòng)作方法名WorkContext.Action = RouteData.Values["action"].ToString().ToLower();WorkContext.PageKey = string.Format("/{0}/{1}", WorkContext.Controller, WorkContext.Action);}protected override void OnAuthorization(AuthorizationContext filterContext){//不能應(yīng)用在子方法上if (filterContext.IsChildAction)return;//當(dāng)用戶ip不在允許的后臺訪問ip列表時(shí)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;}//當(dāng)用戶IP被禁止時(shí)if (BannedIPs.CheckIP(WorkContext.IP)){if (WorkContext.IsHttpAjax)filterContext.Result = new ContentResult { Content = "404" };elsefilterContext.Result = new RedirectResult("/");return;}//當(dāng)用戶等級是禁止訪問等級時(shí)if (WorkContext.UserRid == 1){if (WorkContext.IsHttpAjax)filterContext.Result = new ContentResult { Content = "404" };elsefilterContext.Result = new RedirectResult("/");return;}//如果當(dāng)前用戶沒有登錄if (WorkContext.Uid < 1){if (WorkContext.IsHttpAjax)filterContext.Result = new ContentResult { Content = "404" };elsefilterContext.Result = new RedirectResult("/");return;}//如果當(dāng)前用戶不是管理員if (WorkContext.AdminGid == 1){if (WorkContext.IsHttpAjax)filterContext.Result = new ContentResult { Content = "404" };elsefilterContext.Result = new RedirectResult("/");return;}//判斷當(dāng)前用戶是否有訪問當(dāng)前頁面的權(quán)限if (WorkContext.Controller != "home" && !AdminGroups.CheckAuthority(WorkContext.AdminGid, WorkContext.Controller, WorkContext.PageKey)){if (WorkContext.IsHttpAjax)filterContext.Result = new ContentResult { Content = "notpermit" };elsefilterContext.Result = PromptView("你沒有當(dāng)前操作的權(quán)限!");return;}}protected override void OnActionExecuting(ActionExecutingContext filterContext){//不能應(yīng)用在子方法上if (filterContext.IsChildAction)return;//當(dāng)用戶為會員時(shí),更新用戶的在線時(shí)間if (WorkContext.Uid > 0)Users.UpdateUserOnlineTime(WorkContext.Uid);//更新在線用戶 Asyn.UpdateOnlineUser(WorkContext.Uid, WorkContext.Sid, WorkContext.IP, WorkContext.Region.RegionId);//更新PV統(tǒng)計(jì)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">是否自動(dòng)返回</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到此事情還沒完,那就是這個(gè)上下文是控制器的字段,在視圖中如果想訪問它需要強(qiáng)制類型轉(zhuǎn)換下,代碼為:((BaseWebController)(this.ViewContext.Controller)).WorkContext;試想一下我們每次訪問上下文都需要這么長的一段代碼那是怎樣的煎熬呀?不過幸好有解決辦法,那就是重寫mvc的WebViewPage頁(如果你不知道WebViewPage和mvc的編譯過程請閱讀大神“Artech”的相關(guān)文章,地址如下:http://www.cnblogs.com/artech/)。具體代碼在BrnShop.Web.Framework項(xiàng)目中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>/// 獲得驗(yàn)證錯(cuò)誤列表/// </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);//啟用客戶端驗(yàn)證Html.EnableUnobtrusiveJavaScript(true);//啟用非侵入式腳本WorkContext = ((BaseAdminController)(this.ViewContext.Controller)).WorkContext;}}/// <summary>/// 后臺視圖頁面基類型/// </summary>public abstract class AdminViewPage : AdminViewPage<dynamic>{} } View Code? 定義好新的視圖類后,我們需要通知編譯器使用這個(gè)新類,通知方式在視圖文件的web.config中,具體見下圖:
通過將"pageBaseType"的值設(shè)置為我們的新類名,我們就可以在視圖文件中直接使用上下文了。例:@WorkContext.ShopConfig.SEOKeyword
說完了數(shù)據(jù)的復(fù)用和傳遞,我們再來說說大mvc框架和小mvc框架的問題。首先何為大mvc框架,何為小mvc框架?
- 大mvc框架指的是盡量完整的一套asp.net mvc框架,包含路由,控制器,模型綁定,模型校驗(yàn),篩選器等等。
- 小mvc框架指的是只包含項(xiàng)目所必須使用的mvc部分,對于使用不到的部分盡量不用或移除。
大家可能覺得這有什么難的?但是對于一個(gè)開源項(xiàng)目來說這確實(shí)是一個(gè)很重要的問題,因?yàn)殚_源項(xiàng)目的產(chǎn)品面向的是全國甚至是全世界的開發(fā)者,大家的技術(shù)參差不齊,有的高,有個(gè)低。為了保證盡可能多的覆蓋開發(fā)者,只有原汁原味的mvc才對開發(fā)者更親切和熟悉,所以應(yīng)該使用大mvc框架。可是一款優(yōu)秀的產(chǎn)品不只是面向初級開發(fā)者,還需要面對高級開發(fā)者,對于高級開發(fā)者來說他們希望獲得項(xiàng)目最大的可控權(quán),所以框架應(yīng)該盡量只使用最核心的mvc部分,這樣留給開發(fā)者的空間才能更大,這樣這樣看來又應(yīng)該使用小mvc框架。下面我從兩個(gè)方面來說明我們是如何解決這個(gè)問題的。
首先是mvc篩選器:看過我們源碼的園友已經(jīng)發(fā)現(xiàn),我們項(xiàng)目中沒有定義任何一個(gè)篩選器類。那我們的篩選器在哪兒?答案就在上面的上下文流動(dòng)中,在上面重寫的篩選器方法中我們實(shí)現(xiàn)所有篩選。如果你想針對某個(gè)控制器A單獨(dú)篩選你可以在A中再一次重寫篩選器方法添加自己的代碼。如果你想只針對某一方法進(jìn)行篩選你只需要單獨(dú)在方法中篩選就可以了。這樣通過使用內(nèi)置在controller中的篩選方法我們實(shí)現(xiàn)了和第三方篩選器的隔離,也減少了反射獲取篩選器的次數(shù)。
其次是模型綁定和校驗(yàn):我們首先通過手動(dòng)獲取request集合的方式去除所有模型綁定,以登陸代碼為例:
/// <summary>/// 登錄/// </summary>public ActionResult Login()//注意此方面沒有任何參數(shù) {string returnUrl = WebHelper.GetQueryString("returnUrl");if (returnUrl.Length == 0)returnUrl = "/";if (WorkContext.ShopConfig.LoginType == "")return PromptView(returnUrl, "商城目前已經(jīng)關(guān)閉登陸功能!");if (WorkContext.Uid > 0)return PromptView(returnUrl, "您已經(jīng)登錄,無須重復(fù)登錄!");if (WorkContext.ShopConfig.LoginFailTimes != 0 && LoginFailLogs.GetLoginFailTimesByIp(WorkContext.IP) >= WorkContext.ShopConfig.LoginFailTimes)return PromptView(returnUrl, "您已經(jīng)輸入錯(cuò)誤" + WorkContext.ShopConfig.LoginFailTimes + "次密碼,請15分鐘后再登陸!");//get請求if (WebHelper.IsGet()){ViewData.Add("oAuthPluginList", Plugins.GetOAuthPluginList());return View(new LoginModel());}//post請求LoginModel model = new LoginModel();//模型綁定 手動(dòng)綁定model.AccountName = WebHelper.GetFormString(WorkContext.ShopConfig.ShadowName).Trim();model.Password = WebHelper.GetFormString("password");model.IsRemember = WebHelper.GetFormInt("isRemember");model.VerifyCode = WebHelper.GetFormString("verifyCode");//模型驗(yàn)證PartUserInfo partUserInfo = VerifyLogin(model);if (!ModelState.IsValid)//驗(yàn)證失敗時(shí) {ViewData.Add("oAuthPluginList", Plugins.GetOAuthPluginList());return View(model);}else//驗(yàn)證成功時(shí) {//當(dāng)用戶等級是禁止訪問等級時(shí)if (partUserInfo.UserRid == 1)return PromptView("您的賬號當(dāng)前被鎖定,不能訪問");//刪除登陸失敗日志 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);}}其次是模型校驗(yàn),校驗(yàn)又分為兩部分。第一部分是驗(yàn)證,對此我們也是采用手動(dòng)校驗(yàn)的方式,同樣以登陸為例:
/// <summary>/// 登錄驗(yàn)證/// </summary>private PartUserInfo VerifyLogin(LoginModel model){PartUserInfo partUserInfo = null;//驗(yàn)證賬戶名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個(gè)字符");}else if ((!SecureHelper.IsSafeSqlString(model.AccountName))){ModelState.AddModelError(WorkContext.ShopConfig.ShadowName, "賬戶名不存在");}//驗(yàn)證密碼if (string.IsNullOrWhiteSpace(model.Password)){ModelState.AddModelError("password", "密碼不能為空");}else if (model.Password.Length < 4 || model.Password.Length > 32){ModelState.AddModelError("password", "密碼必須大于3且不大于32個(gè)字符");}//驗(yàn)證驗(yàn)證碼if (CommonHelper.IsInArray(WorkContext.PageKey, WorkContext.ShopConfig.VerifyPages)){if (string.IsNullOrWhiteSpace(model.VerifyCode)){ModelState.AddModelError("verifyCode", "驗(yàn)證碼不能為空");}else if (model.VerifyCode.ToLower() != Sessions.GetValueString(WorkContext.Sid, "verifyCode")){ModelState.AddModelError("verifyCode", "驗(yàn)證碼不正確");}}//當(dāng)以上驗(yàn)證全部通過時(shí)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))//手機(jī)登陸 {partUserInfo = Users.GetPartUserByMobile(model.AccountName);if (partUserInfo == null)ModelState.AddModelError(WorkContext.ShopConfig.ShadowName, "手機(jī)不存在");}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);//增加登陸失敗次數(shù)ModelState.AddModelError("password", "密碼不正確");}}return partUserInfo;}通過上面代碼大家可以看出所有的驗(yàn)證都是手動(dòng)進(jìn)行的。
校驗(yàn)的第二部分是驗(yàn)證信息顯示,在mvc中大家經(jīng)常使用Html.ValidationMessageFor之類的方法來顯示驗(yàn)證信息,所以為了保證上述方法還能夠正常使用,我們需要將所有驗(yàn)證信息都添加到ModelState中(因?yàn)镠tml.ValidationMessageFor之類的方法實(shí)現(xiàn)本質(zhì)就是通過獲取ModelState指定鍵值的內(nèi)容來判斷是否顯示和顯示什么內(nèi)容)。到此我們已經(jīng)有了校驗(yàn)數(shù)據(jù),剩下的就是在視圖中顯示了。關(guān)于顯示我們?nèi)匀豢梢允褂肏tml.ValidationMessageFor之類的方法;如果你想獲得更大的靈活性你可以使用視圖頁面的“GetVerifyErrorList”方法,此方法在我們新定義的視圖基類中,它的功能就是將校驗(yàn)信息構(gòu)建成一個(gè)json對象。代碼如下:
/// <summary>/// 獲得驗(yàn)證錯(cuò)誤列表/// </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());}下面給出一個(gè)使用例子,代碼是登陸視圖的代碼:
//腳本代碼 <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>通過以上實(shí)現(xiàn)我們既保證框架能夠兼容mvc各個(gè)功能,又為高級開發(fā)者提供了足夠的擴(kuò)展空間。PS:團(tuán)隊(duì)中有位同事曾經(jīng)將asp.net mvc源碼中有關(guān)模型綁定和模型校驗(yàn)的代碼全部刪除,并完美運(yùn)行實(shí)例,性能和開銷都少了不少,有興趣的朋友可以去試試!
如果想下載商城源碼可以點(diǎn)此下載。有對網(wǎng)上商城程序設(shè)計(jì)感興趣的朋友,歡迎加入QQ群:235274151,大家可以交流下!
posted on 2014-06-26 17:51 NET未來之路 閱讀(...) 評論(...) 編輯 收藏轉(zhuǎn)載于:https://www.cnblogs.com/lonelyxmas/p/3810565.html
總結(jié)
以上是生活随笔為你收集整理的BrnShop开源网上商城第二讲:ASP.NET MVC框架的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MFC 错误异常,用vs添加资源并为资源
- 下一篇: .NET Framework 4.0源代