zoukankan      html  css  js  c++  java
  • ASP.NET Core 2.0 : 七.一张图看透启动背后的秘密(转)

    ASP.NET Core 系列目录

    为什么我们可以在Startup这个 “孤零零的” 类中配置依赖注入和管道?

      它是什么时候被实例化并且调用的?

      参数中的IServiceCollection services是怎么来的?

      处理管道是怎么构建起来的?

      启动过程中,系统“默默的”做了哪些准备工作?

      上一篇文章讲了ASP.NET Core中的依赖注入(系列目录), 而它的配置是在Startup这个文件中的 ConfigureServices(IServiceCollection services) 方法,而且Startup这个类也没有继承任何类或者接口。 深入的想一想,可能会冒出类似上面列出的好多问题,下面用一幅图来看透它。

    一、整体流程图

    先上图, 觉得看不清可以点击看大图或者下载后放大查看。

    图一  (点击放大

    二、WebHostBuilder

      应用程序在Main方法之后通过调用Create­DefaultBuilder方法创建并配置WebHostBuilder, 

    复制代码
     1     public class WebHostBuilder : IWebHostBuilder
     2     {
     3         private readonly List<Action<WebHostBuilderContext, IServiceCollection>> _configureServicesDelegates;
     4 
     5         private IConfiguration _config;
     6         public IWebHostBuilder UseSetting(string key, string value)
     7         {
     8             _config[key] = value;
     9             return this;
    10         }
    22         public IWebHostBuilder ConfigureServices(Action<WebHostBuilderContext, IServiceCollection> configureServices)
    23         {
    24             if (configureServices == null)
    25             {
    26                 throw new ArgumentNullException(nameof(configureServices));
    27             }
    29             _configureServicesDelegates.Add(configureServices);
    30             return this;
    31         }
    32     }
    复制代码

    WebHostBuilder存在一个重要的集合① private readonly List<Action<WebHostBuilderContext, IServiceCollection>> _configureServicesDelegates; , 通过 ConfigureServices 方法将需要的Action加入进来。

    UseSetting是一个用于设置Key-Value的方法, 一些常用的配置均会通过此方法写入_config中。

    三、UseStartup<Startup>()

      Create­DefaultBuilder之后调用UseStartup<Startup>(),指定Startup为启动类。

    复制代码
            public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType)
            {
                var startupAssemblyName = startupType.GetTypeInfo().Assembly.GetName().Name;
    
                return hostBuilder
                    .UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName)
                    .ConfigureServices(services =>
                    {
                        if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo()))
                        {
                            services.AddSingleton(typeof(IStartup), startupType);
                        }
                        else
                        {
                            services.AddSingleton(typeof(IStartup), sp =>
                            {
                                var hostingEnvironment = sp.GetRequiredService<IHostingEnvironment>();
                                return new ConventionBasedStartup(StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName));
                            });
                        }
                    });
            }
    复制代码

    首先获取Startup类对应的AssemblyName, 调用UseSetting方法将其设置为WebHostDefaults.ApplicationKey(“applicationName”)的值。

    然后调用WebHostBuilder的②ConfigureServices方法,将一个Action写入WebHostBuilder 的 configureServicesDelegates中。

    这个Action的意思就是说,如果这个被指定的类startupType是一个实现了IStartup的类, 那么将其通过AddSingleton注册到services 这个ServiceCollection中, 如果不是, 那么将其“转换”成 ConventionBasedStartup 这个实现了 IStartup的类后再进行注册。这里涉及到一个StartupLoader的LoadMethods()方法,会通过字符串的方式查找“ConfigureServices”、“Configure{environmentName}Services”这样的方法。

    注意:这里只是将一个Action写入了configureServicesDelegates, 而不是已经执行了对IStartup的注册, 因为这个Action尚未执行,services也还不存在。就像菩萨对八戒说: 八戒(Startup)你先在高老庄等着吧, 将来有个和尚带领一个取经小分队(ServiceCollection services )过来的时候你加入他们。

    其实在Create­DefaultBuilder方法中的几个UseXXX的方法也是这样通过ConfigureServices将对应的Action写入了configureServicesDelegates, 等待唐僧的到来。

    四、WebHostBuilder.Build()

    创建并配置好的WebHostBuilder开始通过Build方法创建WebHost了, 首先是BuildCommonServices, 

    复制代码
     1 private IServiceCollection BuildCommonServices(out AggregateException hostingStartupErrors)
     2         {
     3             //...省略...
     4             var services = new ServiceCollection();
     5             services.AddSingleton(_hostingEnvironment);
     6             services.AddSingleton(_context);
     7             //....各种Add....
     9             foreach (var configureServices in _configureServicesDelegates)
    10             {
    11                 configureServices(_context, services);
    12             }
    14             return services;
    15         }                
    复制代码

      在这个方法里创建了ServiceCollection services(以唐僧为首的取经小分队), 然后通过各种Add方法注册了好多内容进去(收了悟空),然后③foreach 之前暂存在configureServicesDelegates中的各个Action,传入services逐一执行, 将之前需要注册的内容注册到services中, 这里就包括Startup(八戒),注意这里仅是进行了注册,而未执行Startup的方法。

      处理好的这个services被BuildCommonServices返回后赋值给 hostingServices,然后 hostingServices经过Clone()生成 applicationServices,再由这个 applicationServices进行GetProviderFromFactory(hostingServices)生成一个 IServiceProvider hostingServiceProvider.经过一系列的处理后,可以创建WebHost了。

    复制代码
    var host = new WebHost(
        applicationServices,
        hostingServiceProvider,
        _options,
        _config,
        hostingStartupErrors);
    
    host.Initialize();
    复制代码

     将生成的applicationServices 和 hostingServiceProvider作为参数传递给新生成的WebHost。接下来就是这个WebHost的 Initialize()。

     五、WebHost.Initialize()

    WebHost的 Initialize()的主要工作就是BuildApplication()。

    EnsureApplicationServices(): 用来处理WebHost的 private IServiceProvider _applicationServices ,④Startup的ConfigureServices方法在这里被调用。

    _startup = _hostingServiceProvider.GetRequiredService<IStartup>();
    _applicationServices = _startup.ConfigureServices(_applicationServiceCollection);

    通过 GetRequiredService<IStartup>() 获取到我们的_startup, 然后调用这个_startup的 ⑤ConfigureServices 方法,这就是我们用于依赖注入的startup类的ConfigureServices方法了。

    所以,_applicationServices是根据_applicationServiceCollection 加上我们在_startup中注册的内容之后重新生成的 IServiceProvider。

    EnsureServer()⑥:通过 GetRequiredService<IServer>()获取Server并配置监听地址。

    var builderFactory = _applicationServices.GetRequiredService<IApplicationBuilderFactory>();
    var builder = builderFactory.CreateBuilder(Server.Features);
    builder.ApplicationServices = _applicationServices;

    获取到 IApplicationBuilderFactory并通过它⑦创建 IApplicationBuilder,并将上面创建的_applicationServices赋值给它的ApplicationServices,它还有个重要的集合_components

    private readonly IList<Func<RequestDelegate, RequestDelegate>> _components = new List<Func<RequestDelegate, RequestDelegate>>();

     从_components的类型可以看出它其实是中间件的集合,是该调用我们的_startup的Configure方法的时候了。

    先获取定义的IStartupFilter, ⑧foreach这些IStartupFilter并与_startup的Configure方法一起将配置的中间件写入_components,然后通过 Build()创建RequestDelegate _application,

    在Build()中对_components进行处理生成请求处理管道,关于IStartupFilter和生成管道这部分将在下篇文章进行详细说明。

    六、WebHost.Run()

      WebHost创建完毕, 最后一步就是Run起来了,WebHost的Run()会调用它的方法StartAsync()

    复制代码
    public virtual async Task StartAsync(CancellationToken cancellationToken = default(CancellationToken))
    {
        //......var hostingApp = new HostingApplication(_application, _logger, diagnosticSource, httpContextFactory);
        await Server.StartAsync(hostingApp, cancellationToken).ConfigureAwait(false);
        _hostedServiceExecutor.StartAsync(cancellationToken).ConfigureAwait(false);
        //.....
    }
    复制代码

      在之前的文章中我们知道,请求是经过 Server监听=>处理成httpContext=>Application处理,所以这里首先传入上面创建的_application和一个httpContextFactory来⑨生成一个HostingApplication,并将这个HostingApplication传入Server的StartAsync(), 当Server监听到请求之后, 后面的工作由HostingApplication来完成。

      ⑩hostedServiceExecutor.StartAsync()方法用来开启一个后台运行的服务,一些需要后台运行的操作比如定期刷新缓存等可以放到这里来。

    七、更新

      感谢dudu的留言,去github上看了一下WebHost的最新源码,BuildApplication()不再包含EnsureApplicationServices()的调用,并且转移到了WebHost.StartAsync() 中进行; WebHost.Initialize() 中由原本调用BuildApplication()改为调用原本放在BuildApplication()中调用的EnsureApplicationServices()。

      通过VS加载符号的方式调试获取到的WebHost仍是原来的版本,即使删除下载的文件后再次重新获取也一样, 应该是和新建项目默认引用的依赖版本有关。

    ASP.NET Core 系列目录

    原文地址:http://www.cnblogs.com/FlyLolo/p/ASPNETCore2_7.html

  • 相关阅读:
    vue form dynamic validator All In one
    TypeScript api response interface All In One
    closable VS closeable All In One
    macOS 如何开启 WiFi 热点 All In One
    vue css inline style All In One
    vs2010里面 新建网站里面的 asp.net网站 和 新建项目里面的 asp.net Web应用程序 的区别 (下)
    牛腩新闻 59 整合添加新闻页 FreeTextBox 富文本编辑器,检测到有潜在危险的 Request.Form 值,DropDownList 的使用
    牛腩新闻 61尾声: error.aspx的使用 防止报错
    vs2010里面 新建网站里面的 asp.net网站 和 新建项目里面的 asp.net Web应用程序 的区别 (上)
    牛腩新闻 62:尾声续2 asp.net的编译和发布
  • 原文地址:https://www.cnblogs.com/NetPig/p/10671212.html
Copyright © 2011-2022 走看看