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 添加了 TagHelperFeatureProviderMetadataReferenceFeatureProviderViewsFeatureProvider 的实例。

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 的执行流程。


相关文章

  • ASP.NET Core MVC 源码学习:Routing 路由

原文地址:http://www.cnblogs.com/savorboard/p/aspnetcore-mvc-startup.html


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/326246.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ps基础知识

一、ps全称&#xff1a;Adobe Photoshop &#xff0c;Adobe Photoshop是由Adobe Systems 开发和发行的专门用于图形图像处理的软件。 二、PS的应用领域&#xff1a; 1.在平面设计中的应用 2.在插画设计中的应用 3.在网页设计中的应用 4.在界面设计中的应用 5.在数码艺术中的应用…

Photoshop的绘图工具

一、油漆桶工具 1.快捷键&#xff1a;g 2.选区图像部分&#xff0c;使用油漆桶工具直接单击选区部分即可 3.返回上一步&#xff1a;ctrlshiftz 4.不透明度&#xff1a;一般我们调整不透明度的时候&#xff0c;使用调整图层的不透明度的方法来实现要求。 二、渐变工具&#xff1…

C语言调用es6,ES6 箭头函数、普通函数、调用方法

importReact,{Component} fromreact;import{Platform,StyleSheet,Text,Image,View,TouchableOpacity,ToastAndroid,} fromreact-native;export default classsrrowFunDemo extendsComponent {constructor(props) {super(props);this.state {data0: 点击0,data1: 点击1,data2: 点…

2020蓝桥杯省赛---java---B---9(子串分值和)

题目描述 时间限制: 3.0s 内存限制: 512.0MB 本题总分&#xff1a;25 分【问题描述】 对于一个字符串 S&#xff0c;我们定义 S 的分值 f(S) 为 S 中出现的不同的字符个 数。例如 f(”aba”) 2&#xff0c;f(”abc”) 3, f(”aaa”) 1。 现在给定一个字符串 S[0…n−1]&…

GitHub 贡献第一的微软开源软件列表

作者&#xff5c;木环 编辑&#xff5c;小智 在GitHub上贡献最多的公司&#xff0c;不是Facebook&#xff0c;也不是Google&#xff0c;而是微软。InfoQ对微软数个较受社区欢迎的项目进行了整理&#xff0c;以飨读者。希望开源的精神&#xff0c;能给技术社区带来更多的实惠&am…

mybatis入门(七)之日志

转载自 mybatis入门&#xff08;七&#xff09;之日志 Mybatis 的内置日志工厂提供日志功能&#xff0c;内置日志工厂将日志交给以下其中一种工具作代理&#xff1a; SLF4JApache Commons LoggingLog4j 2Log4jJDK logging MyBatis 内置日志工厂基于运行时自省机制选择合适…

Photoshop图像修饰工具

一、修饰工具&#xff1a; 1.准确来说是一个修饰工具组。 2.进行图像修饰时&#xff0c;没根据具体情况&#xff0c;进行针对性的选择相应的工具进行修饰。 二、仿制图章工具组: 1.仿制图章工具组 2.图案图章工具 三、修复工具组 1.污点修复画笔工具 2.修复画笔工具 3.修补工具…

android 画布控件,Android canvas画图操作之切割画布实现方法(clipRect)

本文实例讲述了Android canvas画图操作之切割画布实现方法。分享给大家供大家参考&#xff0c;具体如下&#xff1a;android切割画布的历程不算很难&#xff0c;可是理解起来也比较麻烦&#xff0c;这里写一下我的理解 但是不一定正确&#xff1a;canvas.clipRect(30,30,70,Reg…

MyBatis传入多个参数的问题

转载自 MyBatis传入多个参数的问题 一、单个参数&#xff1a; public List<XXBean> getXXBeanList(String xxCode); <select id"getXXXBeanList" parameterType"java.lang.String" resultType"XXBean">select t.* from tableN…

老司机实战Windows Server Docker:5 Windows Server Dockerfile葵花宝典

前面两篇&#xff08;简单运维1、简单运维2&#xff09;介绍了一些Windows Server Docker相关的基本运维知识。今天这一篇&#xff0c;Windows Server Dockerfile葵花宝典&#xff0c;涵盖了许多典型场景的Windows Server下的Dockerfile实例&#xff0c;并且每一个都包含可直接…

2020蓝桥杯省赛---java---B---2(寻找 2020)+测试txt

题目描述 text 0020000002202020002220002022002222202022020200022200020200222022002202202020020022200202000000002200222002022220222202220000222202200200202220200222200222202200000220220020202200022002200200200222000202220202002000000202200200220022020002022…

用一年的时间,依靠SEO创造一个成功的网站

以下内容是一位SEO前辈&#xff08;站长世界的创始人Brett Tabke&#xff09;写的&#xff0c;这篇文章在我最初学习SEO时&#xff0c;深刻得影响了我&#xff0c;给我了足够的自信。希望对你也有所帮助。 a)准备工作和建立内容。      在你注册域名之前&#xff0c;你就应…

Mybatis传入参数类型为Map

转载自 Mybatis传入参数类型为Map mybatis更新sql语句&#xff1a; <update id"publishT00_notice" parameterType"Map"> update test set createdate #{createdate}, creator #{creator} where id in <foreach collection"ids"…

android merge的作用,Android学习手记-merge

为什么用merge&#xff1a;标签的作用是合并UI布局&#xff0c;使用该标签能降低UI布局的嵌套层次。该标签的主要使用场景主要包括两个&#xff0c;第一是当xml文件的根布局是 FrameLayout时&#xff0c;可以用merge作为根节点。理由是因为Activity的内容布局中&#xff0c;默认…

新起点!新征程!

好久没有更新公众号了&#xff0c;其一是因为最近这段时间有点“忙”&#xff08;迫于其他原因&#xff0c;目前包括本公众号一共运营4个公众号&#xff0c;精力不充沛&#xff09;&#xff0c;其次就是犯了懒病不想动。其中博客中也没有更新比较有质量的博文了。感觉自己堕落颓…

2020蓝桥杯省赛---java---B---3(蛇形填数)

题目描述 思路分析 找规律 看对角线 1481216 代码实现 package TEST;public class Main {public static void main(String[] args) {int res 1, t 4;for(int i2; i<20; i) {res t;t 4;//1481216}System.out.println(res); //761}}答案 761

linux微信公众号报警,zabbix报警媒介,微信报警,邮件报警

微信报警首先要申请微信企业公众号&#xff0c;创建相应应用&#xff0c;然后进行配置微信企业公众号申请&#xff0c;目前可免费前往该地址进行申请注册过程很简单&#xff0c;不信你试然后进行企业公众号的基础设置服务端报警微信脚本[rootbogon alertscripts]# pwd/usr/loca…

Mybatis传多个参数(三种解决方案)

转载自 Mybatis传多个参数&#xff08;三种解决方案&#xff09; 据我目前接触到的传多个参数的方案有三种。 第一种方案 DAO层的函数方法 public User selectUser(String name,String area); 对应的Mapper.xml <select id"selectUser" resultMap&quo…

色彩调整与校正

一、Photoshop中所有关于色彩调整的功能都在 图像–》调整子菜单中。 二、色阶&#xff1a; 1.图像–》调整–》色阶 2.快捷键&#xff1a;ctrll 3.设置白场点黑色全变白&#xff0c;设置黑场点白色全变黑 4.设置红绿蓝通道&#xff1a; 1)红通道黑场滑块往右拖&#xff0c;蓝绿…

编写高效率的C#代码

周末空闲&#xff0c;选读了一下一本很不错的C#语言使用的书&#xff0c;特此记载下便于对项目代码进行重构和优化时查看。 Standing On Shoulders of Giants&#xff0c;附上思维导图&#xff0c;其中标记的颜色越深表示在实际中的实际意义越大。 名称内容和示例提供API时尽量…