.NET 7 Preview 3 引入的 HostApplicationBuilder
Intro
在 .NET 6 中,ASP.NET Core 引入了 Minimal API,对于简单的应用使用 Minimal API 我们可以使用非常精简的代码来实现我们的 API,在 .NET 7 Preview 3 中,引入了一个 HostApplicationBuilder
,我们使用普通的 Host 也可以使用 Minimal API 的方式来配置 Host 的 Logging
/Configuration
/Services
等,下面我们来看一下如何使用吧
Sample
首先为可能不熟悉 Minimal API 使用的童鞋看一下 Minimal API 的简单使用:
var builder = WebApplication.CreateBuilder(args);builder.Logging.AddJsonConsole(options =>
{options.JsonWriterOptions = new JsonWriterOptions{// https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-character-encoding?WT.mc_id=DT-MVP-5004222Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping};
});builder.Services.AddDbContextPool<SparkTodoDbContext>(options => options.UseInMemoryDatabase("SparkTodo"));var app = builder.Build();app.Map("/health", ()=> "Healthy");await app.RunAsync();
Minimal API 中的 Logging
/Configuration
/Services
相对来说更为简单一些,更多可以参考之前的一个 Todo 示例:Minimal API Todo Sample
HostApplicationBuilder
使得我们也可以使用这样的配置方式,示例如下:
var builder = Host.CreateApplicationBuilder();builder.Logging.AddJsonConsole(config =>
{config.UseUtcTimestamp = true;config.TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff";config.JsonWriterOptions = new System.Text.Json.JsonWriterOptions(){Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,Indented = true};
});builder.Configuration.AddJsonFile("env.json", true);builder.Services.AddHostedService<TestHostedService>();var host = builder.Build();
await host.StartAsync();
上面的 TestHostedService
是一个简单的每秒输出一下当前时间的后台任务, 使用 .NET 6 引入的 PeriodicTimer
,实现如下:
private sealed class TestHostedService : BackgroundService
{protected async override Task ExecuteAsync(CancellationToken stoppingToken){using var timer = new PeriodicTimer(TimeSpan.FromSeconds(1));while (await timer.WaitForNextTickAsync(stoppingToken)){Console.WriteLine($"{DateTime.Now}");}}
}
示例运行效果如下:
Inside
内部是怎么实现的呢?我们可以使用 Host.CreateApplicationBuilder()
也可以使用 new HostApplicationBuilder()
来创建
在创建的时候我们可以指定一个 HostApplicationBuilderSettings
来初始化 Host
的一些配置
Host.CreateApplicationBuilder()
实现如下:
public static HostApplicationBuilder CreateApplicationBuilder() => new HostApplicationBuilder();public static HostApplicationBuilder CreateApplicationBuilder(string[]? args) => new HostApplicationBuilder(args);
Host.CreateApplicationBuilder()
和 Host.CreateDefaultBuilder()
的行为保持一致,也会加载默认 DOTNET_
环境变量作为 Host Configuration 以及加载 appsettings.json
环境变量、 注册 Logging 等,详细可以参考:https://github.com/dotnet/runtime/blob/0e84780fd2280166cd82374c770177613527f06b/src/libraries/Microsoft.Extensions.Hosting/src/HostingHostBuilderExtensions.cs
HostApplicationBuilder
有三个构造方法
public sealed class HostApplicationBuilder
{private readonly HostBuilderContext _hostBuilderContext;private readonly ServiceCollection _serviceCollection = new();private Func<IServiceProvider> _createServiceProvider;private Action<object> _configureContainer = _ => { };private HostBuilderAdapter? _hostBuilderAdapter;private IServiceProvider? _appServices;private bool _hostBuilt;public HostApplicationBuilder(): this(args: null){}public HostApplicationBuilder(string[]? args): this(new HostApplicationBuilderSettings { Args = args }){}public HostApplicationBuilder(HostApplicationBuilderSettings? settings){// ...}
}
由于代码过多,这里不贴太多具体实现的代码,感兴趣的可以看源码实现 https://github.com/dotnet/runtime/blob/main/src/libraries/Microsoft.Extensions.Hosting/src/HostApplicationBuilder.cs
可以看到上面的代码中,会有一个 HostApplicationBuilderSettings
来决定 Host
的构建行为,我们来看一下它的源码
public sealed class HostApplicationBuilderSettings
{public bool DisableDefaults { get; set; }public string[]? Args { get; set; }public ConfigurationManager? Configuration { get; set; }public string? EnvironmentName { get; set; }public string? ApplicationName { get; set; }public string? ContentRootPath { get; set; }
}
除了 HostingEnvironment
相关的参数之外,增加了 DisableDefault
/Args
/Configuration
三个参数
DisableDefaults
前面我们提到Host.CreateApplicationBuilder
会和Host.CreateDefaultBuilder
的行为保持一致,这样提供了一个可以禁用默认配置的选项Args
是从命令行获取到的参数,会作为默认的一个配置源,当然只有DisableDefaults
为false
时才会生效Configuration
使得我们可以使用一个已有的配置信息
构造方法里的主要实现如下:
settings ??= new HostApplicationBuilderSettings();
Configuration = settings.Configuration ?? new ConfigurationManager();if (!settings.DisableDefaults)
{HostingHostBuilderExtensions.ApplyDefaultHostConfiguration(Configuration, settings.Args);
}// HostApplicationBuilderSettings override all other config sources.
List<KeyValuePair<string, string?>>? optionList = null;
if (settings.ApplicationName is not null)
{optionList ??= new List<KeyValuePair<string, string?>>();optionList.Add(new KeyValuePair<string, string?>(HostDefaults.ApplicationKey, settings.ApplicationName));
}
if (settings.EnvironmentName is not null)
{optionList ??= new List<KeyValuePair<string, string?>>();optionList.Add(new KeyValuePair<string, string?>(HostDefaults.EnvironmentKey, settings.EnvironmentName));
}
if (settings.ContentRootPath is not null)
{optionList ??= new List<KeyValuePair<string, string?>>();optionList.Add(new KeyValuePair<string, string?>(HostDefaults.ContentRootKey, settings.ContentRootPath));
}
if (optionList is not null)
{Configuration.AddInMemoryCollection(optionList);
}(HostingEnvironment hostingEnvironment, PhysicalFileProvider physicalFileProvider) = HostBuilder.CreateHostingEnvironment(Configuration);Configuration.SetFileProvider(physicalFileProvider);_hostBuilderContext = new HostBuilderContext(new Dictionary<object, object>())
{HostingEnvironment = hostingEnvironment,Configuration = Configuration,
};Environment = hostingEnvironment;HostBuilder.PopulateServiceCollection(Services,_hostBuilderContext,hostingEnvironment,physicalFileProvider,Configuration,() => _appServices!);Logging = new LoggingBuilder(Services);ServiceProviderOptions? serviceProviderOptions = null;if (!settings.DisableDefaults)
{HostingHostBuilderExtensions.ApplyDefaultAppConfiguration(_hostBuilderContext, Configuration, settings.Args);HostingHostBuilderExtensions.AddDefaultServices(_hostBuilderContext, Services);serviceProviderOptions = HostingHostBuilderExtensions.CreateDefaultServiceProviderOptions(_hostBuilderContext);
}_createServiceProvider = () =>
{_configureContainer(Services);return serviceProviderOptions is null ? Services.BuildServiceProvider() : Services.BuildServiceProvider(serviceProviderOptions);
};
从这里可以比较清晰的看得出来前面 HostApplicationBuilderSettings
中的参数的使用了
More
在新版本 Minimal API 中的 WebApplicationBuilder
内部也使用了 HostApplicationBuilder
来重构之前的实现,感兴趣的童鞋可以看一下 https://github.com/dotnet/aspnetcore/blob/main/src/DefaultBuilder/src/WebApplicationBuilder.cs
更多细节可以参考文末给出的源码链接
References
https://github.com/dotnet/runtime/blob/main/src/libraries/Microsoft.Extensions.Hosting/src/HostApplicationBuilder.cs
https://github.com/dotnet/runtime/blob/main/src/libraries/Microsoft.Extensions.Hosting/src/HostApplicationBuilderSettings.cs
https://github.com/dotnet/runtime/blob/main/src/libraries/Microsoft.Extensions.Hosting/src/Host.cs
https://github.com/dotnet/runtime/blob/0e84780fd2280166cd82374c770177613527f06b/src/libraries/Microsoft.Extensions.Hosting/src/HostingHostBuilderExtensions.cs
https://github.com/dotnet/runtime/blob/9299bc47f060b822807a88f729b5c1944104417a/src/libraries/Microsoft.Extensions.Hosting/src/HostBuilder.cs
https://github.com/dotnet/aspnetcore/blob/main/src/DefaultBuilder/src/WebApplicationBuilder.cs
https://github.com/WeihanLi/SamplesInPractice/blob/master/net7Sample/Net7Sample/HostApplicationBuilderSample.cs