剖析ASP.NET Core MVC(Part 1)- AddMvcCore(譯)

来源:http://www.cnblogs.com/lookerblue/archive/2017/07/31/7262907.html
-Advertisement-
Play Games

原文:https://www.stevejgordon.co.uk/asp-net-core-mvc-anatomy-addmvccore發佈於:2017年3月環境:ASP.NET Core 1.1 歡迎閱讀新系列的第一部分,我將剖析MVC源代碼,給大家展示隱藏在錶面之下的工作機制。此系列將分析MV ...


原文:https://www.stevejgordon.co.uk/asp-net-core-mvc-anatomy-addmvccore
發佈於:2017年3月
環境:ASP.NET Core 1.1

歡迎閱讀新系列的第一部分,我將剖析MVC源代碼,給大家展示隱藏在錶面之下的工作機制。此系列將分析MVC的內部,如果覺得枯燥,可以停止閱讀。但就我個人而言,也是經過反覆閱讀、調試甚至抓狂,直到最後理解ASP.NET MVC源代碼(或者自認為理解),從中獲益匪淺。通過瞭解框架的運作機制,我們可以更好的使用它們,更容易解決遇到的問題。

我會儘力給大家解釋對源碼的理解,我不能保證自己的理解和解釋是100%正確,但我會竭盡所能。要知道簡潔清晰的把一段代碼解釋清楚是很困難的,我將通過小塊代碼展示MVC源代碼,並附源文件鏈接,方便大家追蹤。閱讀之後如果仍不理解,我建議你花些時間讀讀源代碼,必要時親自動手調試一下。我希望此系列會引起像我一樣喜歡刨根問底的人的興趣。

AddMvcCore

本文我將剖析AddMvcCore到底為我們做了什麼,同時關註幾個像 ApplicationPartManager這樣的類。本文使用的project.json基於rel/1.1.2源代碼,通過運行MVC Sandbox 項目進行調試。

註:MvcSandbox為ASP.Net Core MVC源碼中的示列項目。

由於版本在不斷更新,一些類和方法可能會改變,尤其是內部。請始終參考GitHub上的最新代碼。對於MvcSandbox ConfigureServices我已作更新:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvcCore();
}

AddMvcCore是IServiceCollection的擴展方法。通常把一組關聯的服務註冊到服務集合(services collection)時都使用擴展方法這種模式。在構建MVC應用時,有兩個擴展方法用來註冊MVC服務(MVC Services),AddMvcCore是其中之一。相對AddMvc方法,AddMvcCore提供較少的服務子集。一些不需要使用MVC所有特性的簡單程式,就可以使用AddMvcCore。比如在構建REST APIs,就不需要Razor相關組件,我一般使用AddMvcCore。你也可以在AddMvcCore之後手動添加額外的服務,或者直接使用功能更多的AddMvc。AddMvcCore的實現:

public static IMvcCoreBuilder AddMvcCore(this IServiceCollection services)
{
    if (services == null)
    {
        throw new ArgumentNullException(nameof(services));
    }

    var partManager = GetApplicationPartManager(services);
    services.TryAddSingleton(partManager);

    ConfigureDefaultFeatureProviders(partManager);
    ConfigureDefaultServices(services);
    AddMvcCoreServices(services);

    var builder = new MvcCoreBuilder(services, partManager);

    return builder;
}

 AddMvcCore做的第一件事就是通過GetApplicationPartManager靜態方法獲得ApplicationPartManager,把IServiceCollection作為參數傳遞給GetApplicationPartManager。
GetApplicationPartManager的實現:

private static ApplicationPartManager GetApplicationPartManager(IServiceCollection services)
{
    var manager = GetServiceFromCollection<ApplicationPartManager>(services);
    if (manager == null)
    {
        manager = new ApplicationPartManager();

        var environment = GetServiceFromCollection<IHostingEnvironment>(services);
        if (string.IsNullOrEmpty(environment?.ApplicationName))
        {
            return manager;
        }

        var parts = DefaultAssemblyPartDiscoveryProvider.DiscoverAssemblyParts(environment.ApplicationName);
        foreach (var part in parts)
        {
            manager.ApplicationParts.Add(part);
        }
    }

    return manager;
}

本方法首先檢查當前已註冊的服務中是否有ApplicationPartManager,通常情況是沒有的,但極少情況你可能在調用AddMvcCore之前註冊了其它服務。ApplicationPartManager如果不存在則創建一個新的。

接下來的代碼是計算ApplicationPartManager的ApplicationParts屬性值。首先從服務集合(services collection)中獲得IHostingEnvironment。如果能夠獲IHostingEnvironment實例,則通過它獲得程式名或封裝名(application/assem bly name),然後傳遞給靜態方法DefaultAssemblyPartDiscoveryProvider,DiscoverAssemblyParts方法將返回IEnumerable<ApplicationPart>。

