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」と決め打ちです。なかなか厄介。