Help us understand the problem. What is going on with this article?

[Microsoft] ASP.NET Core サーバが起動するまでを追いかけてみる

ASP.NET Coreを理解するために、プログラムが起動するまでを追ってみます。

テンプレートから生成されたファイルを題材にします。

プログラムエントリ

Program#Main

ProgramクラスのMainメソッドの中は、以下のようになっています。

これを1行ずつ追ってみます。

Program.cs
WebHost.CreateDefaultBuilder(args)
    .UseStartup<Startup>()
    .Build()
    .Run();

1行目: .CreateDefaultBuilder(args)

WebHostBuilderインスタンスの作成

WebHostBuilderインスタンスを作成します。

ソースファイル: https://github.com/aspnet/AspNetCore/blob/master/src/DefaultBuilder/src/WebHost.cs

var builder = new WebHostBuilder();

環境変数を読み込むようにする

WebHostBuilderコンストラクタの主要部分です。

ConfigurationBuilderの拡張メソッドを呼んで、プレフィックス ASPNETCORE_ がついた環境変数を読み込むようにします。

ソースファイル: https://github.com/aspnet/AspNetCore/blob/master/src/Hosting/Hosting/src/WebHostBuilder.cs

_hostingEnvironment = new HostingEnvironment();

_config = new ConfigurationBuilder()
    .AddEnvironmentVariables(prefix: "ASPNETCORE_")
    .Build();

_context = new WebHostBuilderContext
{
    Configuration = _config
};

ConfigurationBuilder#AddEnvironmentVariables

指定されたプレフィックスがついた環境変数を読み込むようにします。

ソースファイル: https://github.com/aspnet/Extensions/blob/master/src/Configuration/Config.EnvironmentVariables/src/EnvironmentVariablesExtensions.cs

public static IConfigurationBuilder AddEnvironmentVariables(
    this IConfigurationBuilder configurationBuilder,
    string prefix)
{
    configurationBuilder.Add(new EnvironmentVariablesConfigurationSource { Prefix = prefix });
    return configurationBuilder;
}

ドキュメントルートをカレントディレクトリにする

if (string.IsNullOrEmpty(builder.GetSetting(WebHostDefaults.ContentRootKey)))
{
    builder.UseContentRoot(Directory.GetCurrentDirectory());
}

コマンドライン引数を処理

dotnet runコマンドでは何も渡ってこないので、実質何もしてないです。

src/DefaultBuilder/src/WebHost.cs
if (args != null)
{
    builder.UseConfiguration(new ConfigurationBuilder().AddCommandLine(args).Build());
}

設定読み込み

環境に応じて、設定ファイルを読み込むdelegateをWebHostBuilderに渡しています。

デフォルトではappsettings.jsonファイルを読み込みます。
さらにappsettings.{環境}.jsonファイルを読み込みます。

Development環境のときは、追加のアセンブリを読み込んでいます。1

src/DefaultBuilder/src/WebHost.cs
builder.ConfigureAppConfiguration((hostingContext, config) =>
{
    var env = hostingContext.HostingEnvironment;

    config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
        .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);

    if (env.IsDevelopment())
    {
        var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
        if (appAssembly != null)
        {
            config.AddUserSecrets(appAssembly, optional: true);
        }
    }

    config.AddEnvironmentVariables();

    if (args != null)
    {
        config.AddCommandLine(args);
    }
})

ロガーを設定

ロガーを設定しています。

.ConfigureLogging((hostingContext, logging) =>
// (省略)
)

デフォルトサービスプロバイダを設定

.UseDefaultServiceProvider((context, options) =>
{
    options.ValidateScopes = context.HostingEnvironment.IsDevelopment();
})

もろもろデフォルト設定

Kestrel webサーバを使う(?)

.UseKestrel((builderContext, options) =>
{
    options.Configure(builderContext.Configuration.GetSection("Kestrel"));
})

ソースファイル: https://github.com/aspnet/AspNetCore/blob/master/src/Servers/Kestrel/Kestrel/src/WebHostBuilderKestrelExtensions.cs

DIコンテナに追加

.ConfigureServices((hostingContext, services) =>
{
    services.PostConfigure<HostFilteringOptions>(/* (省略) */);

    services.AddSingleton<IOptionsChangeTokenSource<HostFilteringOptions>>(/* (省略) */);

    services.AddTransient<IStartupFilter, HostFilteringStartupFilter>();

    services.AddRouting();
})

IISを使う(?)

.UseIIS()
.UseIISIntegration()

ソースファイル: https://github.com/aspnet/AspNetCore/blob/master/src/Servers/IIS/IIS/src/WebHostBuilderIISExtensions.cs

2行目: .UseStartup<Startup>()

DIコンテナにスタートアップクラスを登録するdelegateをWebHostBuilderに追加しています。

実際にはスタートアップクラスの代わりにConventionBasedStartupクラスを登録

実際にはConventionBasedStartupクラスを使用するようになっています。

