ASP.NET Core[ソース分析編]-WebHost

32412 ワード

 _configureServicesDelegatesの引き受け


【ASP.NET Core[ソース分析編]-Startup】この記事では、これまで(UseStartup)、すべての動作が_configureServicesDelegatesに登録依頼が追加されていますが、システムはいつこれらの依頼を実行して登録を完了しますか?

本格的な登録


以前の一連の目まぐるしい操作を通じて、登録が必要なすべての依頼を受けました.configureServicesDelegates、WebHostBuilderを見てみましょう.Buildはどのようにして本当の登録を実現しますか.

  WebHostBuilder.Build()

  public IWebHost Build()
    {
      if (this._webHostBuilt)
        throw new InvalidOperationException(Resources.WebHostBuilder_SingleInstance);
      this._webHostBuilt = true;
      AggregateException hostingStartupErrors;
      IServiceCollection serviceCollection1 = this.BuildCommonServices(out hostingStartupErrors);
      IServiceCollection serviceCollection2 = serviceCollection1.Clone();
      IServiceProvider providerFromFactory = GetProviderFromFactory(serviceCollection1);
      .....

    WebHost webHost = new WebHost(serviceCollection2, providerFromFactory, this._options, this._config, hostingStartupErrors);
      try
      {
        webHost.Initialize();
        return (IWebHost) webHost;
      }
      catch
      {
        webHost.Dispose();
        throw;
      }

    IServiceProvider GetProviderFromFactory(IServiceCollection collection)
      {
        ServiceProvider serviceProvider = collection.BuildServiceProvider();
        IServiceProviderFactory service = ((IServiceProvider) serviceProvider).GetService>();
        if (service == null)
          return (IServiceProvider) serviceProvider;
        using (serviceProvider)
          return service.CreateServiceProvider(service.CreateBuilder(collection));
      }
    }
そこには最も重要な方法BuildCommonServicesがあり、この方法は依頼の真の実行を実現しています.
private IServiceCollection BuildCommonServices(
      out AggregateException hostingStartupErrors)
    {
        .....
     ServiceCollection services = new ServiceCollection();
services.AddTransient
(); services.AddTransient(); services.AddScoped(); services.AddOptions(); services.AddLogging(); services.AddTransient(); services.AddTransient, DefaultServiceProviderFactory>();     ..... foreach (Action servicesDelegate in this._configureServicesDelegates) servicesDelegate(this._context, (IServiceCollection) services); return (IServiceCollection) services; }
上記のコードから、まず本物のServiceCollectionインスタンスを作成し、このインスタンスに基づいて追加の重要な登録(A p p r i c ationBuilderFactory,HttpContextFactory,DefaultServiceProviderFactoryなど)を追加し、その後、このServiceCollectionインスタンスをパラメータとして_に渡します.configureServicesDelegatesリストの各依頼で実行されます.これにより、Startupに登録する必要があるすべてのインスタンスがサービスというServiceCollectionインスタンスに登録されます.
ここまでプログラムはStartup内のメソッドを実行していないことに注意してください.

  WebHost


BuildCommonServicesが完了すると、ServiceCollectionインスタンスが返され、このServiceCollectionインスタンスに基づいてServiceProviderオブジェクトが生成され、WebHostオブジェクトを生成するパラメータとしてWebHostに渡されます.
WebHost webHost = new WebHost(serviceCollection2, providerFromFactory, this._options, this._config, hostingStartupErrors);

webHost.Initialize();  

  WebHost.Initialize()


まずWebHostのInitializeメソッドを見てみましょう
  public void Initialize()
    {
      try
      {
        this.EnsureApplicationServices();
      }
      catch (Exception ex)
      {
        if (this._applicationServices == null)
          this._applicationServices = (IServiceProvider) this._applicationServiceCollection.BuildServiceProvider();
        if (!this._options.CaptureStartupErrors)
          throw;
        else
          this._applicationServicesException = ExceptionDispatchInfo.Capture(ex);
      }
    }

  private void EnsureApplicationServices()
    {
      if (this._applicationServices != null)
        return;
      this.EnsureStartup();
      this._applicationServices = this._startup.ConfigureServices(this._applicationServiceCollection);
    }

    private void EnsureStartup()
    {
      if (this._startup != null)
        return;
      this._startup = this._hostingServiceProvider.GetService<IStartup>();
      if (this._startup == null)
        throw new InvalidOperationException(string.Format("No startup configured. Please specify startup via WebHostBuilder.UseStartup, WebHostBuilder.Configure, injecting {0} or specifying the startup assembly via {1} in the web host configuration.", (object) "IStartup", (object) "StartupAssemblyKey"));
    }
