ASP.NET从MVC5升级到MVC6 总目录

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已经没有了。
using areas in asp-net-5

如果你想完整的看一个MVC6带有Area的例子,MusicStore则应该可以满足你的需求。
MusicStore示例

Area的目录结构还是和MVC5一样:MusicStore/Areas/Admin/
这个也没有什么好修改的。至于Area的路由问题,将在路由里面进一步讨论。

Filter

下面这篇文章很好的介绍了Filter的问题,目录结构还是和MVC5一样(原作者已经更新到RC2了)

asp-net-5-action-filters

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
ASP.NET从MVC5升级到MVC6 总目录