MVC源碼分析 - Controller創建和創建擴展

来源:http://www.cnblogs.com/elvinle/archive/2017/01/16/6289545.html
-Advertisement-
Play Games

上一篇, 出現了一個至關重要的類:MvcHandler, 接下來就來看一下MvcHandler吧. 先不看具體方法, 先看一下類裡面的情況. 從上面看, 有兩種執行方式, 一種是同步的, 一種是非同步的. 那預設情況下, 其實會走非同步的方式. 但是這裡呢, 我想用同步的方式去分析, 其實過程原理都是一 ...


上一篇, 出現了一個至關重要的類:MvcHandler, 接下來就來看一下MvcHandler吧. 先不看具體方法, 先看一下類裡面的情況.

//這裡實現了兩個重要的介面, 非同步處理和同步處理的介面
public
class MvcHandler : IHttpAsyncHandler, IHttpHandler, IRequiresSessionState { // Fields private ControllerBuilder _controllerBuilder; private static readonly object _processRequestTag; internal static readonly string MvcVersion; public static readonly string MvcVersionHeaderName; // Methods static MvcHandler(); public MvcHandler(RequestContext requestContext); protected internal virtual void AddVersionHeader(HttpContextBase httpContext); protected virtual IAsyncResult BeginProcessRequest(HttpContext httpContext, AsyncCallback callback, object state); protected internal virtual IAsyncResult BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, object state); protected internal virtual void EndProcessRequest(IAsyncResult asyncResult); private static string GetMvcVersionString(); protected virtual void ProcessRequest(HttpContext httpContext); protected internal virtual void ProcessRequest(HttpContextBase httpContext); private void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory); private void RemoveOptionalRoutingParameters(); IAsyncResult IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData); void IHttpAsyncHandler.EndProcessRequest(IAsyncResult result); void IHttpHandler.ProcessRequest(HttpContext httpContext); // Properties internal ControllerBuilder ControllerBuilder { get; set; } public static bool DisableMvcResponseHeader { get; set; } protected virtual bool IsReusable { get; } public RequestContext RequestContext { get; private set; } bool IHttpHandler.IsReusable { get; } }

從上面看, 有兩種執行方式, 一種是同步的, 一種是非同步的. 那預設情況下, 其實會走非同步的方式. 但是這裡呢, 我想用同步的方式去分析, 其實過程原理都是一樣的, 只是方式不同.

 

一、解析

註意到這個類, 實現了三個介面, 那第三個介面是幹啥的呢? 先看一下這個介面的內容.

public interface IRequiresSessionState
{
}

點進去, 發現這個介面沒有任何方法, 那麼他是乾什麼的呢? 

其實他是一種標誌, 或者叫標記, 表示這個類有對Session的訪問許可權, 包括讀寫. 如果你想自定義Http處理程式, 又想操作Session的話, 記得實現這個介面.

接下來, 回歸正題了.  來看一下這裡的 PR 方法.

protected virtual void ProcessRequest(HttpContext httpContext)
{
    HttpContextBase base2 = new HttpContextWrapper(httpContext);
    this.ProcessRequest(base2);
}

protected internal virtual void ProcessRequest(HttpContextBase httpContext)
{
    IController controller;
    IControllerFactory factory;
    this.ProcessRequestInit(httpContext, out controller, out factory);
    try
    {
        controller.Execute(this.RequestContext);
    }
    finally
    {
        factory.ReleaseController(controller);
    }
}

這裡也就只有三個方法了, 我們一個一個分析.

1. ProcessRequestInit()

//MvcHandler
private
void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory) { HttpContext current = HttpContext.Current; if ((current != null) && (ValidationUtility.IsValidationEnabled(current) == true)) { ValidationUtility.EnableDynamicValidation(current); } this.AddVersionHeader(httpContext); this.RemoveOptionalRoutingParameters();
   //從路由中獲取控制器的名稱
string requiredString = this.RequestContext.RouteData.GetRequiredString("controller");
   //獲取控制器工廠 factory
= this.ControllerBuilder.GetControllerFactory();
   //利用控制器工廠創建控制器類 controller
= factory.CreateController(this.RequestContext, requiredString); if (controller == null) { throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
      MvcResources.ControllerBuilder_FactoryReturnedNull, new object[] { factory.GetType(), requiredString })); } }

