.Net 6中WebApplicationBuilder介绍和用法
介绍
.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(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(),该方法创建了一个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 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实现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.json填充配置 //要预填充的环境变量(以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, configuration, 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); }); _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属性中收集的任何回调
// 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介绍和用法的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持。