MVC5项目结构
带有Areas和Filter的项目结构
一般来说,小的MVC项目是不考虑领域的,但是,如果是稍微复杂一点的项目,往往是需要领域这个概念的。
一个领域就是一个小型的MVC项目,所以领域Area的目录结构和普通的目录结构是一样的。(具有Controllers和Views目录,以及一个AreaRegistration文件) 一个MVC项目,Controllers和Views这两个目录由于约定的关系,文件夹的名称和相对位置是不能变化的。 在默认的配置下,Controllers下面的Controller和Views下面的子文件夹是一一对应的。Controller里面的方法和View下面的CSHTML视图也是一一对应的。 Model这个文件夹不是必须的,而且按照趋势,Model一般被归为BussinessLogic,往往存在于业务的工程中。数据模型的问题,这里不进行讨论。AreaRegistration文件
一个AreaRegistration文件是这样的: AdminAreaRegistration.cs 请注意命名规范,MVC这样的约定氛围很浓重的框架,最好按照规定命名。
using System.Web.Mvc;namespace WebSite.Areas.Admin{ public class AdminAreaRegistration : AreaRegistration { public override string AreaName { get { return "Admin"; } } public override void RegisterArea(AreaRegistrationContext context) { context.MapRoute( "Admin_default", "Admin/{controller}/{action}/{id}", new {controller = "Home", action = "Index", id = UrlParameter.Optional} ); } }}
当然使得这个Area注册生效的源头是Global.asax 里面的 RegisterAllAreas 方法
public class MvcApplication : HttpApplication { protected void Application_Start() { //MVC AreaRegistration.RegisterAllAreas(); GlobalConfiguration.Configure(WebApiConfig.Register); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes);
RouteConfig.cs(位于App_Start文件夹下面)可以设定默认的领域。
using System.Web.Mvc;using System.Web.Routing;namespace WebSite{ public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute("Default", "{area}/{controller}/{action}/{id}", new {area = "Admin", controller = "Home", action = "Index", id = UrlParameter.Optional}, new[] {"WebSite.Areas.Admin.*"} ).DataTokens.Add("area", "Admin"); } }}
Filter
Filter也不是MVC的标配,但是往往一个复杂的项目会有一些Filter。Filter可以完成很多不同的工作,对于某个环节的输入和输出进行一些干预。当然Filter也必须注册才能使用。FilterConfig.cs
using System.Web.Mvc;namespace WebSite{ public class FilterConfig { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { //默认错误处理 filters.Add(new HandleErrorAttribute()); //日志 filters.Add(new LoggerAttribute()); //异常记录 filters.Add(new ExceptionHandlerAttribute()); //压缩 filters.Add(new CompressAttribute()); } }}
压缩
using System.IO.Compression;using System.Web.Mvc;namespace WebSite{ OnActionExecuting的时候,可以设定输出的压缩 public class CompressAttribute : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext filterContext) { var acceptEncoding = filterContext.HttpContext.Request.Headers["Accept-Encoding"]; if (!string.IsNullOrEmpty(acceptEncoding)) { acceptEncoding = acceptEncoding.ToLower(); var response = filterContext.HttpContext.Response; if (acceptEncoding.Contains("gzip")) { response.AppendHeader("Content-encoding", "gzip"); response.Filter = new GZipStream(response.Filter, CompressionMode.Compress); } else if (acceptEncoding.Contains("deflate")) { response.AppendHeader("Content-encoding", "deflate"); response.Filter = new DeflateStream(response.Filter, CompressionMode.Compress); } } } }}
错误处理
OnException 出现错误的时候可以进行一些处理
using System.Web.Mvc;using InfraStructure.Log;using InfraStructure.Utility;namespace WebSite{ public class ExceptionHandlerAttribute : HandleErrorAttribute { public override void OnException(ExceptionContext actionExecutedContext) { var actionName = actionExecutedContext.RouteData.Values["action"].ToString(); var controllerName = actionExecutedContext.RouteData.Values["controller"].ToString(); var username = string.Empty; if (actionExecutedContext.HttpContext.Session[ConstHelper.Username] != null) { username = actionExecutedContext.HttpContext.Session[ConstHelper.Username].ToString(); } ExceptionLog.Log(username, actionName, controllerName, actionExecutedContext.Exception.StackTrace); } }}
日志
如果希望每个Action都有执行日志可以这样,OnActionExecuted之后,可以添加一些动作
using System.Web.Mvc;using InfraStructure.Log;using InfraStructure.Utility;namespace WebSite{ public class LoggerAttribute : ActionFilterAttribute { public override void OnActionExecuted(ActionExecutedContext filterContext) { var actionName = filterContext.ActionDescriptor.ActionName; var controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName; var username = string.Empty; if (filterContext.HttpContext.Session[ConstHelper.Username] != null) { username = filterContext.HttpContext.Session[ConstHelper.Username].ToString(); } InfoLog.Log(username, actionName, controllerName); } }}
安全
如果每个Controller都进行相同的安全检查,代码量很庞大,可以设定一个SecurityController,然后所有的Controller都继承与SecurityController。
using InfraStructure.Helper;using InfraStructure.Log;using InfraStructure.Table;using InfraStructure.Utility;namespace WebSite.Areas.Admin.Controllers{ public class DataViewSetController : SecurityController { // GET: Develop/DataViewSet public ActionResult Index() { var list = OwnerTableOperator.GetRecListByOwnerId(DataViewSet.CollectionName, OwnerId); //MasterTable Sort Function //list.Sort((x, y) => { return x.Rank - y.Rank; }); ViewData.Model = list; return View(); }
本质上还是在运行Action的时候(OnActionExecuting),进行一些抢先过滤。
using System.Web.Mvc;using BussinessLogic.Security;using InfraStructure.Utility;namespace WebSite{ public class SecurityController : Controller { ////// 验证 /// /// protected override void OnActionExecuting(ActionExecutingContext filterContext) { if (Session[ConstHelper.OwnerId] == null) { filterContext.Result = RedirectToAction("Index", "Home", new { area = "Admin" }); return; } OwnerId = Session[ConstHelper.OwnerId].ToString(); EmployeeInfoType = Session[ConstHelper.EmployeeInfoType].ToString(); Username = Session[ConstHelper.Username].ToString(); AccountCode = Session[ConstHelper.Account].ToString(); Privilege = Session[ConstHelper.Privilege].ToString().GetEnum(PrivilegeType.None); ViewBag.Privilege = Privilege; ViewBag.OwnerId = OwnerId; } }}
MVC6
Area
如果你上网检索关于Area的信息,下面的文章大概会引起你的关注,可惜里面的Sample已经没有了。
如果你想完整的看一个MVC6带有Area的例子,MusicStore则应该可以满足你的需求。
Area的目录结构还是和MVC5一样:MusicStore/Areas/Admin/
这个也没有什么好修改的。至于Area的路由问题,将在路由里面进一步讨论。Filter
下面这篇文章很好的介绍了Filter的问题,目录结构还是和MVC5一样(原作者已经更新到RC2了)
Because the filters will be used as a ServiceType, the different custom filters need to be registered with the framework IoC. If the action filters were used directly, this would not be required.
这里也是需要为Filter进行注册了,只是注册的方式变成下面的方式:
public void ConfigureServices(IServiceCollection services){ services.AddMvc(); services.AddScoped(); services.AddScoped (); services.AddScoped ();}
工具制作(计划中)
界面和整体流程
我在考虑是否要做这样一个工具:
工具的界面如下所示,两个文本框,一个是MVC5目录,一个是MVC6目录。一个升级按钮。 然后一键可以将MVC5 尽可能 得升级到MVC6。整体工具的框架也很简单
////// Start To Upgrade MVC5 to MVC6 /// /// /// private void btnUpgrade_Click(object sender, EventArgs e) { //测试用开始 txtMVC5Root.Text = @"E:\WorkSpace\DominoHR\WebSite"; txtMVC6Root.Text = @"E:\WorkSpace\MVCMigratiorLib"; //测试用结束 SystemManager.mvc5Root = txtMVC5Root.Text; SystemManager.mvc6Root = txtMVC6Root.Text; //Init(Log准备) SystemManager.Init(); //Analyze The Folder StructureAnalyze.Analyze(); //Upgrade MainUpgrade.Upgrade(); //Terminte(Log关闭) SystemManager.Terminate(); }
这里的代码省略LOG输出等次要但是必须的功能介绍,一个好的工具必须有LOG。同时,这个工具不能对原来的MVC5文件进行任何的修改,这个是大原则,所有的文件都必须复制到新的目录下面进行修改
在考虑MVC6的目录之前,我们先来看看如何分析MVC5的目录结构。
这里很简单,就是把顶层目录都遍历一遍即可,没有什么技术含量。当然,由于目录信息都保存起来了,很容易做出HasArea,HasFilter这样的属性方法。////// Has Areas /// public static bool HasAreas { get { return RootFolder.ContainsKey(strAreas); } } ////// Analyze /// public static void Analyze() { //Get Top Level Folder List foreach (var topLevelFolder in Directory.GetDirectories(SystemManager.mvc5Root)) { string folderName = new FileInfo(topLevelFolder).Name; RootFolder.Add(folderName, topLevelFolder); SystemManager.Log("topLevelFolder:" + folderName); } AppendReport(); }
本文已经同步到 http://www.codesnippet.info/Article/Index?ArticleId=00000024