1.1 從路由中獲取控制器名稱 - GetRequiredString

public string GetRequiredString(string valueName)
{
    object obj2;
    if (this.Values.TryGetValue(valueName, out obj2))
    {
        string str = obj2 as string;
        if (!string.IsNullOrEmpty(str))
        {
            return str;
        }
    }
    throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, 
    SR.GetString("RouteData_RequiredValue"), new object[] { valueName })); }

1.2 獲取創建工廠 - GetControllerFactory

public IControllerFactory GetControllerFactory()
{
    return this._serviceResolver.Current;
}

1.3 創建控制器類 - CreateController

//DefaultControllerFactory類
public
virtual IController CreateController(RequestContext requestContext, string controllerName) { if (requestContext == null) { throw new ArgumentNullException("requestContext"); } if (string.IsNullOrEmpty(controllerName)) { throw new ArgumentException(MvcResources.Common_NullOrEmpty, "controllerName"); } Type controllerType = this.GetControllerType(requestContext, controllerName); return this.GetControllerInstance(requestContext, controllerType); }

1.3.1 GetControllerType()

protected internal virtual Type GetControllerType(RequestContext requestContext, string controllerName)
{
    object obj2;
    Type type;
    if (string.IsNullOrEmpty(controllerName))
    {
        throw new ArgumentException(MvcResources.Common_NullOrEmpty, "controllerName");
    }
    if ((requestContext != null) && requestContext.RouteData.DataTokens.TryGetValue("Namespaces", out obj2))
    {
        IEnumerable<string> source = obj2 as IEnumerable<string>;
        if ((source != null) && source.Any<string>())
        {
            HashSet<string> namespaces = new HashSet<string>(source, StringComparer.OrdinalIgnoreCase);
            type = this.GetControllerTypeWithinNamespaces(requestContext.RouteData.Route, controllerName, namespaces);
            if (type == null)
            {
                bool flag = false;
                if (!flag.Equals(requestContext.RouteData.DataTokens["UseNamespaceFallback"]))
                {
                    goto Label_0092;
                }
            }
            return type;
        }
    }
Label_0092:
    if (this.ControllerBuilder.DefaultNamespaces.Count > 0)
    {
        HashSet<string> set2 = new HashSet<string>(this.ControllerBuilder.DefaultNamespaces, 
      StringComparer.OrdinalIgnoreCase); type
= this.GetControllerTypeWithinNamespaces(requestContext.RouteData.Route, controllerName, set2); if (type != null) { return type; } } return this.GetControllerTypeWithinNamespaces(requestContext.RouteData.Route, controllerName, null); }

這裡會從路由中, 獲取控制器所在的命名空間. 不管由這個限制還是沒有這個限制, 都是能正常創建控制器的. 先看這裡.

private Type GetControllerTypeWithinNamespaces(RouteBase route, string controllerName, HashSet<string> namespaces)
{
   //MVC-ControllerTypeCache.xml文件中獲取Controller的緩存
this.ControllerTypeCache.EnsureInitialized(this.BuildManager);
   //從之前獲取的緩存中, 來獲取控制器的類型 ICollection
<Type> controllerTypes = this.ControllerTypeCache.GetControllerTypes(controllerName, namespaces); switch (controllerTypes.Count) { case 0: return null; case 1: return controllerTypes.First<Type>(); } throw CreateAmbiguousControllerException(route, controllerName, controllerTypes); }

 1.3.2 GetControllerInstance()

protected internal virtual IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
    if (controllerType == null)
    {
        throw new HttpException(0x194, 
      string.Format(CultureInfo.CurrentCulture, MvcResources.DefaultControllerFactory_NoControllerFound,   
      new object[] { requestContext.HttpContext.Request.Path })); } if (!typeof(IController).IsAssignableFrom(controllerType)) { throw new ArgumentException(  
      string.Format(CultureInfo.CurrentCulture,
      MvcResources.DefaultControllerFactory_TypeDoesNotSubclassControllerBase,
      new object[] { controllerType }), "controllerType"); } return this.ControllerActivator.Create(requestContext, controllerType); }

