博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
CRUD全栈式编程架构之MVC的扩展设计
阅读量:6339 次
发布时间:2019-06-22

本文共 13493 字,大约阅读时间需要 44 分钟。

MVC执行流程

路由的扩展

我理解的路由作用有以下几个

  •  Seo优化,用“/”分开的url爬虫更爱吃
  •  物理和逻辑文件分离,url不再按照文件路径映射
  •  Controller,Action的选择

 

MVC路由的扩展

实话实说MVC的路由我很少去做扩展,在MVC4时代,还会去重写掉url的大小写,而在MVC5之后,MVC自带了配置去小写化url。不过有一个配置还是必须要提一下那就是Area,在你的系统达到一定规模之后,Controllers通过Area来管理将会变得更容易。这里给出我的Area扩展,很简单但是很重要,注意子类必须以AreaRegistration结尾,同样遵循约定有限于配置的原则,当然你也可以重写。

public abstract class AreaRegistrationBase : AreaRegistration   {       public override string AreaName       {           get {               var item = GetType().Name;               return item.Replace("AreaRegistration", "");           }       }       public override void RegisterArea(AreaRegistrationContext context)       {            context.MapLowerCaseUrlRoute(            AreaName + "_default",            AreaName.ToLower() + "/{controller}/{action}/{id}",            new { action = "Index", id = UrlParameter.Optional }            );            GlobalConfiguration.Configuration.Routes.MapHttpRoute(                AreaName + "Api",                "api/" + AreaName + "/{controller}/{action}/{id}",                new { area = AreaName, id = RouteParameter.Optional, namespaceName = new[] { this.GetType().Namespace } }            );       }   }

 

WebApi路由的扩展

上面MVC的路由也注册了Api的路由,当然还有更好的方法,由于WebApi,基本不需要Url.Action和HtmlAction,这种路由出栈的策略,不需要去通过路由生成Url,所以

我们只需要做到路由的解析即可,并且webapi并没有提供自带的area机制,所以我扩展了DefaultHttpControllerSelector,获取到路由中area和controller参数
然后完成拼接,然后反射类型查找,在最开始时候预先缓存了所有controller,所以性能还不错,由于原代码是反编译获得,所以linq部分有点糟糕,等我回公司拿到源代码再修改。

using System;using System.Collections.Generic;using System.Linq;using System.Net.Http;using System.Text;using System.Web.Http;using System.Web.Http.Controllers;using System.Web.Http.Dispatcher;namespace Coralcode.Webapi.Route{  public class AreaHttpControllerSelector : DefaultHttpControllerSelector  {    public static string CoralControllerSuffix = "ApiController";    private readonly HttpConfiguration _configuration;    private readonly Lazy
> _apiControllerTypes; private ILookup
ApiControllerTypes { get { return this._apiControllerTypes.Value; } } public AreaHttpControllerSelector(HttpConfiguration configuration) : base(configuration) { this._configuration = configuration; this._apiControllerTypes = new Lazy
>(new Func
>(this.GetApiControllerTypes)); } private ILookup
GetApiControllerTypes() { return Enumerable.ToLookup
((IEnumerable
) ServicesExtensions.GetHttpControllerTypeResolver(this._configuration.Services).GetControllerTypes(ServicesExtensions.GetAssembliesResolver(this._configuration.Services)), (Func
) (t => t.Name.ToLower().Substring(0, t.Name.Length - AreaHttpControllerSelector.CoralControllerSuffix.Length)), (Func
) (t => t)); } public override HttpControllerDescriptor SelectController(HttpRequestMessage request) { string controllerName = this.GetControllerName(request); if (!string.IsNullOrWhiteSpace(controllerName)) { List
list = Enumerable.ToList
(this.ApiControllerTypes[controllerName.ToLower()]); if (Enumerable.Any
((IEnumerable
) list)) { IDictionary
values = HttpRequestMessageExtensions.GetRouteData(request).Values; string endString; if (values.Count > 1) { StringBuilder stringBuilder = new StringBuilder(); if (values.ContainsKey("area")) { stringBuilder.Append('.'); stringBuilder.Append(values["area"]); stringBuilder.Append('.'); stringBuilder.Append("controllers"); } if (values.ContainsKey("controller")) { stringBuilder.Append('.'); stringBuilder.Append(values["controller"]); stringBuilder.Append(AreaHttpControllerSelector.CoralControllerSuffix); } endString = stringBuilder.ToString(); } else endString = string.Format(".{0}{1}", (object) controllerName, (object) AreaHttpControllerSelector.CoralControllerSuffix); Type controllerType = Enumerable.FirstOrDefault
((IEnumerable
) Enumerable.OrderBy
(Enumerable.Where
((IEnumerable
) list, (Func
) (t => t.FullName.EndsWith(endString, StringComparison.CurrentCultureIgnoreCase))), (Func
) (t => Enumerable.Count
((IEnumerable
) t.FullName, (Func
) (s => (int) s == 46))))); if (controllerType != (Type) null) return new HttpControllerDescriptor(this._configuration, controllerName, controllerType); } } return base.SelectController(request); } }}

 

