ASP.NET Core 6 基础入门系列(17) ASP.NET Core 的核心对象WebApplication与WebApplicationBuilder
- ASP.NET Core 6 基础入门系列(16) 项目的 web.config 文件介绍
- ASP.NET Core 6 基础入门系列(15) 项目在IIS下部署的两种进程托管模型
- ASP.NET Core 6 基础入门系列(14) 项目发布与IIS部署
- ASP.NET Core 6 基础入门系列(13) Web 服务器介绍
- ASP.NET Core 6 基础入门系列(12) 项目的多种启动方式及问题
- ASP.NET Core 6 基础入门系列(11) 项目结构详解之项目入口Program.cs
- ASP.NET Core 6 基础入门系列(10) 项目结构详解之appsettings.json
- ASP.NET Core 6 基础入门系列(9) 项目结构详解之launchSettings.json
- ASP.NET Core 6 基础入门系列(8) 项目结构详解之MVC
- ASP.NET Core 6 基础入门系列(7) 项目结构详解之wwwroot
- ASP.NET Core 6 基础入门系列(6) 项目结构详解之依赖项
- ASP.NET Core 6 基础入门系列(5) 项目结构详解之项目文件管理
- ASP.NET Core 6 基础入门系列(4) 项目结构简介
- ASP.NET Core 6 基础入门系列(3) 新建 ASP.NET Core MVC 6.0 项目
- ASP.NET Core 6 基础入门系列(2) 开发环境准备
- ASP.NET Core 6 基础入门系列(1) ASP.NET Core 6 简介
在我的博客《ASP.NET Core 6 基础入门系列(11) 项目结构详解之项目入口Program.cs》中介绍了ASP.NET Core 项目入口文件的主要内容,其中逻辑代码的第一行中用到了 WebApplication 与 WebApplicationBuilder 类。
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
这里涉及到 ASP.NET Core 主机 的知识点,ASP.NET Core 应用配置和启动“主机”。 主机负责应用程序启动和生存期管理。 至少,主机配置服务器和请求处理管道。 主机还可以设置日志记录、依赖关系注入和配置。
详细请参考微软文档《ASP.NET Core Web 主机》
WebApplication类主要用于配置 HTTP 管道和路由的 Web 应用程序。查看源码
ASP.NET Core 6.0 项目开源地址: https://github.com/dotnet/aspnetcore/tree/v6.0.13
WebApplication 类的完整代码如下

