介绍
.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介绍和用法的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持代码网。
发表评论