文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

.Net 6中WebApplicationBuilder原理和用法是什么

2023-06-22 04:58

关注

这篇文章将为大家详细讲解有关.Net 6中WebApplicationBuilder原理和用法是什么,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。

介绍

.Net 6为我们带来的一种全新的引导程序启动的方式。与之前的拆分成Program.cs和Startup不同,整个引导启动代码都在Program.cs中。

我们示例程序的第一步是执行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,那么其中许多属性都使用以前版本中的常见类型

在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 builderHost.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原理和用法是什么就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     801人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     348人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     311人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     432人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     220人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