ソースファイル: https://github.com/aspnet/AspNetCore/blob/master/src/Hosting/Hosting/src/WebHostBuilderExtensions.cs

return hostBuilder
    .ConfigureServices(services =>
    {
// (省略)
        services.AddSingleton(typeof(IStartup), sp =>
        {
            var hostingEnvironment = sp.GetRequiredService<IHostingEnvironment>();
            return new ConventionBasedStartup(StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName));
        });
// (省略)
    });

ソースファイル: https://github.com/aspnet/AspNetCore/blob/master/src/Hosting/Hosting/src/Startup/ConventionBasedStartup.cs

スタートアップクラスの呼び出し先メソッドを決定

ConverntionBasedStartupクラスが呼び出すメソッドは、StartupLoaderで決めています。

ソースファイル: https://github.com/aspnet/AspNetCore/blob/master/src/Hosting/Hosting/src/Internal/StartupLoader.cs

3行目: .Build()

WebHostをビルドします。

ソースファイル: https://github.com/aspnet/AspNetCore/blob/master/src/Hosting/Hosting/src/WebHostBuilder.cs

共通サービスの登録

このあたり、なにやっているか追えていません。

var services = new ServiceCollection();
// (省略)
services.AddSingleton<IHostingEnvironment>(_hostingEnvironment);
services.AddSingleton<Extensions.Hosting.IHostingEnvironment>(_hostingEnvironment);
services.AddSingleton(_context);

// (省略)

var configuration = builder.Build();
services.AddSingleton<IConfiguration>(configuration);
_context.Configuration = configuration;

var listener = new DiagnosticListener("Microsoft.AspNetCore");
services.AddSingleton<DiagnosticListener>(listener);
services.AddSingleton<DiagnosticSource>(listener);

services.AddTransient<IApplicationBuilderFactory, ApplicationBuilderFactory>();
services.AddTransient<IHttpContextFactory, DefaultHttpContextFactory>();
services.AddScoped<IMiddlewareFactory, MiddlewareFactory>();
services.AddOptions();
services.AddLogging();

// (省略)

WebHostのインスタンスを作成

WebHostのインスタンスを作成し、Initializeを呼んでいます。

var host = new WebHost(
    applicationServices,
    hostingServiceProvider,
    _options,
    _config,
    hostingStartupErrors);

(snip)
host.Initialize();

WebHostの初期化

ソースファイル: https://github.com/aspnet/AspNetCore/blob/master/src/Hosting/Hosting/src/Internal/WebHost.cs

スタートアップクラスのConfigureServicesを呼び出し

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

4行目: .Run();

WebHostStartAsyncを呼び出し

ソースファイル: https://github.com/aspnet/AspNetCore/blob/master/src/Hosting/Hosting/src/WebHostExtensions.cs

await host.StartAsync(token);

ソースファイル: https://github.com/aspnet/AspNetCore/blob/master/src/Hosting/Hosting/src/Internal/WebHost.cs

アプリケーションのビルド

var application = BuildApplication();

サーバの作成

Server = _applicationServices.GetRequiredService<IServer>();

アドレスの追加(かな?)

var urls = _config[WebHostDefaults.ServerUrlsKey] ?? _config[DeprecatedServerUrlsKey];
// (省略)
foreach (var value in urls.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
{
    addresses.Add(value);
}

ApplicationBuilderを作成

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

ソースファイル: https://github.com/aspnet/AspNetCore/blob/master/src/Hosting/Hosting/src/Builder/ApplicationBuilderFactory.cs

return new ApplicationBuilder(_serviceProvider, serverFeatures);

フィルタクラス達を呼び出し(かな?)

var startupFilters = _applicationServices.GetService<IEnumerable<IStartupFilter>>();
Action<IApplicationBuilder> configure = _startup.Configure;
foreach (var filter in startupFilters.Reverse())
{
    configure = filter.Configure(configure);
}

configure(builder);

アプリケーションをビルド

ソースファイル: https://github.com/aspnet/AspNetCore/blob/master/src/Http/Http/src/Internal/ApplicationBuilder.cs

_componentsには、useしたdelegateが入っています。

// (省略)
foreach (var component in _components.Reverse())
{
    app = component(app);
}

return app;

HostingApplicationインスタンスの作成

var diagnosticSource = _applicationServices.GetRequiredService<DiagnosticListener>();
var httpContextFactory = _applicationServices.GetRequiredService<IHttpContextFactory>();
var hostingApp = new HostingApplication(application, _logger, diagnosticSource, httpContextFactory);

サーバ起動

await Server.StartAsync(hostingApp, cancellationToken).ConfigureAwait(false);

  1. 環境名は「Development」「Production」「Staging」と決め打ちです。なかなか厄介。 

sengoku
テスト原理主義者。 記事は日々の記録です。2020年は、Java。2019年は、Angular/TypeScript/ASP.NET Core/C#が多めです。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした