.Net?6中WebApplicationBuilder介紹和用法
介紹
.Net 6為我們帶來的一種全新的引導程序啟動的方式。與之前的拆分成Program.cs和Startup不同,整個引導啟動代碼都在Program.cs中。
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);在上篇文章中,我簡要描述了如何使用 WebApplication和WebApplicationBuilder配置 ASP.NET Core 應用程序。在這篇文章中,我們來深入看下代碼.
正文
我們示例程序的第一步是執(zhí)行WebApplicationBuilder builder = WebApplication.CreateBuilder(args);創(chuàng)建一個WebApplicationBuilder實例。
從命令行中分配Args參數(shù),并將選項對象傳遞給WebApplicationBuilder構造函數(shù)的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(new() { 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(),該方法創(chuàng)建了一個WebApplication. 我刪除了部分講解用不到的代碼。
如果您熟悉 ASP.NET Core,那么其中許多屬性都使用以前版本中的常見類型
- IWebHostEnvironment: 用于檢索環(huán)境
- IServiceCollection: 用于向 DI 容器注冊服務。
- ConfigurationManager: 用于添加新配置和檢索配置值。我在之前的文章有講
- ILoggingBuilder: 用于注冊額外的日志提供程序
在WebHost和Host性質(zhì)很有趣,因為它們暴露出新的類型,ConfigureWebHostBuilder和ConfigureHostBuilder。這些類型分別實現(xiàn)IWebHostBuilder和IHostBuilder。
公開IWebHostBuilder和IHostBuilder接口對于允許從.NET 6 之前的應用程序遷移到新的最小托管,我們?nèi)绾螌⒌膌ambda風格配置IHostBuilder與命令式風格的WebApplicationBuilder協(xié)調(diào)起來,這就是ConfigureHostBuilder和ConfigureWebHostBuilder與一些內(nèi)部沿來IHostBuilder實現(xiàn)。
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 providers.
/// </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, but not building.
/// To build after configuration, 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實現(xiàn)IHostBuilder和ISupportsConfigureWebHost,但 ISupportsConfigureWebHost 的實現(xiàn)是假的什么意思呢?
這意味著雖然以下代碼可以編譯,但是會在運行時拋出異常。
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Host.ConfigureWebHost(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
ConfigureServices(),該方法Action<>使用IServiceCollection從WebApplicationBuilder. 所以以下兩個調(diào)用在功能上是相同的:
后一種方法顯然不值得在正常實踐中使用,但仍然可以使用依賴于這種方法的現(xiàn)有代碼,
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()保存在列表中,稍后在調(diào)用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我們看內(nèi)部的BootstrapHostBuilder,它記錄了IHostBuilder收到的所有調(diào)用。例如ConfigureHostConfiguration()和ConfigureServices(),
這與ConfigureHostBuilder立即執(zhí)行提供的委托相比,BootstrapHostBuilder的保存將委托提供給稍后執(zhí)行的列表。這類似于泛型的HostBuilder工作方式。但請注意,這BootstrapHostBuilder調(diào)用Build()會引發(fā)異常
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構造函數(shù)
最后我們來看WebApplicationBuilder構造函數(shù),注釋都給大家翻譯了
internal WebApplicationBuilder(WebApplicationOptions options, Action<IHostBuilder>? configureDefaults = null)
{
Services = _services;
var args = options.Args;
//盡早運行方法配置通用和web主機默認值,以從appsettings.json填充配置
//要預填充的環(huán)境變量(以DOTNET和ASPNETCORE為前綴)和其他可能的默認源
//正確的默認值。
_bootstrapHostBuilder = new BootstrapHostBuilder(Services, _hostBuilder.Properties);
//不要在這里指定參數(shù),因為我們希望稍后應用它們,以便
//可以覆蓋ConfigureWebHostDefaults指定的默認值
_bootstrapHostBuilder.ConfigureDefaults(args: null);
// This is for testing purposes
configureDefaults?.Invoke(_bootstrapHostBuilder);
//我們上次在這里指定了命令行,因為我們跳過了對ConfigureDefaults的調(diào)用中的命令行。
//args可以包含主機和應用程序設置,因此我們要確保
//我們適當?shù)赜嗁忂@些配置提供程序,而不復制它們
if (args is { Length: > 0 })
{
_bootstrapHostBuilder.ConfigureAppConfiguration(config =>
{
config.AddCommandLine(args);
});
}
// ....
}
// 自ConfigureWebHostDefaults覆蓋特定于主機的設置(應用程序名稱)以來,上次將參數(shù)應用于主機配置。
_bootstrapHostBuilder.ConfigureHostConfiguration(config =>
{
if (args is { Length: > 0 })
{
config.AddCommandLine(args);
}
// Apply the options after the args
options.ApplyHostConfiguration(config);
});
到此你可能都非常疑惑這玩意到底在干嘛。只要能看明白下面代碼就好了,調(diào)用BootstrapHostBuilder.RunDefaultCallbacks(),
它以正確的順序運行我們迄今為止積累的所有存儲的回調(diào),以構建HostBuilderContext. 該HostBuilderContext則是用來最終設定的剩余性能WebApplicationBuilder。
完成特定于應用程序的配置后,在由我們手動調(diào)用Build()創(chuàng)建一個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, Configuration, Services);
WebApplicationBuilder.Build()
該Build()方法不是非常復雜,首先是將配置的配置源復制到_hostBuilder的ConfigurationBuilder實現(xiàn)中。調(diào)用此方法時,builder它最初為空,因此這將填充由默認構建器擴展方法添加的所有源,以及您隨后配置的額外源。
// source itself here since we don't support mutating the host values after creating the builder.
_hostBuilder.ConfigureHostConfiguration(builder =>
{
builder.AddInMemoryCollection(_hostConfigurationValues);
});
_hostBuilder.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, 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));
}
}
});
接下來運行我們在ConfigureHostBuilder屬性中收集的任何回調(diào)
// Run the other callbacks on the final host builder Host.RunDeferredCallbacks(_hostBuilder);
最后我們調(diào)用_hostBuilder.Build()構建Host實例,并將其傳遞給 的新實例WebApplication。調(diào)用_hostBuilder.Build()是調(diào)用所有注冊回調(diào)的地方。
_builtApplication = new WebApplication(_hostBuilder.Build());
最后,為了保持一切一致ConfigurationManager實例被清除,并鏈接到存儲在WebApplication. 此外IServiceCollectiononWebApplicationBuilder被標記為只讀,因此在調(diào)用后嘗試添加服務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介紹和用法的文章就介紹到這了。希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
ASP.NET MVC4入門教程(六):驗證編輯方法和編輯視圖
本文主要演示如何修改控制器和視圖以及處理POST的請求,以達到實現(xiàn)我們想要的功能。2016-04-04
在ASP.NET 2.0中操作數(shù)據(jù)之二十六:排序自定義分頁數(shù)據(jù)
前面教程中我們實現(xiàn)了高效的自定義分頁,但是只是一種固定排序方式。在本教程中,我們通過引入@sortExpression來擴展存儲過程既實現(xiàn)自定義分頁又提供自定義排序的功能。2016-05-05
Microsoft .Net Remoting系列教程之二:Marshal、Disconnect與生命周期以及跟蹤服務
本文主要講解.Net Remoting中Marshal、Disconnect與生命周期以及跟蹤服務,需要的朋友可以參考下。2016-05-05
在ASP.NET 2.0中操作數(shù)據(jù)之五十三:在Data Web控件顯示二進制數(shù)據(jù)
本文主要介紹在ASP.NET 2.0中直接顯示PDF超鏈接的方法,以及如何把已二進制數(shù)據(jù)形式保存的圖片顯示在GridView中的方法,雖然這種方法在實際開發(fā)中很少用,但還是值得學習一下。2016-05-05
ASP.NET中URL Routing和IIS上URL Rewriting的區(qū)別
這篇文章主要介紹了ASP.NET中URL Routing和IIS上URL Rewriting的區(qū)別,需要的朋友可以參考下。2016-06-06
《解剖PetShop》之二:PetShop數(shù)據(jù)訪問層數(shù)之據(jù)庫訪問設計
本文主要講解PetShop4.0的數(shù)據(jù)訪問層設計,包括:數(shù)據(jù)庫訪問、Messaging、MemberShip、Profile四部分,需要的朋友可以參考下。2016-05-05
在ASP.NET 2.0中操作數(shù)據(jù)之四十四:DataList和Repeater數(shù)據(jù)排序(三)
上篇已經(jīng)完成了自定義分頁,這一節(jié)我們繼續(xù)完善排序功能。2016-05-05
ASP.NET MVC Bundles 用法和說明(打包javascript和css)
本文主要介紹了ASP.NET MVC中的新功能Bundles,利用Bundles可以將javascript和css文件打包壓縮,并且可以區(qū)分調(diào)試和非調(diào)試,在調(diào)試時不進行壓縮,以原始方式顯示出來,以方便查找問題。2016-04-04
[翻譯]Scott Mitchell 的ASP.NET 2.0數(shù)據(jù)教程
本文主要是對Scott Mitchell 的ASP.NET 2.0數(shù)據(jù)系列教程的一個索引的整理,方便大家查看全部的教程。2016-05-05
ExecuteReader(),ExecuteNonQuery(),ExecuteScalar(),ExecuteXml
ExecuteReader(),ExecuteNonQuery(),ExecuteScalar(),ExecuteXmlReader()之間的區(qū)別...2006-10-10

