前言
在 上一篇 文章中,我們學習了 ASP.NET Core MVC 的路由模塊,那么在本篇文章中,主要是對 ASP.NET Core MVC 啟動流程的一個學習。
ASP.NET Core 是新一代的 ASP.NET 應用程序,它是跨平臺的,并且不依賴于 IIS,新的 MVC Core 設計加入了依賴注入和模塊化的 Http 處理管道,這篇文章我們一起通過源碼看一下它的啟動過程,每一步都很重要。
我們知道 MVC Core 是作為一個中間件程序,注冊到 ASP.NET Core 管道流程中的,我先來回顧一下在以前基于 IIS 的傳統 ASP.NET 程序。 傳統的 ASP.NET 應用程序由可執行文件 InetMgr.exe (IIS 宿主進程)創建,然后調用受托管的應用程序入口,接著調用 HttpApplication.Application_Start()
進一步初始化,通常情況下,我們的初始化代碼都寫在 Application_StartGlobal.asax
中。
我們今天的主題是MVC 框架,所以針對 ASP.NET Core Host 和 Server 的初始化流程就不詳細講解了,由興趣的同學可以翻看一下我的這篇文章。
ASP.NET Core 源碼地址:https://github.com/aspnet/mvc
Getting Started
ASP.NET Core MVC 源碼程序主要包含幾部分組成:
- Mvc.Core :源碼的核心實現,包含認證,過濾,模型綁定,路由等等...
- Mvc.Razor:Razor視圖的拓展實現,模板引擎等,核心實現在Rozor那個項目。
- Mvc.TagHelper:Razor中 TagHelper的主要實現。
- Mvc.ViewFeatures:Razor中視圖組件的渲染等。
從 Startup 說起
ASP.NET Core MVC 程序在啟動之后,會經過一系列流程,然后到達 Microsoft.AspNetCore.Mvc包里的擴展程序 IMvcBuilder AddMvc(this IServiceCollection services)
中,然后我們從 ConfigureServices
這個分支說起吧。
在 Startup
啟動的時候,會在 ConfigureServices
中注冊 AddMvc 的 DI 服務,那么MVC也是在這個時候注入到DI容器中的,在MVC中所有的注入都是使用 TryAddXXX
的形式,也就是如果容器中已經有相關服務的話,將不會添加新注冊的服務,所以如果你有一些服務需要進行重寫的話,需要在 builder.AddMvc()
之前注冊到DI中。
我們先看一下 AddMvc 的返回值 IMvcBuilder
, IMvcBuilder
是一個針對 IServiceCollection 包裝的一個接口,除了IServiceCollection之外,還有一個 ApplicationPartManager
。那么它是干嘛的呢?
從命名來看 ApplicationPartManager
是用來管理 ApplicationPart
的,那么其實除了里面 ApplicationPart 之外還有 IApplicationFeatureProvider
。
public interface IMvcBuilder
{
IServiceCollection Services { get; }
ApplicationPartManager PartManager { get; }
}
ApplicationPart
它是 MVC Core 中引用的一個抽象的概念,它允許你暴露一些特性或者一些已知的資源,比如一些元數據信息,發布的資源,磁盤的文件等。
你可以在應用程序啟動的時候進行 ApplicationPart
的配置,它是作為 IMvcBuilder
擴展的一部分。
目前 MVC 框架針對 ApplicationPart 的默認實現只有 AssemblyPart,當然你可以根據需要進行擴展。
ApplicationPartManager
private static void AddDefaultFrameworkParts(ApplicationPartManager partManager)
{
var mvcTagHelpersAssembly = typeof(InputTagHelper).GetTypeInfo().Assembly;
if(!partManager.ApplicationParts.OfType<AssemblyPart>().Any(p => p.Assembly == mvcTagHelpersAssembly))
{
partManager.ApplicationParts.Add(new AssemblyPart(mvcTagHelpersAssembly));
}
var mvcRazorAssembly = typeof(UrlResolutionTagHelper).GetTypeInfo().Assembly;
if(!partManager.ApplicationParts.OfType<AssemblyPart>().Any(p => p.Assembly == mvcRazorAssembly))
{
partManager.ApplicationParts.Add(new AssemblyPart(mvcRazorAssembly));
}
}
提供了從 IApplicationFeatureProvider
列表初始化 ApplicationPart
的功能,例如 FeatureProviders 可以是 ControllerFeatureProvider
,它可以通過從 ApplicationPart
列表中的暴露出來的類型來找到哪些是 Controller,并且把這些 Controller 添加到 ControllerFeature
中,那么在應用程序中這些被保存的 Controller 就將被視為控制器。
其實就是在 MVC 框架啟動的時候,首先會把 Assembly 程序集轉換為 ApplicationPart 添加到 ApplicationPartManager 對象列表中,才能執行后續的任務,因為要從這些程序集中查找 Controller,那么從這個特性我們可以延伸到, 利用此功能,我們可以從 Web 層剝離 Controller 到其他程序集中。
MVC 加載程序集主要是依靠 DefaultAssemblyPartDiscoveryProvider
這個類提供的功能,它是一個靜態類位于 Microsoft.AspNetCore.Mvc.Internal
命名空間,有需要的同學可以直接使用。
下面是 AddMvc 的內部函數列表,它將每一個模塊相關的服務封裝成了一個 AddXXX 的拓展函數。
我們依次來看一下這些注冊到 Services 中 AddXXX 內部都又注冊了哪些東西。
AddMvcCore
根據命名我們可以看到,它是MVC的核心服務,下面是一張圖,羅列出了 MvcCore 內部注冊的一些注冊的接口,我們可以看到,有非常的多。
這個圖注冊的服務,比較多,我就不一一說明了,有幾個關鍵的服務還是要說一下的。
Action相關:
先是 Action 相關的一些服務,可以說他們承載了 Mvc Core 中的核心,:
IActionDescriptorCollectionProvider
: ActionDescriptorCollection 的提供程序,在內部他會對 Action 進行整理,然后我們可以在他的屬性 ActionDescriptors 讀取到相關信息。
ActionDescriptor
:這個可能大家比較熟悉,它封裝了Action上下文的很多信息,也就是Action的描述符。
ApplicationModel
:MVC 應用程序的一個實體,包含了 Controller 信息,Filters信息,屬性信息等,框架根據 ApplicationModel 存儲的這些信息來構建(Build) ControllerActionDescriptor 列表。
ControllerActionDescriptor
: 一個繼承自 ActionDescriptor的類,新增了一些關于 ControllerName, ActionName ,MethodInfo 等屬性。
IActionSelector
:我們知道,當一個路由到我們MVC系統的時候,有可能這個路由會匹配到多個Action與其相符合,那么如何選著最合適的路由處理程序呢? 這個接口主要封裝了相關邏輯。
還有 IControllerActivator
,IControllerActivator
,IActionInvokerFactory
我們將在下一篇中介紹。
然后是一些 Infrastructure 的一些服務,主要是一些Request 和 Response 流的處理,還有 xxxResult 的一些執行程序。
路由相關:
MvcRouteHandler
, MvcAttributeRouteHandler
這兩個我們留到下一篇介紹。
模型綁定驗證相關:
IModelMetadataProvider
:從方法類型的元數據中獲取信息。
IModelBinderFactory
:模型綁定工廠。
IObjectModelValidator
:模型驗證。
關于模型綁定和驗證,后面我應該會單獨有一篇博客介紹細節。
AddViews
Views 注入的服務主要包括,視圖引擎、Html Helper 相關、Json Helper、View Components、CookieTempData、Antiforgery 等。
AddRazorViewEngine
在 AddRazorViewEngine 中,又一次執行了 builder.AddViews() ,不知道這個是 Bug 還是故意這樣的, 我猜測有可能是一些地方單獨使用視圖引擎做一些操作,而 AddRazorViewEngine 又依賴于 AddViews 中提供的一些服務,所以又重新執行了 AddView()。
經過 github 的 issue 印證,這并不是 bug,而是設計如此。開發團隊在實現這一塊的功能的時候,AddXXX 的 設計策略就是會自動添加依賴的服務,因為這樣比較更加友好,這種形式對于添加什么服務也更加的明確。 當然他們也講到,將很多的碎片服務再進行碎片的細分是非常復雜的,有時候會有點不切實際,所以此處不得不如此。
接下來,又向 ApplicationPartMangager 里面的 FeatureProviders 添加了 TagHelperFeatureProvider
,MetadataReferenceFeatureProvider
,ViewsFeatureProvider
的實例。
AddRazorPages
處理Razor Pages 中的一些業務操作,在 AddRazorPages 中,又一次調用了 AddRazorViewEngine() ,而 AddRazorViewEngine 里面又調用了 AddView() ,又此可見這些組件都是環環相扣的,他們都有自己的機制來確保服務的健全。
AddCacheTagHelper
添加 MVC Cache Tag Helper 服務。
在這個注冊的服務中,有兩個關于緩存的服務,一個是 IDistributedCache
另外一個是 IMemoryCache
,當你主動注冊一個IDistributedCache的時候,將會使用你注冊的IDistributedCache。
AddDataAnnotations
注冊了以下兩個服務,我就不畫圖了。
- MvcDataAnnotationsMvcOptionsSetup
- IValidationAttributeAdapterProvider
處理 Mvc Model 中數據注解相關業務,第一個 MvcDataAnnotationsMvcOptionsSetup 將會在 MvcOptions 的 ModelMetadataDetailsProviders 屬性中添加 DataAnnotationsMetadataProvider 和 DataAnnotationsModelValidatorProvider
MvcDataAnnotationsMvcOptionsSetup 實現了 IConfigureOptions<MvcOptions>
, 下面是它的 Configure 方法。
public void Configure(MvcOptions options)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
options.ModelMetadataDetailsProviders.Add(new DataAnnotationsMetadataProvider(
_dataAnnotationLocalizationOptions,
_stringLocalizerFactory));
options.ModelValidatorProviders.Add(new DataAnnotationsModelValidatorProvider(
_validationAttributeAdapterProvider,
_dataAnnotationLocalizationOptions,
_stringLocalizerFactory));
}
AddJsonFormatterServices
- MvcJsonMvcOptionsSetup
- JsonPatchOperationsArrayProvider
- JsonResultExecutor
關于MVC 輸入 Json 格式化的配置項,還有 JsonResultExecutor。 以及 JsonPatch 的相關操作。
有些同學可能對 JsonPatch 不太了解,那么 JsonPatch 是什么東西呢?
JSON Patch 在 IETF 中規范是 RFC 6902。
JSON Patch 是一個用來描述 JSON 文檔變化的格式,它本身也是 JSON 文檔。它可以用于避免在只有一個節點更改時發送整個文檔。當與HTTP PATCH方法組合使用時,它允許以符合標準的方式對HTTP API進行部分更新。例如:
源文件:
{
"baz": "qux",
"foo": "bar"
}
Patch:
[
{ "op": "replace", "path": "/baz", "value": "boo" },
{ "op": "add", "path": "/hello", "value": ["world"] },
{ "op": "remove", "path": "/foo"
]
結果:
{
"baz": "boo",
"hello": ["world"]
}
那么 ASP.NET Core 中,微軟對 RFC 6902 協議的實現位于 Microsoft.AspNetCore.JsonPatch
這個 NuGet 包里面。
MVC 框架針對于 Json Patch 提供了一擴展方法 JsonPatchExtensions
,需要的同學自行使用。
AddCors
- IApplicationModelProvider , CorsApplicationModelProvider
- CorsAuthorizationFilter
下面是 CorsApplicationModelProvider 執行的時候的代碼,就是向 ControllerModel 中添加 Filter:
public void OnProvidersExecuting(ApplicationModelProviderContext context)
{
IEnableCorsAttribute enableCors;
IDisableCorsAttribute disableCors;
foreach (var controllerModel in context.Result.Controllers)
{
enableCors = controllerModel.Attributes.OfType<IEnableCorsAttribute>().FirstOrDefault();
if (enableCors != null)
{
controllerModel.Filters.Add(new CorsAuthorizationFilterFactory(enableCors.PolicyName));
}
disableCors = controllerModel.Attributes.OfType<IDisableCorsAttribute>().FirstOrDefault();
if (disableCors != null)
{
controllerModel.Filters.Add(new DisableCorsAuthorizationFilter());
}
foreach (var actionModel in controllerModel.Actions)
{
enableCors = actionModel.Attributes.OfType<IEnableCorsAttribute>().FirstOrDefault();
if (enableCors != null)
{
actionModel.Filters.Add(new CorsAuthorizationFilterFactory(enableCors.PolicyName));
}
disableCors = actionModel.Attributes.OfType<IDisableCorsAttribute>().FirstOrDefault();
if (disableCors != null)
{
actionModel.Filters.Add(new DisableCorsAuthorizationFilter());
}
}
}
}
其中 CorsAuthorizationFilterFactory
的實例會創建 CorsAuthorizationFilter
。 有關 Cors 的更多示例可以看這里。
UseMvc
UseMvc 的主要過程是Router中間件的初始化和啟動過程,關于Router中間件可以看一下我的上一篇文章,執行流程如下:
1、構建 middlewarePipelineBuilder,它是一個新的 ApplicationBuilder 實例。
2、在UseMvc中,主要是應用 Router 中間件,進行路由攔截。
3、構建 RouterBuilder , 添加或配置 IRouteBuilder。
4、使用IRouteBuilder 注冊 Router 中間件。
總結
本篇文章主要是根據源碼分析了 MVC 中間件在啟動的過程中都注冊了哪些服務,以及這些服務在MVC構建的過程中都起到了什么作用,
那么他是怎么和Router中間件進行配合的呢?我們下一篇來分析 Controller Action 的激活,和 MVC 的執行流程。
有興趣的可以右下角點一波關注,如果你認為本篇文章對你有幫助的話,謝謝你的【推薦】。
本文地址:http://www.cnblogs.com/savorboard/p/aspnetcore-mvc-startup.html
作者博客:Savorboard
歡迎轉載,請在明顯位置給出出處及鏈接
文章列表