BrnShop开源网上商城第二讲:ASP.NET MVC框架

BrnShop开源网上商城第二讲:ASP.NET MVC框架
原文: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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/359775.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

tomcat不停机部署_Tomcat中的零停机部署(和回滚); 演练和清单

tomcat不停机部署亲爱的大家&#xff0c; 如果您认为Tomcat不能再进步&#xff0c;那您就错了。 Tomcat 7引入了所谓的并行部署 。 这是由SpringSource / VMWare贡献的。 简而言之&#xff0c;并行部署是指能够并行部署一个以上版本的Web应用程序&#xff0c;从而使所有版本都…

matlab三维选取二维,基于Matlab绘制二维和三维图形以及其他图形控制函数的使用方法...

Matlab绘图强大的绘图功能是Matlab的特点之一&#xff0c;Matlab提供了一系列的绘图函数&#xff0c;用户不需要过多的考虑绘图的细节&#xff0c;只需要给出一些基本参数就能得到所需图形&#xff0c;这类函数称为高层绘图函数。此外&#xff0c;Matlab还提供了直接对图形句柄…

Console命令详解,让调试js代码变得更简单

刚刚在浏览关于js方面的博客时发现这个方法挺好玩的&#xff0c;自己爽了一把。 1 <script> 2 console.time(/X(.)X/ test); 3 "XX".match(/X(.)X/); 4 console.timeEnd(/X(.)X/ test); 5 </script> 然后恶补了一下关于Firebug控制台的知识。熟练地使用…

PHP求体重成绩函数,PHP数组

数组提出一个问题&#xff1a;一个养鸡场有6只鸡&#xff0c;他们的体重分别为3kg&#xff0c;5kg&#xff0c;1k个&#xff0c;3.4kg&#xff0c;2kg&#xff0c;6.kg请问这六只鸡的总体重是多少平均体重是多少请你用现在掌握的技术编一个程序现在我们使用现有的技术来解决问题…

k8s secret使用_Java Secret:使用枚举构建状态机

k8s secret使用总览 Java中的枚举比许多其他语言更强大&#xff0c;可以导致令人惊讶的用途。 在本文中&#xff0c;我概述了Java 枚举的一些单独功能&#xff0c;并将它们放在一起形成一个状态机。 单例和实用程序类的枚举 您可以非常简单地将枚举用作Singleton或Utility。…

mydumper备份原理和使用方法

mydumper介绍 MySQL自身的mysqldump工具支持单线程工作&#xff0c;依次一个个导出多个表&#xff0c;没有一个并行的机&#xff0c;这就使得它无法迅速的备份数据。 mydumper作为一个实用工具&#xff0c;能够良好支持多线程工作&#xff0c;可以并行的多线程的从表中读入数据…

matlab pca可视化,利用Matlab实现PCA demo展示

input_data rand(1000,3);%随机生成1000个样本&#xff0c;每个样本有x,y,z三个属性 figure(1);%控制画图的窗口为1hold off;%使当前轴和图形不再具备被刷新的性质&#xff0c;关闭在此基础上再画图plot3(input_data(:,1), input_data(:,2), input_data(:,3), ‘ro‘);%% Func…

matlab短均线滞后项,均线理论的滞后性问题

对移动平均线有一定了解的人都会发现移动平均线理论存在一个缺点&#xff0c;那就是移动平均线的信号具有一定的滞后性&#xff0c;这是制约移动平均线运用的最大因素。介绍了均线的计算方法.从它的计算方法中也能看出目前均线的数值要受到前一阶段股价的影响&#xff0c;而且均…

python捕获摄像头帧_Xuggler教程:帧捕获和视频创建

python捕获摄像头帧注意&#xff1a;这是我们的“ Xuggler开发教程 ”系列的一部分。 到目前为止&#xff0c;在我们的Xuggler教程系列中&#xff0c;我们已经对视频处理的Xuggler进行了介绍&#xff0c;并讨论了转码和媒体修改 。 在本教程中&#xff0c;我们将看到如何解码视…

MyEclipse 编写 ExtJS 卡死问题解决方法