Controller激活

Controller激活这部分是MVC和Ioc结合的核心,Ioc有三大部分

  •  Register 类型的发现和注册,解决如何发现那些类型是需要注册的和如何注册,这里我采用Attribute的方式去发现,用Unity自带的注册方法注册
  •  Resolve  类型的解析,如何知道某个类型和将其实例化
  •  LiftTime  如何控制实例的生命周期,例如是否使用单例,生命周期也采用Unity自带的,主要用到单例和每次解析都一个实例

MVC需要和Ioc激活首先我们关注的就是哪里注册和哪里去实例化,由于MVC使用的是约定优先于配置的方式,所有的Controller都是以“Controller”结尾,所以这里我直接加载

程序集,然后找到所有类型以Controller结尾的去注册就好了,激活的话有以下三种方式,难易程度依次递增,嵌套深度也依次递增,这里我采用重写
默认DefaultControllerFactory的方式去激活,这样既可以用自己的也结合其他机制去实现,其他方式大家可以自行扩展。

  •  ControllerFactory
  •  IControllerActivetor
  •  IDependencyResolver

 

using Coralcode.Framework.Aspect.Unity;using Coralcode.Mvc.Resources;using System;using System.Web;using System.Web.Mvc;using System.Web.Routing;namespace Coralcode.Mvc.ControllerFactory{  public class UnityControllerFactory : DefaultControllerFactory  {    public override IController CreateController(RequestContext requestContext, string controllerName)    {      return base.CreateController(requestContext, controllerName);    }    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)    {      if (controllerType == (Type) null)        throw new HttpException(404, Messages.MvcBase_NotFoundPage);      if (!UnityService.HasRegistered(controllerType))        return base.GetControllerInstance(requestContext, controllerType);      return (IController) UnityService.Resolve(controllerType);    }  }}//webapi 激活如下using Coralcode.Framework.Aspect.Unity;using System;using System.Net.Http;using System.Web.Http.Controllers;using System.Web.Http.Dispatcher;namespace Coralcode.Webapi.ControllerFactory{  public class UnityControllerActivator : IHttpControllerActivator  {    public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)    {      IHttpController httpController = (IHttpController) UnityService.Resolve(controllerType);      HttpRequestMessageExtensions.RegisterForDispose(request, httpController as IDisposable);      return httpController;    }  }}

 

Filter执行

LogFilter

这里基本上都是Mvc的源代码,只是增加了一个日志功能而已,扒MVC源代码大家一定去尝试

