.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-5004222
Encoder = 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