public static IEnumerable<ApplicationPart> DiscoverAssemblyParts(string entryPointAssemblyName)
{
    var entryAssembly = Assembly.Load(new AssemblyName(entryPointAssemblyName));
    var context = DependencyContext.Load(Assembly.Load(new AssemblyName(entryPointAssemblyName)));

    return GetCandidateAssemblies(entryAssembly, context).Select(p => new AssemblyPart(p));
}

DiscoverAssemblyParts 首先通過封裝名(assembly name)獲得封裝對象(Assembly Object)和DependencyContex。本例封裝名為“MvcSandbox”。然後將這些值傳遞給GetCandidateAssemblies方法,GetCandidateAssemblies再調用GetCandidateLibraries方法。

internal static IEnumerable<Assembly> GetCandidateAssemblies(Assembly entryAssembly, DependencyContext dependencyContext)
{
    if (dependencyContext == null)
    {
        // Use the entry assembly as the sole candidate.
        return new[] { entryAssembly };
    }

    return GetCandidateLibraries(dependencyContext)
        .SelectMany(library => library.GetDefaultAssemblyNames(dependencyContext))
        .Select(Assembly.Load);
}

internal static IEnumerable<RuntimeLibrary> GetCandidateLibraries(DependencyContext dependencyContext)
{
    if (ReferenceAssemblies == null)
    {
        return Enumerable.Empty<RuntimeLibrary>();
    }

    var candidatesResolver = new CandidateResolver(dependencyContext.RuntimeLibraries, ReferenceAssemblies);
    return candidatesResolver.GetCandidates();
}

解釋一下GetCandidateLibraries做了什麼:

它返回一個在<see cref=”ReferenceAssemblies”/>中引用的程式集列表,預設是我們引用的主要MVC程式集,不包含項目本身的程式集。

更明確一些,獲得的程式集列表包含了我們的解決方案中引用的所有MVC程式集。

ReferenceAssemblies是一個定義在DefaultAssemblyPartDiscoveryProvider類中靜態HashSet<string>,它包含13個MVC預設的程式集。

internal static HashSet<string> ReferenceAssemblies { get; } = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
    "Microsoft.AspNetCore.Mvc",
    "Microsoft.AspNetCore.Mvc.Abstractions",
    "Microsoft.AspNetCore.Mvc.ApiExplorer",
    "Microsoft.AspNetCore.Mvc.Core",
    "Microsoft.AspNetCore.Mvc.Cors",
    "Microsoft.AspNetCore.Mvc.DataAnnotations",
    "Microsoft.AspNetCore.Mvc.Formatters.Json",
    "Microsoft.AspNetCore.Mvc.Formatters.Xml",
    "Microsoft.AspNetCore.Mvc.Localization",
    "Microsoft.AspNetCore.Mvc.Razor",
    "Microsoft.AspNetCore.Mvc.Razor.Host",
    "Microsoft.AspNetCore.Mvc.TagHelpers",
    "Microsoft.AspNetCore.Mvc.ViewFeatures"
};

 GetCandidateLibraries使用CandidateResolver類定位並返回“候選人”。CandidateResolver 通過運行時對象(RuntimeLibrary objects)和ReferenceAssemblies構造。每個運行時對象依次迭代並添加到一個字典中,添加過程中檢查依賴名(dependency name)是否唯一,如果不唯一則拋出異常。

public CandidateResolver(IReadOnlyList<RuntimeLibrary> dependencies, ISet<string> referenceAssemblies)
{
    var dependenciesWithNoDuplicates = new Dictionary<string, Dependency>(StringComparer.OrdinalIgnoreCase);
    foreach (var dependency in dependencies)
    {
        if (dependenciesWithNoDuplicates.ContainsKey(dependency.Name))
        {
            throw new InvalidOperationException(Resources.FormatCandidateResolver_DifferentCasedReference(dependency.Name));
        }
        dependenciesWithNoDuplicates.Add(dependency.Name, CreateDependency(dependency, referenceAssemblies));
    }

    _dependencies = dependenciesWithNoDuplicates;
}

每個依賴對象(既RuntimeLibrary)都作為新的依賴對象存儲到字典中。這些對象包含一個DependencyClassification屬性,用來篩選需要的libraries (candidates)。DependencyClassification是一個枚舉類型:

private enum DependencyClassification
{
    Unknown = 0,
    Candidate = 1,
    NotCandidate = 2,
    MvcReference = 3
}

創建Dependency的時候,如果與ReferenceAssemblies HashSet匹配,則標記為MvcReference,其餘標記為Unknown。