來看一下這裡的Create方法, 看一下具體是怎麼創建的.

public IController Create(RequestContext requestContext, Type controllerType)
{
    IController controller;
    try
    {
        controller = (IController) (this._resolverThunk().GetService(controllerType) ?? 
      Activator.CreateInstance(controllerType)); }
catch (Exception exception) { throw new InvalidOperationException(
      string.Format(CultureInfo.CurrentCulture, MvcResources.DefaultControllerFactory_ErrorCreatingController,
      new object[] { controllerType }), exception); } return controller; }

這裡有一個擴展點, 像之前篇幅提到的Autofac Mvc部分, 在這裡, 反饋的GetService(controllerType)就不是一個空值了.

如果沒有搜索到這些擴展, 就會預設反射的方式來創建控制器.

Demo: 值的註意的是, 這些擴展都是要實現 IDependencyResolve 介面的, 此例子是從Autofac.Integration.Mvc.dll中來的

public class AutofacDependencyResolver : IDependencyResolver
{
    // Fields
    private readonly Action<ContainerBuilder> _configurationAction;
    private readonly ILifetimeScope _container;
    private ILifetimeScopeProvider _lifetimeScopeProvider;

    // Methods
    public AutofacDependencyResolver(ILifetimeScope container);
    public AutofacDependencyResolver(ILifetimeScope container, ILifetimeScopeProvider lifetimeScopeProvider);
    public AutofacDependencyResolver(ILifetimeScope container, Action<ContainerBuilder> configurationAction);
    public AutofacDependencyResolver(ILifetimeScope container, 
    ILifetimeScopeProvider lifetimeScopeProvider,
    Action<ContainerBuilder> configurationAction); public object GetService(Type serviceType); public IEnumerable<object> GetServices(Type serviceType); // Properties public ILifetimeScope ApplicationContainer { get; } public static AutofacDependencyResolver Current { get; } public ILifetimeScope RequestLifetimeScope { get; } }

 

2. Execute()

這裡執行的是ControllerBase的Execute方法. 是一個虛方法. 這裡的方法, 留到下一篇分析了.

protected virtual void Execute(RequestContext requestContext)
{
    if (requestContext == null)
    {
        throw new ArgumentNullException("requestContext");
    }
    if (requestContext.HttpContext == null)
    {
        throw new ArgumentException(MvcResources.ControllerBase_CannotExecuteWithNullHttpContext, "requestContext");
    }
    this.VerifyExecuteCalledOnce();
    this.Initialize(requestContext);
    using (ScopeStorage.CreateTransientScope())
    {
        this.ExecuteCore();
    }
}

 

3. ReleaseController()

這個方法, 顧名思義, 是釋放資源的. 來看一下, DefaultControllerFactory 類中的此方法

public virtual void ReleaseController(IController controller)
{
    IDisposable disposable = controller as IDisposable;
    if (disposable != null)
    {
        disposable.Dispose();
    }
}

 從上面的分析, 基本可以看到控制器的創建過程, 至於他的執行過程, 就放到下一篇去了, 內容還是很多的.

 

二、擴展

從上面的分析能看到這裡有一個依賴註入擴展, 那麼下麵, 就這個依賴註入擴展, 來舉一個小例子.(殘破的例子, 別介意, 能演示功能的)

用的Autofac, 不過與上面不同, 並沒有引用上面的程式集, 只引用Autofac.dll一個程式集就可以了.

我先從之前Autofac篇章, 弄了一個Helper類過來, 精簡一下.

public class IocContainer
{
    private static ContainerBuilder builder;

    private static IContainer container;

    static IocContainer()
    {
        builder = new ContainerBuilder();
    }

    public static void RegisterTypes(params Type[] types)
    {
        builder.RegisterTypes(types);
    }