MyEclipse 8.6 在 jsp 中编写 ExtJS时&#xff0c;会出现卡死现象&#xff0c;让人甚是头疼。网上找了很多方法&#xff0c;折腾半天&#xff0c;还是不管用。 什么MyEclipse 优化&#xff0c;Validation 取消&#xff0c;MyEclipse 在 JSP 中打 "点" 时&#xff0…

java的aqs是什么,AQS在Java中的应用

上篇文章我们详细分析了AQS的底层实现原理,这节就来探索jdk中使用AQS实现的工具类ReentrantLock一, 是什么?怎么用?是什么?是一个独占锁,也就是在并发环境下同一时刻只能有一个线程获得资源,也是一个可重入锁.可重入锁: 一个线程已经获取到了该资源,下次再次获取资源时不会出…

php怎么把字符转成大写,php怎么把字符串转换为大写

php把字符串转换为大写的方法&#xff1a;可以利用内置函数strtoupper()来进行转换。strtoupper()函数可以把指定的字符串转换为大写&#xff0c;并返回被转换为大写的字符串。使用函数&#xff1a;(学习视频推荐&#xff1a;php视频教程)strtoupper() 函数把字符串转换为大写&…

oracle存储过程与函数的区别及作用,Oracle存储过程与存储函数-入门

文章思维导图一. 存储过程和存储函数的定义定义&#xff1a;存储在数据库中&#xff0c;供所有用户程序调用的子程序叫做存储过程/存储函数。复杂点的解释&#xff1a;存储过程(Stored Procedure)&#xff0c;就是一组用于完成特定数据库功能的SQL 语句集&#xff0c;该SQL语句…

CC++初学者编程教程(8) VS2013配置编程助手与QT

1. 2. 配置编程助手 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19&#xff0e; 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30&#xff0e; 31. 32&#xff0e; 33. 34. 35. 36. 37. 38. 39 40 41 42 43 这个时候我们就在VS2013里面集成了QT&#xff0c;编程助…

转子碰磨 matlab,航空科普:什么是航空发动机转子碰磨?

中国航空报讯&#xff1a;随着人们对航空发动机油耗、推重比等要求的逐步提高&#xff0c;提升航空发动机运行效率&#xff0c;尤其是提升民用航空发动机的经济性已经变得越来越重要。航空发动机的总体运行效率是气动效率、燃烧效率、冷却效率与机械效率等共同决定的。其中&…

Android 自定义 ListView 显示网络上 JSON 格式歌曲列表

本文内容 环境 项目结构 演示自定义 ListView 显示网络上 JSON 歌曲列表 参考资料 本文最开始看的是一个国人翻译的文章&#xff0c;没有源代码可下载&#xff0c;根据文中提供的代码片段&#xff0c;自己新建的项目&#xff08;比较可恶的是&#xff0c;没有图标图片资源&…

oracle 索引invisible,Oracle index unusable和invisible的区别

invisible index会被优化器所忽略&#xff0c;但是dml操作仍然会维护索引。在session或者system级别使用参数OPTIMIZER_USE_INVISIBLE_INDEX摘录自Oracle 11g的官方文档&#xff1a;UNUSABLE Specify UNUSABLE to mark the index or index partition(s) or index subpartition(…

php16进制密钥签名对接支付,简单理解rsa的加密和签名-PHP实现

我们先动手在linux上生成一下rsaPs&#xff1a;openssl是一堆加密算法和安全协议的开源集合,像RSA,DES,MD5,RC4等等,都能在openssl里面找到源代码.用openssl指定生成test.key文件&#xff0c;其中包含公钥私钥&#xff0c;1024为生成密钥长度tbtb:~/mimi$ openssl genrsa -out …

java中字符和字节的转换_Java最佳实践–字符到字节和字节到字符的转换

java中字符和字节的转换在使用Java编程语言时&#xff0c;我们将继续讨论与建议的实践有关的系列文章&#xff0c;我们将讨论String性能调优。 特别是&#xff0c;我们将着重于使用默认编码时如何有效地处理字符到字节和字节到字符的转换。 本文总结了两种建议的自定义方法与两…

Android实现简单短信发送器

布局&#xff1a; <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"xmlns:tools"http://schemas.android.com/tools"android:id"id/container"android:layout_width"match_parent"android:layout_heigh…