CRUD全栈式编程架构之MVC的扩展设计
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<ILookup<string, Type>> _apiControllerTypes; private ILookup<string, Type> ApiControllerTypes
{
get
{
return this._apiControllerTypes.Value;
}
} public AreaHttpControllerSelector(HttpConfiguration configuration)
: base(configuration)
{
this._configuration = configuration;
this._apiControllerTypes = new Lazy<ILookup<string, Type>>(new Func<ILookup<string, Type>>(this.GetApiControllerTypes));
} private ILookup<string, Type> GetApiControllerTypes()
{
return Enumerable.ToLookup<Type, string, Type>((IEnumerable<Type>) ServicesExtensions.GetHttpControllerTypeResolver(this._configuration.Services).GetControllerTypes(ServicesExtensions.GetAssembliesResolver(this._configuration.Services)), (Func<Type, string>) (t => t.Name.ToLower().Substring(0, t.Name.Length - AreaHttpControllerSelector.CoralControllerSuffix.Length)), (Func<Type, Type>) (t => t));
} public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
string controllerName = this.GetControllerName(request);
if (!string.IsNullOrWhiteSpace(controllerName))
{
List<Type> list = Enumerable.ToList<Type>(this.ApiControllerTypes[controllerName.ToLower()]);
if (Enumerable.Any<Type>((IEnumerable<Type>) list))
{
IDictionary<string, object> 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<Type>((IEnumerable<Type>) Enumerable.OrderBy<Type, int>(Enumerable.Where<Type>((IEnumerable<Type>) list, (Func<Type, bool>) (t => t.FullName.EndsWith(endString, StringComparison.CurrentCultureIgnoreCase))), (Func<Type, int>) (t => Enumerable.Count<char>((IEnumerable<char>) t.FullName, (Func<char, bool>) (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<HandleErrorInfo>(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);
});
加载三级菜单
/// <summary>
/// 处理界面
/// </summary>
/// <returns></returns>
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执行流程串起来
- 主体设计已经讲完了,源代码整理中,工作比较忙,见谅,
- 喜欢请关注,有问题请留言,头疼得一笔,睡觉,晚安
CRUD全栈式编程架构之MVC的扩展设计的更多相关文章
- CRUD全栈式编程架构之导入导出的设计
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.We ...
- CRUD全栈式编程架构之更精简的设计
精简的程度 ViewModel精简 服务精简 控制器精简 Index.cshmtl精简 AddOrEdit.cshtml精简 效果:最精简的情况下,只需要写Entity这一个数据库实体然后加上一些简单 ...
- CRUD全栈式编程架构之数据层的设计
CodeFirst 一直以来我们写应用的时候首先都是创建数据库 终于在orm支持codefirst之后,我们可以先建模. 通过模型去创建数据库,并且基于codefirst可以实现方便的 实现数据库迁移 ...
- CRUD全栈式编程架构之界面层的设计
Layout的设计 模板模式 mvc的模板特别类似设计模式中模板方法模式,结合Layout中RenderSection和RenderBody方法可以将部分html展现逻辑延迟到具体的视图页面去实现里面 ...
- CRUD全栈式编程架构之服务层的设计
服务层代码 首先我先放出2个主要类的代码再分别讲解 接口 using System; using System.Collections.Generic; using System.Linq; usin ...
- CRUD全栈式编程架构之控制器的设计
页面 这里界面我采用jquery miniui来做的,当你完全了解了整个设计之后可以轻松切换到其他的js框架,个人认为类似muniui,easyui等等这类可以将web界面做得和winform类似的框 ...
- CRUD全栈式编程架构总结
这里放出实例代码 github.com/SkyvenXiong/HCC
- CRUD全栈式编程概述
业务场景 CRUD,从数据驱动的角度几乎所有的的业务都是在做这样的事情. 几乎所有的操作都是在做对表的增删改查. 假设我们将数据库数据规个类: 分为基础/配置数据和业务/增长数据,或者说静态数据 ...
- 全栈式JavaScript
如今,在创建一个Web应用的过程中,你需要做出许多架构方面的决策.当然,你会希望做的每一个决定都是正确的:你想要使用能够快速开发的技术,支持持续的迭代,最高的工作效率,迅速,健壮性强.你想要精益求精并 ...
随机推荐
- sql replace()函数的用法
replace()函数的用法: replace('带操作的字符串','被换掉的内容'[要换的内容,可写可不写默认为null]) 先上一张图 下面我门对jxid进行操作: select replace( ...
- 练习五十六:for循环
某个公司采用公用电话传递数据,数据是四位的整数,在传递过程中是加密的,加密规则如下:每位数字都加上5,然后用和除以10的余数代替该数字,再将第一位和第四位交换,第二位和第三位交换 方法一: def o ...
- 08-----pymysql模块使用
pymysql的下载和使用 exctue() 之sql注入 增.删.改:conn.commit() 查:fetchone.fetchmany.fetchall 一.pytmysql的下载和使用 ...
- mysql5.7导出到csv
版本:mysql5.7 SELECT * FROM table_name where xxx into outfile 'C:/ProgramData/MySQL/MySQL Server 5.7/U ...
- python3.7安装Scrapy
环境:windows 7. 安装过程中遇到的问题 1.error: Unable to find vcvarsall.bat 2.1083: Cannot open include file: 'ba ...
- Technical Committee Weekly Meeting 2016.06.21
Meeting time: 2016.June.21 1:00~2:00 Chairperson: Thierry Carrez Meeting summary: 1.Add current hou ...
- 使用Advanced Installer进行二次打包
使用Advanced Installer进行二次打包 在上一篇使用InstallerShield打包VS程序中,我已经叙述过,为什么要进行二次打包的问题,在此我就不再赘述.本次长枪直入,说一说如何使用 ...
- Hibernate课程 初探多对多映射2-3 配置映射文件
本节主要内容:配置映射文件 Project.hbm.xml <hibernate-mapping> <class name="com.ddwei.entity.Projec ...
- 从零开始的全栈工程师——js篇(作用域 this 原型笔试题练习)
作用域 // 1. fn() function fn () { console.log(12) } var as = function () { console.log(45) } // 2. var ...
- 微信小程序电商实战-首页(下)
好了,上一期我们把首页搜索.导航栏和广告轮播给做完了,那么接下来会继续完成我们首页的剩余部分,先看我们要实现的效果吧! 本期实现效果图.gif 本期我们要实现首页的实时热销榜.福利专场和左下方个人 ...