private Dependency CreateDependency(RuntimeLibrary library, ISet<string> referenceAssemblies)
{
    var classification = DependencyClassification.Unknown;
    if (referenceAssemblies.Contains(library.Name))
    {
        classification = DependencyClassification.MvcReference;
    }

    return new Dependency(library, classification);
}

當CandidateResolver.GetCandidates方法被調用時,結合ComputeClassification方法,遍歷整個依賴對象樹。每個依賴對象都將檢查他的所有子項,直到匹配Candidate或者MvcReference,同時標記父依賴項為Candidate類型。遍歷結束將返回包含identified candidates的IEnumerable<RuntimeLibrary>。例如本例,只有MvcSandbox程式集被標記為Candidate。

public IEnumerable<RuntimeLibrary> GetCandidates()
{
    foreach (var dependency in _dependencies)
    {
        if (ComputeClassification(dependency.Key) == DependencyClassification.Candidate)
        {
            yield return dependency.Value.Library;
        }
    }
}

private DependencyClassification ComputeClassification(string dependency)
{
    Debug.Assert(_dependencies.ContainsKey(dependency));

    var candidateEntry = _dependencies[dependency];
    if (candidateEntry.Classification != DependencyClassification.Unknown)
    {
        return candidateEntry.Classification;
    }
    else
    {
        var classification = DependencyClassification.NotCandidate;
        foreach (var candidateDependency in candidateEntry.Library.Dependencies)
        {
            var dependencyClassification = ComputeClassification(candidateDependency.Name);
            if (dependencyClassification == DependencyClassification.Candidate ||
                dependencyClassification == DependencyClassification.MvcReference)
            {
                classification = DependencyClassification.Candidate;
                break;
            }
        }

        candidateEntry.Classification = classification;

        return classification;
    }
}

DiscoverAssemblyParts將返回的candidates轉換為新的AssemblyPart。此對象是對程式集的簡單封裝,只包含瞭如名稱、類型等主要封裝屬性。後續我可能單獨撰文分析此類。

最後,通過GetApplicationPartManager,AssemblyParts被加入到ApplicationPartManager。

      var parts = DefaultAssemblyPartDiscoveryProvider.DiscoverAssemblyParts(environment.ApplicationName);
      foreach (var part in parts)
      {
          manager.ApplicationParts.Add(part);
      }
  }

  return manager;

返回的ApplicationPartManager實例通過AddMvcCore擴展方法加入到services collection。

接下來AddMvcCore把ApplicationPartManager作為參數調用靜態ConfigureDefaultFeatureProviders方法,為ApplicationPartManager的FeatureProviders添加ControllerFeatureProvider。

private static void ConfigureDefaultFeatureProviders(ApplicationPartManager manager)
{
    if (!manager.FeatureProviders.OfType<ControllerFeatureProvider>().Any())
    {
        manager.FeatureProviders.Add(new ControllerFeatureProvider());
    }
}

ControllerFeatureProvider將被用在ApplicationPar的實例中發現controllers。我將在後續的博文中介紹ControllerFeatureProvider。現在我們繼續研究AddMovCore的最後一步。(ApplicationPartManager此時已更新)

首先調用私有方法ConfigureDefaultServices,通過Microsoft.AspNetCore.Routing提供的AddRouting擴展方法開啟routing功能。它提供啟用routing功能所需的必要服務和配置。本文不對此作詳細描述。

AddMvcCore接下來調用另一個私有方法AddMvcCoreServices,該方法負責註冊MVC核心服務,包括框架可選項,action 的發現、選擇和調用,controller工廠,模型綁定和認證。

最後AddMvcCore通過services collection和ApplicationPartManager,構造一個新的MvcCoreBuilder對象。此類被叫做:

允許細粒度的配置MVC基礎服務(Allows fine grained configuration of essential MVC services.)

AddMvcCore擴展方法返回MvcCoreBuilder,MvcCoreBuilder包含IserviceCollection和ApplicationPartManager屬性。MvcCoreBuilder和其擴展方法用在AddMvcCore初始化之後做一些額外的配置。實際上,AddMvc方法中首先調用的是AddMvcCore,然後使用MvcCoreBuilder配置額外的服務。

小結

要把上述所有問題都解釋清楚不是件容易的事,所以簡單總結一下。本文我們分析的是MVC底層代碼,主要實現了把MVCs需要的服務添加到IserviceCollection中。通過追蹤ApplicationPartManager的創建過程,我們瞭解了MVC如何一步步創建內部應用模型(ApplicationModel)。雖然我們並沒有看到很多實際的功能,但通過對startup的跟蹤分析我們發現了許多有趣的東西,這為後續分析奠定了基礎。

以上就是我對AddMvcCore的初步探究,共有46個附加項註冊到IservceCollection中。下一篇我將進一步分析AddMvc擴展方法。
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...