zoukankan      html  css  js  c++  java
  • 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 的执行流程。

    有兴趣的可以右下角点一波关注,如果你认为本篇文章对你有帮助的话,谢谢你的【推荐】。


    本文地址:http://www.cnblogs.com/savorboard/p/aspnetcore-mvc-startup.html
    作者博客:Savorboard
    欢迎转载,请在明显位置给出出处及链接

  • 相关阅读:
    ElasticSearch实战:Linux日志对接Kibana
    想知道谁是你的最佳用户?基于Redis实现排行榜周期榜与最近N期榜
    详解CSS的Flex布局
    如何一步步在生产环境上部署django和vue
    Anisotropic Specular Shader
    Failed to install *.apk on device *: timeout
    logcat
    Android Debug Bridge
    DVI与VGA有什么区别
    Solo and Mute
  • 原文地址:https://www.cnblogs.com/savorboard/p/aspnetcore-mvc-startup.html
Copyright © 2011-2022 走看看