1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 4 using Microsoft.AspNetCore.Hosting; 5 using Microsoft.AspNetCore.Hosting.Server; 6 using Microsoft.AspNetCore.Hosting.Server.Features; 7 using Microsoft.AspNetCore.Http; 8 using Microsoft.AspNetCore.Http.Features; 9 using Microsoft.AspNetCore.Routing; 10 using Microsoft.Extensions.Configuration; 11 using Microsoft.Extensions.DependencyInjection; 12 using Microsoft.Extensions.Hosting; 13 using Microsoft.Extensions.Logging; 14 15 namespace Microsoft.AspNetCore.Builder 16 { 17 /// <summary> 18 /// The web application used to configure the HTTP pipeline, and routes. 19 /// </summary> 20 public sealed class WebApplication : IHost, IApplicationBuilder, IEndpointRouteBuilder, IAsyncDisposable 21 { 22 internal const string GlobalEndpointRouteBuilderKey = "__GlobalEndpointRouteBuilder"; 23 24 private readonly IHost _host; 25 private readonly List<EndpointDataSource> _dataSources = new(); 26 27 internal WebApplication(IHost host) 28 { 29 _host = host; 30 ApplicationBuilder = new ApplicationBuilder(host.Services); 31 Logger = host.Services.GetRequiredService<ILoggerFactory>().CreateLogger(Environment.ApplicationName); 32 33 Properties[GlobalEndpointRouteBuilderKey] = this; 34 } 35 36 /// <summary> 37 /// The application's configured services. 38 /// </summary> 39 public IServiceProvider Services => _host.Services; 40 41 /// <summary> 42 /// The application's configured <see cref="IConfiguration"/>. 43 /// </summary> 44 public IConfiguration Configuration => _host.Services.GetRequiredService<IConfiguration>(); 45 46 /// <summary> 47 /// The application's configured <see cref="IWebHostEnvironment"/>. 48 /// </summary> 49 public IWebHostEnvironment Environment => _host.Services.GetRequiredService<IWebHostEnvironment>(); 50 51 /// <summary> 52 /// Allows consumers to be notified of application lifetime events. 53 /// </summary> 54 public IHostApplicationLifetime Lifetime => _host.Services.GetRequiredService<IHostApplicationLifetime>(); 55 56 /// <summary> 57 /// The default logger for the application. 58 /// </summary> 59 public ILogger Logger { get; } 60 61 /// <summary> 62 /// The list of URLs that the HTTP server is bound to. 63 /// </summary> 64 public ICollection<string> Urls => ServerFeatures.Get<IServerAddressesFeature>()?.Addresses ?? 65 throw new InvalidOperationException($"{nameof(IServerAddressesFeature)} could not be found."); 66 67 IServiceProvider IApplicationBuilder.ApplicationServices 68 { 69 get => ApplicationBuilder.ApplicationServices; 70 set => ApplicationBuilder.ApplicationServices = value; 71 } 72 73 internal IFeatureCollection ServerFeatures => _host.Services.GetRequiredService<IServer>().Features; 74 IFeatureCollection IApplicationBuilder.ServerFeatures => ServerFeatures; 75 76 internal IDictionary<string, object?> Properties => ApplicationBuilder.Properties; 77 IDictionary<string, object?> IApplicationBuilder.Properties => Properties; 78 79 internal ICollection<EndpointDataSource> DataSources => _dataSources; 80 ICollection<EndpointDataSource> IEndpointRouteBuilder.DataSources => DataSources; 81 82 internal ApplicationBuilder ApplicationBuilder { get; } 83 84 IServiceProvider IEndpointRouteBuilder.ServiceProvider => Services; 85 86 /// <summary> 87 /// Initializes a new instance of the <see cref="WebApplication"/> class with preconfigured defaults. 88 /// </summary> 89 /// <param name="args">Command line arguments</param> 90 /// <returns>The <see cref="WebApplication"/>.</returns> 91 public static WebApplication Create(string[]? args = null) => 92 new WebApplicationBuilder(new WebApplicationOptions() { Args = args }).Build(); 93 94 /// <summary> 95 /// Initializes a new instance of the <see cref="WebApplicationBuilder"/> class with preconfigured defaults. 96 /// </summary> 97 /// <returns>The <see cref="WebApplicationBuilder"/>.</returns> 98 public static WebApplicationBuilder CreateBuilder() => 99 new WebApplicationBuilder(new WebApplicationOptions()); 100 101 /// <summary> 102 /// Initializes a new instance of the <see cref="WebApplicationBuilder"/> class with preconfigured defaults. 103 /// </summary> 104 /// <param name="args">Command line arguments</param> 105 /// <returns>The <see cref="WebApplicationBuilder"/>.</returns> 106 public static WebApplicationBuilder CreateBuilder(string[] args) => 107 new WebApplicationBuilder(new WebApplicationOptions() { Args = args }); 108 109 /// <summary> 110 /// Initializes a new instance of the <see cref="WebApplicationBuilder"/> class with preconfigured defaults. 111 /// </summary> 112 /// <param name="options">The <see cref="WebApplicationOptions"/> to configure the <see cref="WebApplicationBuilder"/>.</param> 113 /// <returns>The <see cref="WebApplicationBuilder"/>.</returns> 114 public static WebApplicationBuilder CreateBuilder(WebApplicationOptions options) => 115 new (options); 116 117 /// <summary> 118 /// Start the application. 119 /// </summary> 120 /// <param name="cancellationToken"></param> 121 /// <returns> 122 /// A <see cref="Task"/> that represents the startup of the <see cref="WebApplication"/>. 123 /// Successful completion indicates the HTTP server is ready to accept new requests. 124 /// </returns> 125 public Task StartAsync(CancellationToken cancellationToken = default) => 126 _host.StartAsync(cancellationToken); 127 128 /// <summary> 129 /// Shuts down the application. 130 /// </summary> 131 /// <param name="cancellationToken"></param> 132 /// <returns> 133 /// A <see cref="Task"/> that represents the shutdown of the <see cref="WebApplication"/>. 134 /// Successful completion indicates that all the HTTP server has stopped. 135 /// </returns> 136 public Task StopAsync(CancellationToken cancellationToken = default) => 137 _host.StopAsync(cancellationToken); 138 139 /// <summary> 140 /// Runs an application and returns a Task that only completes when the token is triggered or shutdown is triggered. 141 /// </summary> 142 /// <param name="url">The URL to listen to if the server hasn't been configured directly.</param> 143 /// <returns> 144 /// A <see cref="Task"/> that represents the entire runtime of the <see cref="WebApplication"/> from startup to shutdown. 145 /// </returns> 146 public Task RunAsync(string? url = null) 147 { 148 Listen(url); 149 return HostingAbstractionsHostExtensions.RunAsync(this); 150 } 151 152 /// <summary> 153 /// Runs an application and block the calling thread until host shutdown. 154 /// </summary> 155 /// <param name="url">The URL to listen to if the server hasn't been configured directly.</param> 156 public void Run(string? url = null) 157 { 158 Listen(url); 159 HostingAbstractionsHostExtensions.Run(this); 160 } 161 162 /// <summary> 163 /// Disposes the application. 164 /// </summary> 165 void IDisposable.Dispose() => _host.Dispose(); 166 167 /// <summary> 168 /// Disposes the application. 169 /// </summary> 170 public ValueTask DisposeAsync() => ((IAsyncDisposable)_host).DisposeAsync(); 171 172 internal RequestDelegate BuildRequestDelegate() => ApplicationBuilder.Build(); 173 RequestDelegate IApplicationBuilder.Build() => BuildRequestDelegate(); 174 175 // REVIEW: Should this be wrapping another type? 176 IApplicationBuilder IApplicationBuilder.New() 177 { 178 var newBuilder = ApplicationBuilder.New(); 179 // Remove the route builder so branched pipelines have their own routing world 180 newBuilder.Properties.Remove(GlobalEndpointRouteBuilderKey); 181 return newBuilder; 182 } 183 184 IApplicationBuilder IApplicationBuilder.Use(Func<RequestDelegate, RequestDelegate> middleware) 185 { 186 ApplicationBuilder.Use(middleware); 187 return this; 188 } 189 190 IApplicationBuilder IEndpointRouteBuilder.CreateApplicationBuilder() => ((IApplicationBuilder)this).New(); 191 192 private void Listen(string? url) 193 { 194 if (url is null) 195 { 196 return; 197 } 198 199 var addresses = ServerFeatures.Get<IServerAddressesFeature>()?.Addresses; 200 if (addresses is null) 201 { 202 throw new InvalidOperationException($"Changing the URL is not supported because no valid {nameof(IServerAddressesFeature)} was found."); 203 } 204 if (addresses.IsReadOnly) 205 { 206 throw new InvalidOperationException($"Changing the URL is not supported because {nameof(IServerAddressesFeature.Addresses)} {nameof(ICollection<string>.IsReadOnly)}."); 207 } 208 209 addresses.Clear(); 210 addresses.Add(url); 211 } 212 } 213 }
1、WebApplication 类实现了 IApplicationBuilder、 IEndpointRouteBuilder、 IHost、 IAsyncDisposable、 IDisposable 接口。
2、Program.cs 文件中调用了 CreateBuilder(args) 方法并传入命令行参数以生成一个 WebApplicationBuilder 对象。
查看源码其内部实现如下:
/// <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 WebApplicationBuilder(new WebApplicationOptions() { Args = args });
首先将 命令行参数args 赋值给 WebApplicationOptions 类的 Args 属性,然后将生成的 WebApplicationOptions 对象作为参数传入WebApplicationBuilder 类的构造函数以生成一个 WebApplicationBuilder 对象并返回。
其中 WebApplicationOptions 类是用于配置CreateBuilder()行为的选项。查看源码,WebApplicationOptions 类的实现如下

1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 4 using System; 5 using System.Collections.Generic; 6 using System.Linq; 7 using System.Reflection; 8 using System.Text; 9 using System.Threading.Tasks; 10 using Microsoft.AspNetCore.Hosting; 11 using Microsoft.Extensions.Configuration; 12 using Microsoft.Extensions.Hosting; 13 14 namespace Microsoft.AspNetCore.Builder 15 { 16 /// <summary> 17 /// Options for configuing the behavior for <see cref="WebApplication.CreateBuilder(WebApplicationOptions)"/>. 18 /// </summary> 19 public class WebApplicationOptions 20 { 21 /// <summary> 22 /// The command line arguments. 23 /// </summary> 24 public string[]? Args { get; init; } 25 26 /// <summary> 27 /// The environment name. 28 /// </summary> 29 public string? EnvironmentName { get; init; } 30 31 /// <summary> 32 /// The application name. 33 /// </summary> 34 public string? ApplicationName { get; init; } 35 36 /// <summary> 37 /// The content root path. 38 /// </summary> 39 public string? ContentRootPath { get; init; } 40 41 /// <summary> 42 /// The web root path. 43 /// </summary> 44 public string? WebRootPath { get; init; } 45 46 internal void ApplyHostConfiguration(IConfigurationBuilder builder) 47 { 48 Dictionary<string, string>? config = null; 49 50 if (EnvironmentName is not null) 51 { 52 config = new(); 53 config[HostDefaults.EnvironmentKey] = EnvironmentName; 54 } 55 56 if (ApplicationName is not null) 57 { 58 config ??= new(); 59 config[HostDefaults.ApplicationKey] = ApplicationName; 60 } 61 62 if (ContentRootPath is not null) 63 { 64 config ??= new(); 65 config[HostDefaults.ContentRootKey] = ContentRootPath; 66 } 67 68 if (WebRootPath is not null) 69 { 70 config ??= new(); 71 config[WebHostDefaults.WebRootKey] = WebRootPath; 72 } 73 74 if (config is not null) 75 { 76 builder.AddInMemoryCollection(config); 77 } 78 } 79 80 internal void ApplyApplicationName(IWebHostBuilder webHostBuilder) 81 { 82 string? applicationName = null; 83 84 // We need to "parse" the args here since 85 // we need to set the application name via UseSetting 86 if (Args is not null) 87 { 88 var config = new ConfigurationBuilder() 89 .AddCommandLine(Args) 90 .Build(); 91 92 applicationName = config[WebHostDefaults.ApplicationKey]; 93 94 // This isn't super important since we're not adding any disposable sources 95 // but just in case 96 if (config is IDisposable disposable) 97 { 98 disposable.Dispose(); 99 } 100 } 101 102 // Application name overrides args 103 if (ApplicationName is not null) 104 { 105 applicationName = ApplicationName; 106 } 107 108 // We need to override the application name since the call to Configure will set it to 109 // be the calling assembly's name. 110 applicationName ??= Assembly.GetEntryAssembly()?.GetName()?.Name ?? string.Empty; 111 112 webHostBuilder.UseSetting(WebHostDefaults.ApplicationKey, applicationName); 113 } 114 } 115 }
其核心属性
- ApplicationName:应用程序名称
- Args:命令行参数
- ContentRootPath:内容根路径
- EnvironmentName:环境名称
- WebRootPath:Web 根路径
调试程序,查看运行结果如下,从下图可以看出,通过调用 WebApplication.CreateBuilder(args) 方法创建 WebApplicationBuilder 之后,通过 builder.Environment 属性查看项目的环境信息。使用的都是 ASP.NET Core 项目中预设的默认信息。
通过 WebApplication 对象的 Environment 属性也可以查看项目的环境信息,与上述结果一致。
通过 WebApplication.CreateBuilder(args) 创建 WebApplicationBuilder 对象之后,无法更改任何主机设置,例如应用名称、环境或内容根。
如果想实现自定义的一些配置,则需要使用 CreateBuilder() 的重载方法
查看源码,其内部实现如下
/// <summary> /// Initializes a new instance of the <see cref="WebApplicationBuilder"/> class with preconfigured defaults. /// </summary> /// <param name="options">The <see cref="WebApplicationOptions"/> to configure the <see cref="WebApplicationBuilder"/>.</param> /// <returns>The <see cref="WebApplicationBuilder"/>.</returns> public static WebApplicationBuilder CreateBuilder(WebApplicationOptions options) => new (options);
通过 WebApplication.CreateBuilder(options) 实现方式如下(以下仅为测试)
创建 WebApplicationOptions 对象的同时,将 args 参数赋值给 Args 属性,同时根据实际需要设置其他自定义的配置信息,如设置web静态资源的存储目录为 webroot等。
调试程序,查看运行结果如下,从下图可以看出,通过调用 WebApplication.CreateBuilder(options) 方法创建 WebApplicationBuilder 对象之后,通过 builder.Environment 属性查看项目的环境信息,自定义的信息已经生效
3、调用中间件
Program.cs 文件中 通过 执行builder.Build() 构建了一个 WebApplication 对象,然后调用丰富的中间件组件以实现不同的功能。中间件是系统内置的针对 IApplicationBuilder 接口进行扩展的方法。因为 WebApplication 继承了IApplicationBuilder 接口,所以其可以调用扩展的中间件。
通过 WebApplication.CreateBuilder(args) 方法创建了 WebApplicationBuilder 对象, 再仔细分析CreateBuilder()方法,其内部通过初始化 WebApplicationBuilder 类的新实例并返回
1 /// <summary> 2 /// Initializes a new instance of the <see cref="WebApplicationBuilder"/> class with preconfigured defaults. 3 /// </summary> 4 /// <param name="args">Command line arguments</param> 5 /// <returns>The <see cref="WebApplicationBuilder"/>.</returns> 6 public static WebApplicationBuilder CreateBuilder(string[] args) => 7 new WebApplicationBuilder(new WebApplicationOptions() { Args = args });
WebApplicationBuilder 类的完整实现如下

1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 4 using System.Diagnostics; 5 using System.Linq; 6 using Microsoft.AspNetCore.Hosting; 7 using Microsoft.Extensions.Configuration; 8 using Microsoft.Extensions.DependencyInjection; 9 using Microsoft.Extensions.Hosting; 10 using Microsoft.Extensions.Logging; 11 12 namespace Microsoft.AspNetCore.Builder 13 { 14 /// <summary> 15 /// A builder for web applications and services. 16 /// </summary> 17 public sealed class WebApplicationBuilder 18 { 19 private const string EndpointRouteBuilderKey = "__EndpointRouteBuilder"; 20 21 private readonly HostBuilder _hostBuilder = new(); 22 private readonly BootstrapHostBuilder _bootstrapHostBuilder; 23 private readonly WebApplicationServiceCollection _services = new(); 24 private readonly List<KeyValuePair<string, string>> _hostConfigurationValues; 25 private readonly ConfigurationManager _hostConfigurationManager = new(); 26 27 private WebApplication? _builtApplication; 28 29 internal WebApplicationBuilder(WebApplicationOptions options, Action<IHostBuilder>? configureDefaults = null) 30 { 31 Services = _services; 32 33 var args = options.Args; 34 35 // Run methods to configure both generic and web host defaults early to populate config from appsettings.json 36 // environment variables (both DOTNET_ and ASPNETCORE_ prefixed) and other possible default sources to prepopulate 37 // the correct defaults. 38 _bootstrapHostBuilder = new BootstrapHostBuilder(Services, _hostBuilder.Properties); 39 40 // Don't specify the args here since we want to apply them later so that args 41 // can override the defaults specified by ConfigureWebHostDefaults 42 _bootstrapHostBuilder.ConfigureDefaults(args: null); 43 44 // This is for testing purposes 45 configureDefaults?.Invoke(_bootstrapHostBuilder); 46 47 // We specify the command line here last since we skipped the one in the call to ConfigureDefaults. 48 // The args can contain both host and application settings so we want to make sure 49 // we order those configuration providers appropriately without duplicating them 50 if (args is { Length: > 0 }) 51 { 52 _bootstrapHostBuilder.ConfigureAppConfiguration(config => 53 { 54 config.AddCommandLine(args); 55 }); 56 } 57 58 _bootstrapHostBuilder.ConfigureWebHostDefaults(webHostBuilder => 59 { 60 // Runs inline. 61 webHostBuilder.Configure(ConfigureApplication); 62 63 // Attempt to set the application name from options 64 options.ApplyApplicationName(webHostBuilder); 65 }); 66 67 // Apply the args to host configuration last since ConfigureWebHostDefaults overrides a host specific setting (the application name). 68 _bootstrapHostBuilder.ConfigureHostConfiguration(config => 69 { 70 if (args is { Length: > 0 }) 71 { 72 config.AddCommandLine(args); 73 } 74 75 // Apply the options after the args 76 options.ApplyHostConfiguration(config); 77 }); 78 79 Configuration = new(); 80 // This is chained as the first configuration source in Configuration so host config can be added later without overriding app config. 81 Configuration.AddConfiguration(_hostConfigurationManager); 82 83 // Collect the hosted services separately since we want those to run after the user's hosted services 84 _services.TrackHostedServices = true; 85 86 // This is the application configuration 87 var (hostContext, hostConfiguration) = _bootstrapHostBuilder.RunDefaultCallbacks(Configuration, _hostBuilder); 88 89 // Stop tracking here 90 _services.TrackHostedServices = false; 91 92 // Capture the host configuration values here. We capture the values so that 93 // changes to the host configuration have no effect on the final application. The 94 // host configuration is immutable at this point. 95 _hostConfigurationValues = new(hostConfiguration.AsEnumerable()); 96 97 // Grab the WebHostBuilderContext from the property bag to use in the ConfigureWebHostBuilder 98 var webHostContext = (WebHostBuilderContext)hostContext.Properties[typeof(WebHostBuilderContext)]; 99 100 // Grab the IWebHostEnvironment from the webHostContext. This also matches the instance in the IServiceCollection. 101 Environment = webHostContext.HostingEnvironment; 102 Logging = new LoggingBuilder(Services); 103 Host = new ConfigureHostBuilder(hostContext, Configuration, Services); 104 WebHost = new ConfigureWebHostBuilder(webHostContext, Configuration, Services); 105 106 Services.AddSingleton<IConfiguration>(_ => Configuration); 107 } 108 109 /// <summary> 110 /// Provides information about the web hosting environment an application is running. 111 /// </summary> 112 public IWebHostEnvironment Environment { get; } 113 114 /// <summary> 115 /// A collection of services for the application to compose. This is useful for adding user provided or framework provided services. 116 /// </summary> 117 public IServiceCollection Services { get; } 118 119 /// <summary> 120 /// A collection of configuration providers for the application to compose. This is useful for adding new configuration sources and providers. 121 /// </summary> 122 public ConfigurationManager Configuration { get; } 123 124 /// <summary> 125 /// A collection of logging providers for the application to compose. This is useful for adding new logging providers. 126 /// </summary> 127 public ILoggingBuilder Logging { get; } 128 129 /// <summary> 130 /// An <see cref="IWebHostBuilder"/> for configuring server specific properties, but not building. 131 /// To build after configuration, call <see cref="Build"/>. 132 /// </summary> 133 public ConfigureWebHostBuilder WebHost { get; } 134 135 /// <summary> 136 /// An <see cref="IHostBuilder"/> for configuring host specific properties, but not building. 137 /// To build after configuration, call <see cref="Build"/>. 138 /// </summary> 139 public ConfigureHostBuilder Host { get; } 140 141 /// <summary> 142 /// Builds the <see cref="WebApplication"/>. 143 /// </summary> 144 /// <returns>A configured <see cref="WebApplication"/>.</returns> 145 public WebApplication Build() 146 { 147 // Wire up the host configuration here. We don't try to preserve the configuration 148 // source itself here since we don't support mutating the host values after creating the builder. 149 _hostBuilder.ConfigureHostConfiguration(builder => 150 { 151 builder.AddInMemoryCollection(_hostConfigurationValues); 152 }); 153 154 var chainedConfigSource = new TrackingChainedConfigurationSource(Configuration); 155 156 // Wire up the application configuration by copying the already built configuration providers over to final configuration builder. 157 // We wrap the existing provider in a configuration source to avoid re-bulding the already added configuration sources. 158 _hostBuilder.ConfigureAppConfiguration(builder => 159 { 160 builder.Add(chainedConfigSource); 161 162 foreach (var (key, value) in ((IConfigurationBuilder)Configuration).Properties) 163 { 164 builder.Properties[key] = value; 165 } 166 }); 167 168 // This needs to go here to avoid adding the IHostedService that boots the server twice (the GenericWebHostService). 169 // Copy the services that were added via WebApplicationBuilder.Services into the final IServiceCollection 170 _hostBuilder.ConfigureServices((context, services) => 171 { 172 // We've only added services configured by the GenericWebHostBuilder and WebHost.ConfigureWebDefaults 173 // at this point. HostBuilder news up a new ServiceCollection in HostBuilder.Build() we haven't seen 174 // until now, so we cannot clear these services even though some are redundant because 175 // we called ConfigureWebHostDefaults on both the _deferredHostBuilder and _hostBuilder. 176 foreach (var s in _services) 177 { 178 services.Add(s); 179 } 180 181 // Add the hosted services that were initially added last 182 // this makes sure any hosted services that are added run after the initial set 183 // of hosted services. This means hosted services run before the web host starts. 184 foreach (var s in _services.HostedServices) 185 { 186 services.Add(s); 187 } 188 189 // Clear the hosted services list out 190 _services.HostedServices.Clear(); 191 192 // Add any services to the user visible service collection so that they are observable 193 // just in case users capture the Services property. Orchard does this to get a "blueprint" 194 // of the service collection 195 196 // Drop the reference to the existing collection and set the inner collection 197 // to the new one. This allows code that has references to the service collection to still function. 198 _services.InnerCollection = services; 199 200 // Keep any configuration sources added before the TrackingChainedConfigurationSource (namely host configuration from _hostConfigurationValues) 201 // from overriding config values set via Configuration by inserting them at beginning using _hostConfigurationValues. 202 var beforeChainedConfig = true; 203 var hostBuilderProviders = ((IConfigurationRoot)context.Configuration).Providers; 204 205 if (!hostBuilderProviders.Contains(chainedConfigSource.BuiltProvider)) 206 { 207 // Something removed the _hostBuilder's TrackingChainedConfigurationSource pointing back to the ConfigurationManager. 208 // This is likely a test using WebApplicationFactory. Replicate the effect by clearing the ConfingurationManager sources. 209 ((IConfigurationBuilder)Configuration).Sources.Clear(); 210 beforeChainedConfig = false; 211 } 212 213 // Make the ConfigurationManager match the final _hostBuilder's configuration. To do that, we add the additional providers 214 // to the inner _hostBuilders's configuration to the ConfigurationManager. We wrap the existing provider in a 215 // configuration source to avoid rebuilding or reloading the already added configuration sources. 216 foreach (var provider in hostBuilderProviders) 217 { 218 if (ReferenceEquals(provider, chainedConfigSource.BuiltProvider)) 219 { 220 beforeChainedConfig = false; 221 } 222 else 223 { 224 IConfigurationBuilder configBuilder = beforeChainedConfig ? _hostConfigurationManager : Configuration; 225 configBuilder.Add(new ConfigurationProviderSource(provider)); 226 } 227 } 228 }); 229 230 // Run the other callbacks on the final host builder 231 Host.RunDeferredCallbacks(_hostBuilder); 232 233 _builtApplication = new WebApplication(_hostBuilder.Build()); 234 235 // Mark the service collection as read-only to prevent future modifications 236 _services.IsReadOnly = true; 237 238 // Resolve both the _hostBuilder's Configuration and builder.Configuration to mark both as resolved within the 239 // service provider ensuring both will be properly disposed with the provider. 240 _ = _builtApplication.Services.GetService<IEnumerable<IConfiguration>>(); 241 242 return _builtApplication; 243 } 244 245 private void ConfigureApplication(WebHostBuilderContext context, IApplicationBuilder app) 246 { 247 Debug.Assert(_builtApplication is not null); 248 249 // UseRouting called before WebApplication such as in a StartupFilter 250 // lets remove the property and reset it at the end so we don't mess with the routes in the filter 251 if (app.Properties.TryGetValue(EndpointRouteBuilderKey, out var priorRouteBuilder)) 252 { 253 app.Properties.Remove(EndpointRouteBuilderKey); 254 } 255 256 if (context.HostingEnvironment.IsDevelopment()) 257 { 258 app.UseDeveloperExceptionPage(); 259 } 260 261 // Wrap the entire destination pipeline in UseRouting() and UseEndpoints(), essentially: 262 // destination.UseRouting() 263 // destination.Run(source) 264 // destination.UseEndpoints() 265 266 // Set the route builder so that UseRouting will use the WebApplication as the IEndpointRouteBuilder for route matching 267 app.Properties.Add(WebApplication.GlobalEndpointRouteBuilderKey, _builtApplication); 268 269 // Only call UseRouting() if there are endpoints configured and UseRouting() wasn't called on the global route builder already 270 if (_builtApplication.DataSources.Count > 0) 271 { 272 // If this is set, someone called UseRouting() when a global route builder was already set 273 if (!_builtApplication.Properties.TryGetValue(EndpointRouteBuilderKey, out var localRouteBuilder)) 274 { 275 app.UseRouting(); 276 } 277 else 278 { 279 // UseEndpoints will be looking for the RouteBuilder so make sure it's set 280 app.Properties[EndpointRouteBuilderKey] = localRouteBuilder; 281 } 282 } 283 284 // Wire the source pipeline to run in the destination pipeline 285 app.Use(next => 286 { 287 _builtApplication.Run(next); 288 return _builtApplication.BuildRequestDelegate(); 289 }); 290 291 if (_builtApplication.DataSources.Count > 0) 292 { 293 // We don't know if user code called UseEndpoints(), so we will call it just in case, UseEndpoints() will ignore duplicate DataSources 294 app.UseEndpoints(_ => { }); 295 } 296 297 // Copy the properties to the destination app builder 298 foreach (var item in _builtApplication.Properties) 299 { 300 app.Properties[item.Key] = item.Value; 301 } 302 303 // Remove the route builder to clean up the properties, we're done adding routes to the pipeline 304 app.Properties.Remove(WebApplication.GlobalEndpointRouteBuilderKey); 305 306 // reset route builder if it existed, this is needed for StartupFilters 307 if (priorRouteBuilder is not null) 308 { 309 app.Properties[EndpointRouteBuilderKey] = priorRouteBuilder; 310 } 311 } 312 313 private sealed class LoggingBuilder : ILoggingBuilder 314 { 315 public LoggingBuilder(IServiceCollection services) 316 { 317 Services = services; 318 } 319 320 public IServiceCollection Services { get; } 321 } 322 } 323 }
分析 WebApplicationBuilder 类的构造函数
1 internal WebApplicationBuilder(WebApplicationOptions options, Action<IHostBuilder>? configureDefaults = null) 2 { 3 Services = _services; 4 5 var args = options.Args; 6 7 // 运行方法以尽早配置通用和web主机默认值,以从appsettings.json环境变量(以DOTNET_和ASPNETCORE_为前缀)和其他可能的默认源填充config,以预填充正确的默认值。 8 _bootstrapHostBuilder = new BootstrapHostBuilder(Services, _hostBuilder.Properties); 9 10 // 不要在此处指定参数,因为我们希望稍后应用它们可以覆盖ConfigureWebHostDefaults指定的默认值 11 _bootstrapHostBuilder.ConfigureDefaults(args: null); 12 13 // 这是为了测试目的 14 configureDefaults?.Invoke(_bootstrapHostBuilder); 15 16 // 上次在这里指定了命令行,因为跳过了 ConfigureDefaults 调用中的命令行。 17 // 参数可以包含主机和应用程序设置,因此我们希望确保正确订购这些配置提供程序,而不复制它们 18 if (args is { Length: > 0 }) 19 { 20 _bootstrapHostBuilder.ConfigureAppConfiguration(config => 21 { 22 config.AddCommandLine(args); 23 }); 24 } 25 26 _bootstrapHostBuilder.ConfigureWebHostDefaults(webHostBuilder => 27 { 28 // 内联运行 29 webHostBuilder.Configure(ConfigureApplication); 30 31 // 尝试从选项设置应用程序名称 32 options.ApplyApplicationName(webHostBuilder); 33 }); 34 35 // 由于ConfigureWebHostDefaults覆盖特定于主机的设置(应用程序名称),最后将参数应用于主机配置。 36 _bootstrapHostBuilder.ConfigureHostConfiguration(config => 37 { 38 if (args is { Length: > 0 }) 39 { 40 config.AddCommandLine(args); 41 } 42 43 // 在参数后应用选项 44 options.ApplyHostConfiguration(config); 45 }); 46 47 Configuration = new(); 48 // 这是作为配置中的第一个配置源链接的,因此可以稍后添加主机配置,而无需覆盖应用程序配置。 49 Configuration.AddConfiguration(_hostConfigurationManager); 50 51 // 单独收集托管服务,因为我们希望这些服务在用户的托管服务之后运行 52 _services.TrackHostedServices = true; 53 54 // 这是应用程序配置 55 var (hostContext, hostConfiguration) = _bootstrapHostBuilder.RunDefaultCallbacks(Configuration, _hostBuilder); 56 57 // 此处停止跟踪 58 _services.TrackHostedServices = false; 59 60 //在此捕获主机配置值。我们捕获这些值,以便对主机配置的更改不会对最终应用程序产生影响。此时主机配置是不可变的。 61 _hostConfigurationValues = new(hostConfiguration.AsEnumerable()); 62 63 // 从属性包中获取 WebHostBuilderContext 以在 ConfigureWebHostBuilder 中使用 64 var webHostContext = (WebHostBuilderContext)hostContext.Properties[typeof(WebHostBuilderContext)]; 65 66 // 从webHostContext中获取IWebHostEnvironment。这也与IServiceCollection中的实例匹配。 67 Environment = webHostContext.HostingEnvironment; 68 Logging = new LoggingBuilder(Services); 69 Host = new ConfigureHostBuilder(hostContext, Configuration, Services); 70 WebHost = new ConfigureWebHostBuilder(webHostContext, Configuration, Services); 71 72 Services.AddSingleton<IConfiguration>(_ => Configuration); 73 }
1、其内部完成如下工作
(1)第3行,通过 WebApplicationServiceCollection _services = new() 方式创建的服务容器赋值给 IServiceCollection 类型的 Services 属性。
Progarm.cs 文件中用到的服务容器 Services 就是该容器。
(2)第8行, _bootstrapHostBuilder = new BootstrapHostBuilder(Services, _hostBuilder.Properties); 创建了一个引导配置程序对象。
(3)第11行,执行 _bootstrapHostBuilder.ConfigureDefaults(args: null); 使用预先配置的默认值配置现有的 IHostBuilder 实例。
ConfigureDefaults()方法内部实现如下:

1 public static IHostBuilder ConfigureDefaults(this IHostBuilder builder, string[] args) 2 { 3 builder.UseContentRoot(Directory.GetCurrentDirectory()); 4 builder.ConfigureHostConfiguration(config => 5 { 6 config.AddEnvironmentVariables(prefix: "DOTNET_"); 7 if (args is { Length: > 0 }) 8 { 9 config.AddCommandLine(args); 10 } 11 }); 12 13 builder.ConfigureAppConfiguration((hostingContext, config) => 14 { 15 IHostEnvironment env = hostingContext.HostingEnvironment; 16 bool reloadOnChange = GetReloadConfigOnChangeValue(hostingContext); 17 18 config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: reloadOnChange) 19 .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: reloadOnChange); 20 21 if (env.IsDevelopment() && env.ApplicationName is { Length: > 0 }) 22 { 23 var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName)); 24 if (appAssembly is not null) 25 { 26 config.AddUserSecrets(appAssembly, optional: true, reloadOnChange: reloadOnChange); 27 } 28 } 29 30 config.AddEnvironmentVariables(); 31 32 if (args is { Length: > 0 }) 33 { 34 config.AddCommandLine(args); 35 } 36 }) 37 .ConfigureLogging((hostingContext, logging) => 38 { 39 bool isWindows = 40 #if NET6_0_OR_GREATER 41 OperatingSystem.IsWindows(); 42 #else 43 RuntimeInformation.IsOSPlatform(OSPlatform.Windows); 44 #endif 45 46 // IMPORTANT: This needs to be added *before* configuration is loaded, this lets 47 // the defaults be overridden by the configuration. 48 if (isWindows) 49 { 50 // Default the EventLogLoggerProvider to warning or above 51 logging.AddFilter<EventLogLoggerProvider>(level => level >= LogLevel.Warning); 52 } 53 54 logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); 55 #if NET6_0_OR_GREATER 56 if (!OperatingSystem.IsBrowser()) 57 #endif 58 { 59 logging.AddConsole(); 60 } 61 logging.AddDebug(); 62 logging.AddEventSourceLogger(); 63 64 if (isWindows) 65 { 66 // Add the EventLogLoggerProvider on windows machines 67 logging.AddEventLog(); 68 } 69 70 logging.Configure(options => 71 { 72 options.ActivityTrackingOptions = 73 ActivityTrackingOptions.SpanId | 74 ActivityTrackingOptions.TraceId | 75 ActivityTrackingOptions.ParentId; 76 }); 77 78 }) 79 .UseDefaultServiceProvider((context, options) => 80 { 81 bool isDevelopment = context.HostingEnvironment.IsDevelopment(); 82 options.ValidateScopes = isDevelopment; 83 options.ValidateOnBuild = isDevelopment; 84 }); 85 86 return builder; 87 88 [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "Calling IConfiguration.GetValue is safe when the T is bool.")] 89 static bool GetReloadConfigOnChangeValue(HostBuilderContext hostingContext) => hostingContext.Configuration.GetValue("hostBuilder:reloadConfigOnChange", defaultValue: true); 90 }
其内部逻辑实现了如下功能:
- 将内容根路径设置为由 Directory.GetCurrentDirectory() 返回的路径
- 通过以下对象加载主机配置
- 前缀为 DOTNET_ 的环境变量
- 命令行参数
- 前缀为 DOTNET_ 的环境变量
- 按以下顺序加载应用配置(优先级从低到高。如果出现同名配置,则优先级高的配置覆盖优先级低的配置)
- appsettings.json
- appsettings.{EnvironmentName}.json
- 判断如果是开发者模式(Development),则加载应用程序文件(xx.dll)并读取用户秘钥信息
- 环境变量
- 命令行参数
在主机和应用程序配置中设置相同的配置键时,将使用应用程序的配置值。
- 配置 Logging 日志信息
- 如果应用程序是运行在Windows平台,则添加 EventLogLoggerProvider 过滤器,并设置日志级别。
- 读取 appsettings.json 与 appsettings.{EnvironmentName}.json 文件中的 Logging节点配置的日志信息。
- 如果当前应用程序不是在浏览器中作为WASM运行,则向日志工厂中添加 Console 控制台日志提供程序。
- 向日志工厂中添加 Debug 日志提供程序。
- 向日志工厂中添加 EventSource 日志提供程序。
- 向日志工厂中添加 EventSourceLogger 日志提供程序。
- 如果应用程序是运行在 Windows 平台,则向日志工厂中添加 EventLog 日志提供程序。
- 配置默认的服务提供程序。当为“开发”环境时,启用范围验证和依赖关系验证
(4)第26至33行,执行 ConfigureWebHostDefaults() 方法以配置Web主机默认信息,其中设置了应用程序的名称。
(5)第36至45行,执行 ConfigureHostConfiguration() 方法以配置主机默认信息(先读取命令行参数,然后再应用主机配置)。
(6)第47行,Configuration = new(); 创建一个 ConfigurationManager 对象。
(7)第55行,执行 var (hostContext, hostConfiguration) = _bootstrapHostBuilder.RunDefaultCallbacks(Configuration, _hostBuilder); 主要应用程序配置。逻辑如下:

1 public (HostBuilderContext, ConfigurationManager) RunDefaultCallbacks(ConfigurationManager configuration, HostBuilder innerBuilder) 2 { 3 var hostConfiguration = new ConfigurationManager(); 4 5 foreach (var configureHostAction in _configureHostActions) 6 { 7 configureHostAction(hostConfiguration); 8 } 9 10 // This is the hosting environment based on configuration we've seen so far. 11 var hostingEnvironment = new HostingEnvironment() 12 { 13 ApplicationName = hostConfiguration[HostDefaults.ApplicationKey], 14 EnvironmentName = hostConfiguration[HostDefaults.EnvironmentKey] ?? Environments.Production, 15 ContentRootPath = HostingPathResolver.ResolvePath(hostConfiguration[HostDefaults.ContentRootKey]), 16 }; 17 18 hostingEnvironment.ContentRootFileProvider = new PhysicalFileProvider(hostingEnvironment.ContentRootPath); 19 20 // Normalize the content root setting for the path in configuration 21 hostConfiguration[HostDefaults.ContentRootKey] = hostingEnvironment.ContentRootPath; 22 23 var hostContext = new HostBuilderContext(Properties) 24 { 25 Configuration = hostConfiguration, 26 HostingEnvironment = hostingEnvironment, 27 }; 28 29 // Split the host configuration and app configuration so that the 30 // subsequent callback don't get a chance to modify the host configuration. 31 configuration.SetBasePath(hostingEnvironment.ContentRootPath); 32 33 // Chain the host configuration and app configuration together. 34 configuration.AddConfiguration(hostConfiguration, shouldDisposeConfiguration: true); 35 36 // ConfigureAppConfiguration cannot modify the host configuration because doing so could 37 // change the environment, content root and application name which is not allowed at this stage. 38 foreach (var configureAppAction in _configureAppActions) 39 { 40 configureAppAction(hostContext, configuration); 41 } 42 43 // Update the host context, everything from here sees the final 44 // app configuration 45 hostContext.Configuration = configuration; 46 47 foreach (var configureServicesAction in _configureServicesActions) 48 { 49 configureServicesAction(hostContext, _services); 50 } 51 52 foreach (var callback in _remainingOperations) 53 { 54 callback(innerBuilder); 55 } 56 57 return (hostContext, hostConfiguration); 58 }
(8)第95至98行,设置了Environment、Logging、Host、WebHost等信息
(9)第72行,以上所有逻辑实现了web主机与应用程序的配置信息,最后将 Configuration 配置对象加入服务容器中,供开发者在应用程序中使用。
2、进入到 WebApplicationBuilder 类的内部查看构造函数,其包含如下重要属性
通过它们可以设置与或获取配置信息、环境信息、日志信息、请求地址列表、服务集合等。其中最常用的是通过 Services 属性向容器中注册各种服务,配置的服务在整个应用程序中可用,供开发者在不同的业务逻辑中调用。
3、执行 builder.Build() 方法,使用一组默认选项配置主机
其内部实现逻辑如下:

1 public WebApplication Build() 2 { 3 // 在此连接主机配置。我们不会试图保留配置源代码本身,因为不支持在创建生成器后更改主机值 4 _hostBuilder.ConfigureHostConfiguration(builder => 5 { 6 builder.AddInMemoryCollection(_hostConfigurationValues); 7 }); 8 9 var chainedConfigSource = new TrackingChainedConfigurationSource(Configuration); 10 11 // 通过将已构建的配置提供程序复制到最终的配置生成器来连接应用程序配置。将现有提供程序包装在配置源中,以避免重新构建已添加的配置源。 12 _hostBuilder.ConfigureAppConfiguration(builder => 13 { 14 builder.Add(chainedConfigSource); 15 16 foreach (var (key, value) in ((IConfigurationBuilder)Configuration).Properties) 17 { 18 builder.Properties[key] = value; 19 } 20 }); 21 22 // 这需要转到此处以避免添加两次引导服务器的 IHostedService(GenericWebHostService)。 23 // 将通过 WebApplicationBuilder.services 添加的服务复制到最终的 IServiceCollection 中 24 _hostBuilder.ConfigureServices((context, services) => 25 { 26 /* 此时,只添加了由 GenericWebHostBuilder 和 WebHost.ConfigureWebDefaults 配置的服务。 27 * HostBuilder 在 HostBuilder.Build()中发布了一个新的 ServiceCollection,现在还没有看到,因此无法清除这些服务,即使有些服务是多余的, 28 * 因为在 _deferredHostBuilder 和 _hostBuilder 上调用 ConfigureWebHostDefaults。 29 */ 30 foreach (var s in _services) 31 { 32 services.Add(s); 33 } 34 35 /* 添加最初添加的托管服务,这将确保添加的任何托管服务都在初始托管服务集之后运行。这意味着托管服务在web主机启动之前运行。*/ 36 foreach (var s in _services.HostedServices) 37 { 38 services.Add(s); 39 } 40 41 _services.HostedServices.Clear(); 42 43 /* 将任何服务添加到用户可见的服务集合中,以便在用户捕获services属性时可以观察到这些服务。Orchard这样做是为了获得服务集合的“蓝图”*/ 44 45 /* 删除对现有集合的引用,并将内部集合设置为新集合。这允许具有对服务集合的引用的代码仍然运行 */ 46 _services.InnerCollection = services; 47 48 /* 在 TrackingChainedConfigurationSource 之前添加的任何配置源(即_hostConfigurationValues中的主机配置), 49 * 通过在开头使用 _hostConfigurationValue 插入配置值,避免覆盖通过 configuration 设置的配置值。 50 */ 51 var beforeChainedConfig = true; 52 var hostBuilderProviders = ((IConfigurationRoot)context.Configuration).Providers; 53 54 if (!hostBuilderProviders.Contains(chainedConfigSource.BuiltProvider)) 55 { 56 /* 删除了指向ConfigurationManager的_hostBuilder的TrackingChainedConfigurationSource。这可能是使用WebApplicationFactory进行的测试。 57 * 通过清除ConfirmationManager源复制效果。 58 */ 59 ((IConfigurationBuilder)Configuration).Sources.Clear(); 60 beforeChainedConfig = false; 61 } 62 63 /* 使 ConfigurationManager 与最终 _hostBuilder 的配置匹配。 64 * 为此,将额外的提供程序添加到 ConfigurationManager 的内部 _hostBuilders 配置中。 65 * 将现有提供程序包装在配置源中,以避免重新生成或重新加载已添加的配置源。 66 */ 67 foreach (var provider in hostBuilderProviders) 68 { 69 if (ReferenceEquals(provider, chainedConfigSource.BuiltProvider)) 70 { 71 beforeChainedConfig = false; 72 } 73 else 74 { 75 IConfigurationBuilder configBuilder = beforeChainedConfig ? _hostConfigurationManager : Configuration; 76 configBuilder.Add(new ConfigurationProviderSource(provider)); 77 } 78 } 79 }); 80 81 // 在最终的主机生成器上运行其他回调 82 Host.RunDeferredCallbacks(_hostBuilder); 83 84 _builtApplication = new WebApplication(_hostBuilder.Build()); 85 86 //将服务集合标记为只读以防止将来修改 87 _services.IsReadOnly = true; 88 89 /* 解析_hostBuilder的Configuration和builder.Configuration,以在服务提供程序中将两者标记为已解析,以确保两者都将与提供程序一起正确处理。 */ 90 _ = _builtApplication.Services.GetService<IEnumerable<IConfiguration>>(); 91 92 return _builtApplication; 93 }
ASP.NET Core 应用配置和启动“主机”。 主机负责应用程序启动和生存期管理。 ASP.NET Core 模板创建的 WebApplicationBuilder 包含主机。 虽然可以在主机和应用程序配置提供程序中完成一些配置,但通常,只有主机必需的配置才应在主机配置中完成。应用程序配置具有最高优先级。
成在管理,败在经验;嬴在选择,输在不学! 贵在坚持!
个人作品
BIMFace.SDK.NET
开源地址:https://gitee.com/NAlps/BIMFace.SDK
系列博客:https://www.cnblogs.com/SavionZhang/p/11424431.html
系列视频:https://www.cnblogs.com/SavionZhang/p/14258393.html
技术栈
1、AI、DeepSeek、MiniMax、通义千问
2、Visual Studio、.NET Core/.NET、MVC、Web API、RESTful API、gRPC、SignalR、Java、Python
3、jQuery、Vue.js、Bootstrap、ElementUI
4、数据库:分库分表、读写分离、SQLServer、MySQL、PostgreSQL、Redis、MongoDB、ElasticSearch、达梦DM、GaussDB、OpenGauss
5、架构:DDD、ABP、SpringBoot、jFinal
6、环境:跨平台、Windows、Linux
7、移动App:Android、IOS、HarmonyOS、微信小程序、钉钉、uni-app、MAUI
8、分布式、高并发、云原生、微服务、Docker、CI/CD、DevOps、K8S;Dapr、RabbitMQ、Kafka、RPC、Elasticsearch
欢迎关注作者头条号 张传宁IT讲堂,获取更多IT文章、视频等优质内容。
出处:www.cnblogs.com/SavionZhang
作者:张传宁 技术顾问、培训讲师、微软MCP、系统架构设计师、系统集成项目管理工程师、科技部创新工程师。
专注于企业级通用开发平台、工作流引擎、自动化项目(代码)生成器、SOA 、DDD、 云原生(Docker、微服务、DevOps、CI/CD);PDF、CAD、BIM 审图等研究与应用。
多次参与电子政务、图书教育、生产制造等企业级大型项目研发与管理工作。
熟悉中小企业软件开发过程:可行调研、需求分析、架构设计、编码测试、实施部署、项目管理。通过技术与管理帮助中小企业实现互联网转型升级全流程解决方案。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
如有问题,可以通过邮件905442693@qq.com联系。共同交流、互相学习。
如果您觉得文章对您有帮助,请点击文章右下角【推荐】。您的鼓励是作者持续创作的最大动力!
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库