using Coralcode.Framework.Log;using System;using System.Web;using System.Web.Mvc;namespace Coralcode.Mvc.Filters{  [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]  public class LogExceptionAttribute : HandleErrorAttribute  {    public override void OnException(ExceptionContext filterContext)    {      if (filterContext == null)        throw new ArgumentNullException("filterContext");      Exception exception = filterContext.Exception;      LoggerFactory.Instance.Error(exception.ToString());      if (filterContext.IsChildAction || filterContext.ExceptionHandled || (!filterContext.HttpContext.IsCustomErrorEnabled || new HttpException((string) null, exception).GetHttpCode() != 500) || !this.ExceptionType.IsInstanceOfType((object) exception))        return;      this.HandlerViewResultException(filterContext);    }    private void HandlerViewResultException(ExceptionContext filterContext)    {      string controllerName = (string) filterContext.RouteData.Values["controller"];      string actionName = (string) filterContext.RouteData.Values["action"];      HandleErrorInfo model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);      ExceptionContext exceptionContext = filterContext;      ViewResult viewResult1 = new ViewResult();      viewResult1.ViewName = this.View;      viewResult1.MasterName = this.Master;      viewResult1.ViewData = (ViewDataDictionary) new ViewDataDictionary
(model); viewResult1.TempData = filterContext.Controller.TempData; ViewResult viewResult2 = viewResult1; exceptionContext.Result = (ActionResult) viewResult2; filterContext.ExceptionHandled = true; filterContext.HttpContext.Response.Clear(); filterContext.HttpContext.Response.StatusCode = 500; filterContext.HttpContext.Response.TrySkipIisCustomErrors = true; } }}

 

ResultFilter

这里统一处理了Ajax请求返回数据以ResultMessage返回,如果不是JsonResult选择忽略,具体设计初衷可以看Controller设计那一节.

using Coralcode.Framework.Models;using Coralcode.Mvc.ActionResults;using System.Web.Mvc;namespace Coralcode.Mvc.Filters{  public class ResultMessageAttribute : ActionFilterAttribute  {    public override void OnResultExecuting(ResultExecutingContext filterContext)    {      JsonResult jsonResult = (JsonResult) (filterContext.Result as CustomJsonResult) ?? filterContext.Result as JsonResult;      if (jsonResult == null)        return;      jsonResult.Data = this.GetResultMessage(jsonResult.Data);      filterContext.Result = (ActionResult) jsonResult;    }    private object GetResultMessage(object data)    {      if (data is BaseMessage)        return data;      return (object) new ResultMessage(ResultState.Success, "Success", data);    }  }}

 

Action执行

提高并发利器,最佳使用方式,这里详细的介绍可以结合Artec的Controller同步和异步这一节,其中我个人推荐的就是采用返回Task<ActionResult>。

ValueProvider和ModelBinder

这里会将Url中的参数作为Model绑定的数据源。在做三级菜单的时候可以体现出用途,例如我首先在数据字典中添加如下数据

var newsType = new Glossary()           {               Key = "NewsType",               Value = "NewsType",               Description = "新闻",               Seq = 0,               Title = "新闻类型",               ParentId = -1,           };           _glossaryService.Add(newsType);           _glossaryService.Add(new Glossary()           {               Key = "RecentNews",               Value = "RecentNews",               Description = "新闻类型",               Seq = 1,               Title = "最新活动",               ParentId = newsType.Id,           });           _glossaryService.Add(new Glossary()           {               Key = "UserActivity",               Value = "UserActivity",               Description = "新闻类型",               Seq = 2,               Title = "会员活动",               ParentId = newsType.Id,           });           _glossaryService.Add(new Glossary()           {               Key = "OnlineMarket",               Value = "OnlineMarket",               Description = "新闻类型",               Seq = 3,               Title = "在线商城",               ParentId = newsType.Id,           });           _glossaryService.Add(new Glossary()           {               Key = "AboutUs",               Value = "AboutUs",               Description = "新闻类型",               Seq = 4,               Title = "关于我们",               ParentId = newsType.Id,           });           Repository.UnitOfWork.Commit();

 然后从字段中读出数据去添加菜单

var newsMenu = Regist("新闻管理", "/portal/home/handerindex?menuId=" + systemManagerMenu.Identify + "-NewsType",             systemManagerMenu.Identify, systemManagerMenu.Identify + "-NewsType");         //新闻管理         _glossaryService.GetFiltered("新闻类型").ForEach(item =>         {             Regist(item.Title,string.Format( "/portal/news/index?typeid={0}&type={1}" , item.Id,item.Title),                 newsMenu.Identify, newsMenu.Identify + "-" + item.Id);         });

 加载三级菜单

