ASP.NET Core MVC 源码学习:MVC 启动流程详解
前言
在 上一篇 文章中,我们学习了 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
欢迎转载,请在明显位置给出出处及链接
ASP.NET Core MVC 源码学习:MVC 启动流程详解的更多相关文章
- MVC系列——MVC源码学习:打造自己的MVC框架(一:核心原理)
前言:最近一段时间在学习MVC源码,说实话,研读源码真是一个痛苦的过程,好多晦涩的语法搞得人晕晕乎乎.这两天算是理解了一小部分,这里先记录下来,也给需要的园友一个参考,奈何博主技术有限,如有理解不妥之 ...
- [转]MVC系列——MVC源码学习:打造自己的MVC框架(一:核心原理)
本文转自:http://www.cnblogs.com/landeanfen/p/5989092.html 阅读目录 一.MVC原理解析 1.MVC原理 二.HttpHandler 1.HttpHan ...
- MVC系列——MVC源码学习:打造自己的MVC框架(四:了解神奇的视图引擎)
前言:通过之前的三篇介绍,我们基本上完成了从请求发出到路由匹配.再到控制器的激活,再到Action的执行这些个过程.今天还是趁热打铁,将我们的View也来完善下,也让整个系列相对完整,博主不希望烂尾. ...
- MVC系列——MVC源码学习:打造自己的MVC框架(三:自定义路由规则)
前言:上篇介绍了下自己的MVC框架前两个版本,经过两天的整理,版本三基本已经完成,今天还是发出来供大家参考和学习.虽然微软的Routing功能已经非常强大,完全没有必要再“重复造轮子”了,但博主还是觉 ...
- MVC系列——MVC源码学习:打造自己的MVC框架(二:附源码)
前言:上篇介绍了下 MVC5 的核心原理,整篇文章比较偏理论,所以相对比较枯燥.今天就来根据上篇的理论一步一步进行实践,通过自己写的一个简易MVC框架逐步理解,相信通过这一篇的实践,你会对MVC有一个 ...
- ASP.NET Core 框架源码地址
ASP.NET Core 框架源码地址 https://github.com/dotnet/corefx 这个是.net core的 开源项目地址 https://github.com/aspnet ...
- ASP.NET Core Web服务器 Kestrel和Http.sys 特性详解
ASP.NET Core Web服务器 Kestrel和Http.sys 特性详解 1.1. 名词解释 1.2. Kestrel基本工作原理 1.2.1. Kestrel的基本架构 1.2.2. Ke ...
- [转帖]ASP.NET Core Web服务器 Kestrel和Http.sys 特性详解
ASP.NET Core Web服务器 Kestrel和Http.sys 特性详解 https://www.cnblogs.com/vipyoumay/p/7525478.html ASP.NET C ...
- Mybatis源码学习第六天(核心流程分析)之Executor分析
今Executor这个类,Mybatis虽然表面是SqlSession做的增删改查,其实底层统一调用的是Executor这个接口 在这里贴一下Mybatis查询体系结构图 Executor组件分析 E ...
随机推荐
- Ubuntu 14.04 64bit 系统下打开PHPadmin时出现缺少mysqli|mysql 扩展的解决方法(php5)
网上找了很多方法,都是翻来覆去的抄袭. 都在说把 /etc/php5/apache2/php.ini 下面的 ;extension=php_mysqli.dll 前面的 ; 注释符号去掉 再重启 ...
- UI 常用(UIButton)
1.手势 UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@sele ...
- HTML中的a标签实现点击下载
通常在咱们写项目的时候会遇到上传下载什么的,在上传完文件后会把文件的路径保存到数据库里以便下载,如果想不通过后台直接下载的话,可以把文件路径给a标签的属性href: <a href=" ...
- AJSX 传输数组
如果要利用ajax传输数组,或者传输多个(不知道有多少个)class的某一属性的值,例如: 要将这三个数据传入php编辑界面,图片显示有三个数据,但实际上,数据的多少是由数据库所导出的数据 决定的.如 ...
- 使用Python实现子区域数据分类统计
目录 前言 geopandas简介 子区域数据分类统计 总结 一.前言 最近碰到一个需求,需要统计某省内的所有市的某数据分布情况信息.现有该省的数据分布情况以及该省的行政区划数据.我通过 ...
- angular、vue使用感受
最近开始学习并使用vue.js,并使用vue+node开发了一个移动端APP来练手,下面想聊聊我对于vue的粗浅看法,并将它和angular进行一些对比: 1.vue是一个轻量.高效的前端组件化框架, ...
- 读书笔记 effective c++ Item 22 将数据成员声明成private
我们首先看一下为什么数据成员不应该是public的,然后我们将会看到应用在public数据成员上的论证同样适用于protected成员.最后够得出结论:数据成员应该是private的. 1. 为什么数 ...
- Android源码编译常见错误(持续更新)
本文为个人工作中处理遇到的编译问题做个小结,后续遇到新的问题,持续更新. No such file or directory: 1. 检查路径是否有问题,文件是否存在,若文件存在且路径没问题 2. 检 ...
- 了解 : angular $rootScope 在 ui-view
在view 的element 可以直接调用 <p>{{$stateParams.xx}}</p> 要让xx有资料必须注入 app.run["$rootScope&qu ...
- 过程 : 概念 : 结构 jobbox jobPost
概念是employer创建jobPost时,可以publish或unpublish. sort expired后,会通过server tast 去更新成history.所有的publish和unpub ...