CRUD全棧式編程架構之MVC的擴展設計

MVC執行流程

路由的擴展

我理解的路由做用有如下幾個html

  •  Seo優化,用「/」分開的url爬蟲更愛吃
  •  物理和邏輯文件分離,url再也不按照文件路徑映射
  •  Controller,Action的選擇

 

MVC路由的擴展

實話實說MVC的路由我不多去作擴展,在MVC4時代,還會去重寫掉url的大小寫,而在MVC5以後,MVC自帶了配置去小寫化url。不過有一個配置仍是必需要提一下那就是Area,在你的系統達到必定規模以後,Controllers經過Area來管理將會變得更容易。這裏給出個人Area擴展,很簡單可是很重要,注意子類必須以AreaRegistration結尾,一樣遵循約定有限於配置的原則,固然你也能夠重寫。web

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部分有點糟糕,等我回公司拿到源代碼再修改。json

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有三大部分api

  •  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>。ide

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執行流程串起來
  •  主體設計已經講完了,源代碼整理中,工做比較忙,見諒,
  •  喜歡請關注,有問題請留言,頭疼得一筆,睡覺,晚安
相關文章
相關標籤/搜索