    public static void Build()
    {
        container = builder.Build();
    }

    public static object Create(Type t)
    {
        return container.Resolve(t);
    }
}

然後, 我建了一個Resolver.

public class MyDependencyResolver : IDependencyResolver
{
    public object GetService(Type serviceType)
    {
        bool flag = true;
        string path = @"F:\MVC解析\MyControllerFac\1.txt";
        if (!File.Exists(path))
        {
            File.Create(path).Close();
        }

        try
        {
            return IocContainer.Create(serviceType);
        }
        catch (Exception)
        {
            flag = false;
            //這裡需要註意, 需要返回一個null值, 否則會報錯
            return null;
        }
        finally
        {
            using (FileStream fs = new FileStream(path, FileMode.Append))
            {
                var msg = flag ? "命中" : "飄過";
                byte[] bt = System.Text.Encoding.UTF8.GetBytes(serviceType.ToString() + " : " + msg + "\r\n");
                fs.Write(bt, 0, bt.Length);
           fs.Close(); } } }
public IEnumerable<object> GetServices(Type serviceType) { var res = new List<object>(); try { var obj = IocContainer.Create(serviceType); res.Add(obj); } catch (Exception) { } return res; } }

現在前置工作做好了, 可以去MVC中, 註冊我自己的方法了.

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();

    WebApiConfig.Register(GlobalConfiguration.Configuration);
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    RouteConfig.RegisterRoutes(RouteTable.Routes);
    BundleConfig.RegisterBundles(BundleTable.Bundles);
   
   //這裡我就省懶了, 並沒有寫到單獨的方法中取 IocContainer.RegisterTypes(System.Reflection.Assembly.Load(
"MyMvc").GetTypes()); IocContainer.Build(); DependencyResolver.SetResolver(new MyDependencyResolver()); }

OK, 萬事俱備, 只差測試. 跑起來吧

頁面能正常訪問, 那麼是不是走的我哪裡呢? 從頁面上肯定看不出來, 所以我加了點東西, 看一下1.txt

 從這裡來看, 並不是只有 HomeController 走了我自定義的那個方式, 這也是為什麼一定要返回一個null了. 對於這些飄過的, 就只能使用MVC預設的方式了. 還是很神奇的.

目錄已同步


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 參考頁面: http://www.yuanjiaocheng.net/ASPNET-CORE/core-middleware.html http://www.yuanjiaocheng.net/ASPNET-CORE/core-exception.html http://www.yuanjiaoch ...
  • 參考頁面: http://www.yuanjiaocheng.net/webapi/first.html http://www.yuanjiaocheng.net/webapi/web-api-gaisu.html http://www.yuanjiaocheng.net/webapi/create ...
  • 參考頁面: http://www.yuanjiaocheng.net/ASPNET-CORE/project-layout.html http://www.yuanjiaocheng.net/ASPNET-CORE/projectjson.html http://www.yuanjiaocheng. ...
  • 參考頁面: http://www.yuanjiaocheng.net/entity/entity-relations.html http://www.yuanjiaocheng.net/entity/entity-lifecycle.html http://www.yuanjiaocheng.net ...
  • 參考頁面: http://www.yuanjiaocheng.net/webapi/create-crud-api-1-delete.html http://www.yuanjiaocheng.net/webapi/Consume-web-api.html http://www.yuanjiaoch ...
  • 參考頁面: http://www.yuanjiaocheng.net/entity/entity-relations.html http://www.yuanjiaocheng.net/entity/entity-lifecycle.html http://www.yuanjiaocheng.net ...
  • 參考頁面: http://www.yuanjiaocheng.net/Entity/first.html http://www.yuanjiaocheng.net/Entity/jieshao.html http://www.yuanjiaocheng.net/entity/tixijiegou.h ...
  • asp.net core + mysql + ef core + linux 以前開髮網站是針對windows平臺,在iis上部署。由於這次需求的目標伺服器是linux系統,就嘗試用跨平臺的.NET core來開發和部署。結果還是比較滿意,整個過程如下,歡迎交流: 開發環境: Win10 Vs201 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...