上のコードフローから分かるように
  • 解析Startupクラス
  • StartupクラスのConfigureServicesメソッドを実行してカスタムサービスを登録し、IServiceProviderオブジェクト
  • を返します.
    これで、StartupクラスのConfigureServicesが実行され、WebHostにはIServiceProviderオブジェクトがあります.

      WebHost.Run()


    WebHostの拡張メソッドRunを呼び出してアプリケーションを起動すると、本質的にWebHostのStartAsyncメソッドが呼び出されます.このプロセスは、HTTPリクエストのリスニング、受信、処理、応答に最も重要なパイプラインを作成します. 
      public virtual async Task StartAsync(CancellationToken cancellationToken = default (CancellationToken))
        {
          HostingEventSource.Log.HostStart();
          this._logger = this._applicationServices.GetRequiredService>();
          this._logger.Starting();
          RequestDelegate application = this.BuildApplication();
          this._applicationLifetime = this._applicationServices.GetRequiredService() as ApplicationLifetime;
          this._hostedServiceExecutor = this._applicationServices.GetRequiredService();
          DiagnosticListener requiredService1 = this._applicationServices.GetRequiredService();
          IHttpContextFactory requiredService2 = this._applicationServices.GetRequiredService();
          ILogger logger = this._logger;
          DiagnosticListener diagnosticSource = requiredService1;
          IHttpContextFactory httpContextFactory = requiredService2;
          await this.Server.StartAsync((IHttpApplication) new HostingApplication(application, (ILogger) logger, diagnosticSource, httpContextFactory), cancellationToken).ConfigureAwait(false);
          this._applicationLifetime?.NotifyStarted();
          await this._hostedServiceExecutor.StartAsync(cancellationToken).ConfigureAwait(false);
         .....
        }
    
      private RequestDelegate BuildApplication()
        {
            this._applicationServicesException?.Throw();
            this.EnsureServer();
            IApplicationBuilder builder = this._applicationServices.GetRequiredService().CreateBuilder(this.Server.Features);
            builder.ApplicationServices = this._applicationServices;
            IEnumerable service = this._applicationServices.GetService>();
            Action next = new Action(this._startup.Configure);
            foreach (IStartupFilter startupFilter in service.Reverse())
              next = startupFilter.Configure(next);
            next(builder);
            return builder.Build();
          }
    
      private void EnsureServer()
        {
          if (this.Server != null)
            return;
          this.Server = this._applicationServices.GetRequiredService<IServer>();
          IServerAddressesFeature addressesFeature = this.Server.Features?.Get();
          ICollection<string> addresses = addressesFeature?.Addresses;
          if (addresses == null || addresses.IsReadOnly || addresses.Count != 0)
            return;
          string str1 = this._config[WebHostDefaults.ServerUrlsKey] ?? this._config[WebHost.DeprecatedServerUrlsKey];
          if (string.IsNullOrEmpty(str1))
            return;
          addressesFeature.PreferHostingUrls = WebHostUtilities.ParseBool(this._config, WebHostDefaults.PreferHostingUrlsKey);
          string str2 = str1;
          char[] separator = new char[1]{ ';' };
          foreach (string str3 in str2.Split(separator, StringSplitOptions.RemoveEmptyEntries))
            addresses.Add(str3);
        }
    これは主にServerの作成であり、パイプラインの作成とHttpリクエストのServer起動の傍受を行い、段階的に分析します.

      1. EnsureServer


    まずこのサーバーが何なのか見てみましょう
    public interface IServer : IDisposable
    {
        IFeatureCollection Features { get; }
    
        Task StartAsync(IHttpApplication application, CancellationToken cancellationToken);
    
        Task StopAsync(CancellationToken cancellationToken);
    }
    IServerのインスタンスは、プログラムを開始するCreateDefaultBuilderで、デフォルトのサーバインスタンスとしてKestrelServerが指定されています.  
    public static IWebHostBuilder UseKestrel(this IWebHostBuilder hostBuilder)
    {
        hostBuilder.UseLibuv();
    
        return hostBuilder.ConfigureServices(services =>
        {
            services.AddTransient, KestrelServerOptionsSetup>();
            services.AddSingleton();
        });
    }
    では、このサーバーは何に使いますか?サーバは、HTTPの傍受を担当するHTTPサーバで、FeatureCollectionタイプの元のリクエストのセットを受信し、HttpContextにパッケージして、アプリケーションが応答の処理を完了します.どこを監視してるの?コードからAddressesは、UseUrlsで指定したパラメータ(WebHostDefaults.ServerUrlsKey)またはDeprecatedServerUrlsKey(プロファイル内のserver.urls)で検索されていることがわかります.

      2. BuildApplication


    上記では、要求をリスニングするためのサーバを取得しました.次に、Http要求を処理するパイプを構築します.IApplicationBuilderは、アプリケーションを構築するための要求パイプです.
    私たちの一般的なパイプ作成はStartupクラスのコンフィギュレーションメソッドでIApplicationBuilderを構成します.ええ、ここではIApplicationBuilderを構成するためにIStartupFilterも使用できます.また、Startupクラスのコンフィギュレーションメソッドの前に実行します.BuildApplicationメソッドでは、大体のステップは次のようになります.
  • IApplicationBuilderFactoryに基づいてIApplicationBuilderオブジェクト
  • を作成する.
  • IStartupFilterに基づくパイプ構築
  • IApplicationBuilderオブジェクトを呼び出すBuildメソッドは、完全なパイプ
  • を完了する.
    public RequestDelegate Build()
        {
          RequestDelegate requestDelegate = (RequestDelegate) (context =>
          {
            context.Response.StatusCode = 404;
            return Task.CompletedTask;
          });
          foreach (Func func in this._components.Reverse>())
            requestDelegate = func(requestDelegate);
          return requestDelegate;
        }

      3. Server.StartAsync


    ここで、サーバの起動には、HttpContextの作成を担当するIHttpApplicationタイプのパラメータが必要です.このパラメータを見てみましょう.
    public interface IHttpApplication
    {
        TContext CreateContext(IFeatureCollection contextFeatures);
    
        Task ProcessRequestAsync(TContext context);
    
        void DisposeContext(TContext context, Exception exception);
    }
    そのデフォルト実装クラスはそのデフォルト実装がHostingApplicationクラスである
    public class HostingApplication : IHttpApplication
      {
        private readonly RequestDelegate _application;
        private readonly IHttpContextFactory _httpContextFactory;public Task ProcessRequestAsync(HostingApplication.Context context)
        {
          return this._application(context.HttpContext);
        }
      ...... }
    ServerのHttpリスニングバインディングを見てみましょう
    public async Task StartAsync(
          IHttpApplication application,
          CancellationToken cancellationToken)
        {
          try
          {
            if (!BitConverter.IsLittleEndian)
              throw new PlatformNotSupportedException(CoreStrings.BigEndianNotSupported);
            this.ValidateOptions();
            if (this._hasStarted)
              throw new InvalidOperationException(CoreStrings.ServerAlreadyStarted);
            this._hasStarted = true;
            this._heartbeat.Start();
            await AddressBinder.BindAsync(this._serverAddresses, this.Options, (ILogger) this.Trace, new Func(OnBind)).ConfigureAwait(false);
          }
          catch (Exception ex)
          {
            this.Trace.LogCritical((EventId) 0, ex, "Unable to start Kestrel.");
            this.Dispose();
            throw;
          }
    
          async Task OnBind(ListenOptions endpoint)
          {
            endpoint.UseHttpServer((IList) endpoint.ConnectionAdapters, this.ServiceContext, application, endpoint.Protocols);
            ConnectionDelegate connectionDelegate = endpoint.Build();
            if (this.Options.Limits.MaxConcurrentConnections.HasValue)
              connectionDelegate = new ConnectionDelegate(new ConnectionLimitMiddleware(connectionDelegate, this.Options.Limits.MaxConcurrentConnections.Value, this.Trace).OnConnectionAsync);
            ConnectionDispatcher connectionDispatcher = new ConnectionDispatcher(this.ServiceContext, connectionDelegate);
            ITransport transport = this._transportFactory.Create((IEndPointInformation) endpoint, (IConnectionDispatcher) connectionDispatcher);
            this._transports.Add(transport);
            await transport.BindAsync().ConfigureAwait(false);
          }
        }
    これまでサーバは傍受ポートを1つバインドし,HTTP接続イベントを登録していたが,残りは傍受を開始した.  

      4. HostedService


    HostedServiceは、プログラムの起動に伴って起動するバックグラウンド実行サービスを開始しました.  
    public class HostedServiceExecutor
      {
        private readonly IEnumerable _services;public async Task StartAsync(CancellationToken token)
        {
        
    await this.ExecuteAsync((Func) (service => service.StartAsync(token))); } public async Task StopAsync(CancellationToken token) {
        await this.ExecuteAsync((Func) (service => service.StopAsync(token))); } private async Task ExecuteAsync(Func callback) { List exceptions = (List) null; foreach (IHostedService service in this._services) { try { await callback(service); } catch (Exception ex) { if (exceptions == null) exceptions = new List(); exceptions.Add(ex); } } if (exceptions != null) throw new AggregateException((IEnumerable) exceptions); } }

    まとめ


    この2つの文章はStartupから最後のHttpパイプ作成とHttpServerの起動リスニングまで、多くのキーに関連しており、コードフローから見ると、いくつかのキーをつかむだけで全体の1つのフローを理解することができます.皆さんは以下の問題を持って文章について行くことができます.
  • Startupにはいくつのインスタンス化方法がありますか?
  • IStartupはどこでインスタンス化されていますか?
  • IServiceCollectionはいつインスタンス化されますか?
  • IServiceProviderはいつインスタンス化されましたか?
  • StartupのConfigureServiceメソッドはいつ実行されますか?
  • IApplicationBuilderはいつインスタンス化されましたか?
  • StartupのConfigureメソッドはいつ実行されますか?
  • Httpリスニングパイプはいつ、どのように構築されましたか?