• 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执行流程串起来
    •  主体设计已经讲完了,源代码整理中,工作比较忙,见谅,
    •  喜欢请关注,有问题请留言,头疼得一笔,睡觉,晚安
  • 相关阅读:
    WCF系列(五) 也谈序列化(下)
    优秀程序员的十个习惯
    WCF系列(九) WCF安全系列(四) WSHttpBinding绑定之Transport安全模式
    TCP/IP 协议简单分析
    技巧:在Silverlight应用程序中进行数据验证
    WCF系列(三) WCF配置文件注释
    [转]ASP.NET(C#)常用代码30例
    服务器端 在iframe中控制父窗体div并调用父窗体Button
    GridView的模版列中加入按钮,触发按钮事件后,如何获取该行的某个值?
    通用分页 模块
  • 原文地址:https://www.cnblogs.com/Skyven/p/5679767.html
Copyright © 2020-2023  润新知