///       /// 处理界面      ///       /// 
public ActionResult HanderIndex(string menuId) { ViewBag.Tree = string.Empty; //TODO:请求两次,待处理 if (menuId == null) return View(); var items = _menuService.GetChildrenMenus(menuId); ViewBag.Tree = JsonConvert.SerializeObject(items); return View(); }

 界面如图

然后在Search和ViewModel中有一个字段是TypeId,这样在List,PageSearch,AddOrEdit中就可以自动绑定值了。 

public  class NewsSearch:SearchBase   {       public long? TypeId { get; set; }              public string Title { get; set; }   }

 

View发现和ActionResult执行

MVC默认的系统比较弱,当controller和view比较多的时候一个文件下面内容会非常多,我这里做了一个模块化处理.

using System.Web.Mvc;namespace Coralcode.Mvc.ViewEngines{  public class ThemesRazorViewEngine : RazorViewEngine  {    public ThemesRazorViewEngine()    {      this.AreaViewLocationFormats = new string[3]      {        "~/Themes/{2}/{1}/{0}.cshtml",        "~/Themes/Shared/{0}.cshtml",        "~/Themes/{2}/Shared/{0}.cshtml"      };      this.AreaMasterLocationFormats = new string[1]      {        "~/Themes/Shared/{0}.cshtml"      };      this.AreaPartialViewLocationFormats = new string[4]      {        "~/Themes/{2}/{1}/{0}.cshtml",        "~/Themes/{2}/Shared/{0}.cshtml",        "~/Themes/Shared/{0}.cshtml",        "~/Themes/Shared/Control/{0}.cshtml"      };      this.ViewLocationFormats = new string[2]      {        "~/Themes/{1}/{0}.cshtml",        "~/Themes/Shared/{0}.cshtml"      };      this.MasterLocationFormats = new string[1]      {        "~/Themes/Shared/{0}.cshtml"      };      this.PartialViewLocationFormats = new string[2]      {        "~/Themes/{1}/{0}.cshtml",        "~/Themes/Shared/{0}.cshtml"      };    }    public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)    {      if (controllerContext.RouteData.Values.ContainsKey("area") && !controllerContext.RouteData.DataTokens.ContainsKey("area"))        controllerContext.RouteData.DataTokens.Add("area", controllerContext.RouteData.Values["area"]);      return base.FindView(controllerContext, viewName, masterName, useCache);    }  }

 项目目录结构如下图

 

这样子发布后的目录非常的干净,如图:

JsonResult执行和MetaData(模型元数据提供机制)

这部分在Controller设计那一节有详细说明,请往前一步参考

总结

  •  大家一定要看看MVC源代码,
  •  脑袋里要能把MVC执行流程串起来
  •  主体设计已经讲完了,源代码整理中,工作比较忙,见谅,
  •  喜欢请关注,有问题请留言,头疼得一笔,睡觉,晚安

转载于:https://www.cnblogs.com/Skyven/p/5679767.html

你可能感兴趣的文章
Jquery闪烁提示特效
查看>>
最佳6款用于移动网站开发的 jQuery 图片滑块插件
查看>>
C++ String
查看>>
获取系统托盘图标的坐标及文本
查看>>
log4j Test
查看>>
HDU 1255 覆盖的面积(矩形面积交)
查看>>
SQL数据库无法附加,提示 MDF" 已压缩,但未驻留在只读数据库或文件组中。必须将此文件解压缩。...
查看>>
第二十一章流 3用cin输入
查看>>
在workflow中,无法为实例 ID“...”传递接口类型“...”上的事件“...” 问题的解决方法。...
查看>>
获取SQL数据库中的数据库名、所有表名、所有字段名、列描述
查看>>
Orchard 视频资料
查看>>
简述:预处理、编译、汇编、链接
查看>>
调试网页PAIP HTML的调试与分析工具
查看>>
路径工程OpenCV依赖文件路径自动添加方法
查看>>
玩转SSRS第七篇---报表订阅
查看>>
WinCE API
查看>>
SQL语言基础
查看>>
对事件处理的错误使用
查看>>
最大熵模型(二)朗格朗日函数
查看>>
html img Src base64 图片显示
查看>>