欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

.Net 6中WebApplicationBuilder介绍和用法

程序员文章站 2022-06-19 09:58:37
目录介绍正文configurehostbuilderbootstraphostbuilderwebapplicationbuilder构造函数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(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介绍和用法的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持。