.Net 6中WebApplicationBuilder介紹和用法
目錄
- 介紹
- 正文
- ConfigureHostBuilder
- BootstrapHostBuilder
- WebApplicationBuilder建構函式
- WebApplicationBuilder.Build()
介紹
.Net 6為我們帶來的一種全新的載入程式啟動的方式。與之前的拆分成Program.cs和Startup不同,整個引導啟動程式碼都在Program.cs中。
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
在上篇文章中,我簡要描述瞭如何使用 WebApplication和WebApplicationBuilder配置 ASP.NET Core 應用程式。在這篇文章中,我們來深入看下程式碼.
正文
我們示例程式的第一步是執行WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
建立一個WebApplicationBuilder
例項。
從命令列中分配Args引數,並將選項物件傳遞給WebApplicationBuilder建構函式的WebApplicationOptions
/// <summary> /// Initializes a new instance of the <see cref="WebApplicationBuilder"/> class with preconfigured defaults. /// </summary> /// <param name="args">Command line arguments</param> /// <returns>The <see cref="WebApplicationBuilder"/>.</returns> public static WebApplicationBuilder CreateBuilder(string[] args) => new(bxxupkNiCnew() { Args = args });
WebApplicationOptions和WebApplicationBuilder後面在講
internal WebApplicationBuilder(WebApplicationOptions options,Action<IHostBuilder>? configureDefaults = null) /// <summary> /// Options for configuing the behavior for <see cref="WebApplication.CreateBuilder(WebApplicationOptions)"/>. /// </summary> public class WebApplicationOptions { /// <summary> /// The command line arguments. /// </summary> public string[]? Args { get; init; } /// <summary> /// The environment name. /// </summary> public string? EnvironmentName { get; init; } /// <summary> /// The application name. /// </summary> public string? ApplicationName { get; init; } /// <summary> /// The content root path. /// </summary> public string? ContentRootPath { get; init; } /// <summary> /// The web root path. /// </summary> public string? WebRootPath { get; init; } }
WebApplicationBuilder由一堆只讀屬性和一個方法組成Build(),該方法建立了一個WebApplication. 我刪除了部分講解用不到的程式碼。
如果您熟悉 ASP.NET Core,那麼其中許多屬性都使用以前版本中的常見型別
- IWebHostEnvironment: 用於檢索環境
- IServiceCollection: 用於向 DI 容器註冊服務。
- ConfigurationManager: 用於新增新配置和檢索配置值。我在之前的文章有講
- ILoggingBuilder: 用於註冊額外的日誌提供程式
在WebHost和Host性質很有趣,因為它們暴露出新的型別,ConfigureWebHostBuilder和ConfigureHostBuilder。這些型別分別實現IWebHostBuilder和IHostBuilder。
公開IWebHostBuilder和IHostBuilder介面對於允許從.NET 6 之前的應用程式遷移到新的最小託管,我們如何將的lambda風格配置IHostBuilder與命令式風格的WebApplicationBuilder協調起來,這就是ConfigureHostBuilder和ConfigureWebHostBuilder與一些內部沿來IHostBuilder實現。
public sealed class WebApplicationBuilder { private const string EndpointRouteBuilderKey = "__EndpointRouteBuilder"; private readonly HostBuilder _hostBuilder = new(); private readonly BootstrapHostBuilder _bootstrapHostBuilder; private readonly WebApplicationServiceCollection _services = new(); private readonly List<KeyValuePair<string,string>> _hostConfigurationValues; private WebApplication? _builtApplication; /// <summary> /// Provides information about the web hosting environment an application is running. /// </summary> public IWebHostEnvironment Environment { get; } /// <summary> /// A collection of services for the application to compose. This is useful for adding user provided or framework provided services. /// </summary> public IServiceCollection Services { get; } /// <summary> /// A collection of configuration providers for the application to compose. This is useful for adding new configuration sources and providehttp://www.cppcns.comrs. /// </summary> public ConfigurationManager Configuration { get; } /// <summary> /// A collection of logging providers for the application to compose. This is useful for adding new logging providers. /// </summary> public ILoggingBuilder Logging { get; } /// <summary> /// An <see cref="IWebHostBuilder"/> for configuring server specific properties,but not building. /// To build after configuration,call <see cref="Build"/>. /// </summary> public ConfigureWebHostBuilder WebHost { get; } /// <summary> /// An <see cref="IHostBuilder"/> for configuring host specific properties,call <see cref="Build"/>. /// </summary> public ConfigureHostBuilder Host { get; } /// <summary> /// Builds the <see cref="WebApplication"/>. /// </summary> /// <returns>A configured <see cref="WebApplication"/>.</returns> public WebApplication Build() { // Wire up the host configuration here. We don't try to preserve the configuration // source itself here since we don't support mutating the host values after creating the builder. _hostBuilder.ConfigureHostConfiguration(builder => { builder.AddInMemoryCollection(_hostConfigurationValues); }); var chainedConfigSource = new TrackingChainedConfigurationSource(Configuration); // Wire up the application configuration by copying the already built configuration providers over to final configuration builder. // We wrap the existing provider in a configuration source to avoid re-bulding the already added configuration sources. _hostBuilder.ConfigureAppConfiguration(builder => { builder.Add(chainedConfigSource); foreach (var (key,value) in ((IConfigurationBuilder)Configuration).Properties) { builder.Properties[key] = value; } }); // This needs to go here to avoid adding the IHostedService that boots the server twice (the GenericWebHostService). // Copy the services that were added via WebApplicationBuilder.Services into the final IServiceCollection _hostBuilder.ConfigureServices((context,services) => { // We've only added services configured by the GenericWebHostBuilder and WebHost.ConfigureWebDefaults // at this point. HostBuilder news up a new ServiceCollection in HostBuilder.Build() we haven't seen // until now,so we cannot clear these services even though some are redundant because // we called ConfigureWebHostDefaults on both the _deferredHostBuilder and _hostBuilder. foreach (var s in _services) { services.Add(s); } // Add the hosted services that were initially added last // this makes sure any hosted services that are added run after the initial set // of hosted services. This means hosted services run before the web host starts. foreach (var s in _services.HostedServices) { services.Add(s); } // Clear the hosted services list out _services.HostedServices.Clear(); // Add any services to the user visible service collection so that they are observable // just in case users capture the Services property. Orchard does this to get a "blueprint" // of the service collection // Drop the reference to the existing collection and set the inner collection // to the new one. This allows code that has references to the service collection to still function. _services.InnerCollection = services; var hostBuilderProviders = ((IConfigurationRoot)context.Configuration).Providers; if (!hostBuilderProviders.Contains(chainedConfigSource.BuiltProvider)) { // Something removed the _hostBuilder's TrackingChainedConfigurationSource pointing back to the ConfigurationManager. // This is likely a test using WebApplicationFactory. Replicate the effect by clearing the ConfingurationManager sources. ((IConfigurationBuilder)Configuration).Sources.Clear(); } // Make builder.Configuration match the final configuration. To do that,we add the additional // providers in the inner _hostBuilders's Configuration to the ConfigurationManager. foreach (var provider in hostBuilderProviders) { if (!ReferenceEquals(provider,chainedConfigSource.BuiltProvider)) { ((IConfigurationBuilder)Configuration).Add(new ConfigurationProviderSource(provider)); } } }); // Run the other callbacks on the final host builder Host.RunDeferredCallbacks(_hostBuilder); _builtApplication = new WebApplication(_hostBuilder.Build()); // Mark the service collection as read-only to prevent future modifications _services.IsReadOnly = true; // Resolve both the _hostBuilder's Configuration and builder.Configuration to mark both as resolved within the // service provider ensuring both will be properly disposed with the provider. _ = _builtApplication.Services.GetService<IEnumerable<IConfiguration>>(); return _builtApplication; } private void ConfigureApplication(WebHostBuilderContext context,IApplicationBuilder app) { Debug.Assert(_builtApplication is not null); // UseRouting called before WebApplication such as in a StartupFilter // lets remove the property and reset it at the end so we don't mess with the routes in the filter if (app.Properties.TryGetValue(EndpointRouteBuilderKey,out var priorRouteBuilder)) { app.Properties.Remove(EndpointRouteBuilderKey); } if (context.HostingEnvironment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } // Wrap the entire destination pipeline in UseRouting() and UseEndpoints(),essentially: // destination.UseRouting() // destination.Run(source) // destination.UseEndpoints() // Set the route builder so that UseRouting will use the WebApplication as the IEndpointRouteBuilder for route matching app.Properties.Add(WebApplication.GlobalEndpointRouteBuilderKey,_builtApplication); // Only call UseRouting() if there are endpoints configured and UseRouting() wasn't called on the global route builder already if (_builtApplication.DataSources.Count > 0) { // If this is set,someone called UseRouting() when a global route builder was already set if (!_builtApplication.Properties.TryGetValue(EndpointRouteBuilderKey,out var localRouteBuilder)) { app.UseRouting(); } else { // UseEndpoints will be looking for the RouteBuilder so make sure it's set app.Properties[EndpointRouteBuilderKey] = localRouteBuilder; } } // Wire the source pipeline to run in the destination pipeline app.Use(next => { _builtApplication.Run(next); return _builtApplication.BuildRequestDelegate(); }); if (_builtApplication.DataSources.Count > 0) { // We don't know if user code called UseEndpoints(),so we will call it just in case,UseEndpoints() will ignore duplicate DataSources app.UseEndpoints(_ => { }); } // Copy the properties to the destination app builder foreach (var item in _builtApplication.Properties) { app.Properties[item.Key] = item.Value; } // Remove the route builder to clean up the properties,we're done adding routes to the pipeline app.Properties.Remove(WebApplication.GlobalEndpointRouteBuilderKey); // reset route builder if it existed,this is needed for StartupFilters if (priorRouteBuilder is not null) { app.Properties[EndpointRouteBuilderKey] = priorRouteBuilder; } } private sealed class LoggingBuilder : ILoggingBuilder { public LoggingBuilder(IServiceCollection services) { Services = services; } public IServiceCollection Services { get; } } }
ConfigureHostBuilder
public sealed class ConfigureHostBuilder : IHostBuilder,ISupportsConfigureWebHost IHostBuilder ISupportsConfigureWebHost.ConfigureWebHost(Action<IWebHostBuilder> configure,Action<WebHostBuilderOptions> configureOptions) { throw new NotSupportedException("ConfigureWebHost() is not supported by WebApplicationBuilder.Host. Use the WebApplication returned by WebApplicationBuilder.Build() instead."); }
ConfigureHostBuilder實現IHostBuilder和ISupportsConfigureWebHost,但 ISupportsConfigureWebHost 的實現是假的什麼意思呢?
這意味著雖然以下程式碼可以編譯,但是會在執行時丟擲異常。
WebApplicationBuilder builder = WebApplication.CreateBuilder(args); builder.Host.ConfigureWebHost(webBuilder => { webBuilder.UseStartup<Startup>(); });
ConfigureServices(),該方法Action<>使用IServiceCollection從WebApplicationBuilder. 所以以下兩個呼叫在功能上是相同的:
後一種方法顯然不值得在正常實踐中使用,但仍然可以使用依賴於這種方法的現有程式碼,
public IHostBuilder ConfigureAppConfiguration(Action<HostBuilderContext,IConfigurationBuilder> configureDelegate) { // Run these immediately so that they are observable by the imperative code configureDelegate(_context,_configuration); return this; } public IHostBuilder ConfigureServices(Action<HostBuilderContext,IServiceCollection> configureDelegate) { // Run these immediately so that they are observable by the imperative code configureDelegate(_context,_services); return this; }
builder.Services.AddSingleton<MyImplementation>(); builder.Host.ConfigureServices((ctx,services) => services.AddSingleton<MyImplementation>());
並非所有委託ConfigureHostBuilder都立即傳遞給執行中的方法。例如UseServiceProviderFactory()儲存在列表中,稍後在呼叫WebApplicationBuilder.Build()
public IHostBuilder UseServiceProviderFactory<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory) where TContainerBuilder : notnull { if (factory is null) { throw new ArgumentNullException(nameof(factory)); } _operations.Add(b => b.UseServiceProviderFactory(factory)); return this; }
BootstrapHostBuilder
回到ConfigureHostBuilder我們看內部的BootstrapHostBuilder,它記錄了IHostBuilder收到的所有呼叫。例如ConfigureHostConfiguration()和ConfigureServices(),
這與ConfigureHostBuilder立即執行提供的委託相比,BootstrapHostBuilder的儲存將委託提供給稍後執行的列表。這類似於泛型的HostBuilder工作方式。但請注意,這BootstrapHostBuilder呼叫Build()會引發異常
internal class BootstrapHostBuilder : IHostBuilder { private readonly IServiceCollection _services; private readonly List<Action<IConfigurationBuilder>> _configureHostActions = new(); private readonly List<Action<HostBuilderContext,IConfigurationBuilder>> _configureAppActions = new(); private readonly List<Action<HostBuilderContext,IServiceCollection>> _configureServicesActions = new(); public IHost Build() { // HostingHostBuilderExtensions.ConfigureDefaults should never call this. throw new InvalidOperationException(); } public IHostBuilder ConfigureHostConfiguration(Action<IConfigurationBuilder> configureDelegate) { _configureHostActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate))); return this; } public IHostBuilder ConfigureServices(Action<HostBuilderContext,IServiceCollection> configureDelegate) { // HostingHostBuilderExtensions.ConfigureDefaults calls this via ConfigureLogging _configureServicesActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate))); return this; } // ..... }
WebApplicationBuilder建構函式
最後我們來看WebApplicationBuilder建構函式,註釋都給大家翻譯了
internal WebApplicationBuilder(WebApplicationOptions options,Action<IHostBuilder>? configureDefaults = null) { Services = _services; var args = options.Args; //儘早執行方法配置通用和web主機預設值,以從appsettings.on填充配置 //要預填充的環境變數(以DOTNET和ASPNETCORE為字首)和其他可能的預設源 //正確的預設值。 _bootstrapHostBuilder = new BootstrapHostBuilder(Services,_hostBuilder.Properties); //不要在這裡指定引數,因為我們希望稍後應用它們,以便 //可以覆蓋ConfigureWebHostDefaults指定的預設值 _bootstrapHostBuilder.ConfigureDefaults(args: null); // This is for testing purposes configureDefaults?.Invoke(_bootstrapHostBuilder); //我們上次在這裡指定了命令列,因為我們跳過了對ConfigureDefaults的呼叫中的命令列。 //args可以包含主機和應用程式設定,因此我們要確保 //我們適當地訂購這些配置提供程式,而不復制它們 if (args is { Length: > 0 }) { _bootstrapHostBuilder.ConfigureAppConfiguration(config => { config.AddCommandLine(args); }); } // .... }
// 自ConfigureWebHostDefaults覆蓋特定於主機的設定(應用程式名稱)以來,上次將引數應用於主機配置。 _bootstrapHostBuilder.ConfigureHostConfiguration(config => { if (args is { Length: > 0 }) { config.AddCommandLine(args); } // Apply the options after the args options.ApplyHostConfiguration(config); });
到此你可能都非常疑惑這玩意到底在幹嘛。只要能看明白下面程式碼就好了,呼叫BootstrapHostBuilder.RunDefaultCallbacks(),
它以正確的順序執行我們迄今為止積累的所有儲存的回撥,以構建HostBuilderContext. 該HostBuilderContext則是用來最終設定的剩餘效能WebApplicationBuilder。
完成特定於應用程式的配置後,在由我們手動呼叫Build()建立一個WebApplication例項。
Configuration = new(); // Collect the hosted services separately since we want those to run after the user's hosted services _services.TrackHostedServices = true; // This is the application configuration var (hostContext,hostConfiguration) = _bootstrapHostBuilder.RunDefaultCallbacks(Configuration,_hostBuilder); // Stop tracking here _services.TrackHostedServices = false; // Capture the host configuration values here. We capture the values so that // changes to the host configuration have no effect on the final application. The // host configuration is immutable at this point. _hostConfigurationValues = new(hostConfiguration.AsEnumerable()); // Grab the WebHostBuilderContext from the property bag to use in the ConfigureWebHostBuilder var webHostContext = (WebHostBuilderContext)hostContext.Properties[typeof(WebHostBuilderContext)]; // Grab the IWebHostEnvironment from the webHostContext. This also matches the instance in the IServiceCollection. Environment = webHostContext.HostingEnvironment; Logging = new LoggingBuilder(Services); Host = new ConfigureHostBuilder(hostContext,Configuration,Services); WebHost = new ConfigureWebHostBuilder(webHostContext,Services);
WebApplicationBuilder.Build()
該Build()方法不是非常複雜,首先是將配置的配置源複製到_hostBuilder的ConfigurationBuilder實現中。呼叫此方法時,builder它最初為空,因此這將填充由預設構建器擴充套件方法新增的所有源,以及您隨後配置的額外源。
// source itself here since we don't support mutating the host values after creating the builder.
_hostBuilder.ConfigureHostConfiguration(builder =>
{
builder.AddInMemoryCollection(_hostConfigurationValues);
});
_hoshttp://www.cppcns.comtBuilder.ConfigureAppConfiguration(builder =>
{
builder.Add(chainedConfigSource);
foreach (var (key,value) in ((IConfigurationBuilder)Configuration).Properties)
{
builder.Properties[key] = value;
}
});
接下來,是一樣的事情IServiceCollection,將它們從_services例項複製到_hostBuilder的集合中。
// This needs to go here to avoid adding the IHostedService that boots the server twice (the GenericWebHostService). // Copy the services that were added via WebApplicationBuilder.Services into the final IServiceCollection _hostBuilder.ConfigureServices((context,chainedConfigSource.BuiltProvider)) { ((IConfigurationBuilder)Configuration).Add(new ConfigurationProviderSource(provider)); } } });
接下來執行我們在ConfigureHostBuilder屬性中收集的任何回撥
// Run the other callbacks on the final host builder Host.RunDeferredCallbacks(_hostBuilder);
最後我們呼叫_hostBuilder.Build()構建Host例項,並將其傳遞給 的新例項WebApplication。呼叫_hostBuilder.Build()是呼叫所有註冊回撥的地方。
_builtApplication = new WebApplication(_hostBuilder.Build());
最後,為了保持一切一致ConfigurationManager例項被清除,並連結到儲存在WebApplication. 此外IServiceCollectiononWebApplicationBuilder被標記為只讀,因此在呼叫後嘗試新增服務WebApplicationBuilder將丟擲一個InvalidOperationException. 最後WebApplication返回。
// Mark the service collection as read-only to prevent future modifications _services.IsReadOnly = true; // Resolve both the _hostBuilder's Configuration and builder.Configuration to mark both as resolved within the // service provider ensuring both will be properly disposed with the provider. _ = _builtApplication.Services.GetService<IEnumerable<IConfiguration>>(); return _builtApplication;
差不多就是這樣.
到此這篇關於.Net 6中WebApplicationBuilder介紹和用法的文章就介紹到這了。希望對大家的學習有所幫助,也希望大家多多支援我們。