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,一经查实,立即删除!

相关文章

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…

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

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

老司机实战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…

新起点!新征程!

好久没有更新公众号了&#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…

编写高效率的C#代码

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

2020蓝桥杯省赛---java---B---1(门牌制作)

题目描述 代码实现 package TEST;public class Main {public static void main(String[] args) {int sum0;for (int i 0; i < 2020; i) {int tempi;while (temp>0){if(temp%102){sum;}temp/10;}}System.out.println(sum);} }答案 624

使用Mybatis-Generator自动生成Dao、Model、Mapping相关文件

转载自 使用Mybatis-Generator自动生成Dao、Model、Mapping相关文件 Mybatis属于半自动ORM&#xff0c;在使用这个框架中&#xff0c;工作量最大的就是书写Mapping的映射文件&#xff0c;由于手动书写很容易出错&#xff0c;我们可以利用Mybatis-Generator来帮我们自动生成文…

android拦截短信获取短信内容,《英雄联盟手游》先锋测试招募说明:仅安卓用户...

招募时间&#xff1a;5月10日~5月17日测试开始时间&#xff1a;预计5月下旬或6月上旬招募(体验)要求&#xff1a;1、测试期间有较长时间可投入游戏体验&#xff1b;2、能够积极反馈和表达自己的游戏体验感受&#xff1b;3、需提前完成招募问卷(最终是否获取资格需筛选后确认)。…

ASP.NET Core MVC 源码学习:详解 Action 的匹配

前言 在 上一篇 文章中&#xff0c;我们已经学习了 ASP.NET Core MVC 的启动流程&#xff0c;那么 MVC 在启动了之后&#xff0c;当请求到达过来的时候&#xff0c;它是怎么样处理的呢&#xff1f; 又是怎么样把我们的请求准确的传达到我们的 Action 上呢&#xff1f; 那么&am…

win10偶尔打不开开始菜单(按win键和点击开始菜单都没反应)

像我这种桌面上一个图标都没有的。习惯把所有的应用程序放在开始菜单里面&#xff0c;但是……最近发现点击开始菜单或者按win键的时候召唤不出来开始菜单&#xff0c;怎么都出不来&#xff0c;怎么办&#xff1f;&#xff1f;&#xff1f;难道只有重启电脑来解决吗&#xff1f…

Mybatis 的Log4j日志输出问题 - 以及有关日志的所有问题

转载自 Mybatis 的Log4j日志输出问题 - 以及有关日志的所有问题 使用Mybatis的时候&#xff0c;有些时候能输出&#xff08;主要是指sql&#xff0c;参数&#xff0c;结果&#xff09;日志。有些时候就不能。 无法输出日志的时候&#xff0c;无论怎么配置log4j&#xff0c;…

2019蓝桥杯省赛---java---C---9(等差数列)

题目描述 代码实现 package TEST;import java.util.Arrays; import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner scanner new Scanner(System.in);int nscanner.nextInt();int[] arrnew int[n];for (int i 0; i < n; i) {arr…

Win10 Bash\/WSL调试Linux环境下的.NET Core应用程序

一、简介 使用过Mac OS的程序员都知道,在Mac Book Pro上写程序是一件比较爽的事儿,作为dotneter&#xff0c;我们都比较羡慕Mac系统的环境,比如命令行,当然设备也是挺漂亮的。 在新的Win10系统中微软给我们提供了一个基于Ubuntu的Linux子系统&#xff08;Bash/WSL&#xff09…

公众号新上线微信小游戏(疯狂猜图)

为了活跃公众号&#xff0c;于2018.09.29推出一款小游戏《疯狂猜图》&#xff0c;可以赢大奖哦&#xff0c;那么小游戏怎么玩呢&#xff1f;关注公众号的用户只需回复“小游戏”即可弹出游戏链接&#xff0c;点击进入就可以啦~~目前已经有126人参与&#xff0c;期待您的参与&am…

android输入时背景颜色,Button根据EditText输入状态改变背景颜色

需求Button随EditText输入状态改变颜色有3个不同颜色状态&#xff0c;EditText未输入时&#xff0c;Button处于不可点击状态EditText输入时&#xff0c;Button处于高亮状态EditText输入且用户按下按钮&#xff0c;Button --> Pressed状态效果如下&#xff1a;演示图片EditTe…

小和问题

题目描述 思路分析 代码实现 package class02;import java.util.Arrays; import java.util.concurrent.locks.ReentrantLock;/*** 创建人 wdl* 创建时间 2021/4/13* 描述*/ public class Demo02SmallSum {public static int mergeSort(int[] arr){if(arrnull|| arr.length<…