应用--Program中的WebApplication
在6.0,微软团队对于NetCore做了很大的改变,其中有一个改变就是推出了新的托管模型--最小托管模型,使用该模型可以创建最小的web应用。(最小webapi请查看官网)
需要掌握:
- 什么是最小托管模型?以及他的作用
- 什么是WebApplication和WebApplicationBuilder?他们和Host的区别是什么
- WebApplication代码上以及对应逻辑上的构造流程,能简单描述就好了
- WebApplication的六个属性以及其作用,为什么需要这6个属性?
- WebApplicationBuilder的作用
- WebApplication的6个属性以及继承4个接口有什么用
构造流程
得到WebApplicationBuilder构造器
-> 配置服务 -> build()
方法得到WebApplication对象
-> 配置中间件 -> 运行主机
C#
// 得到应用构造器:WebApplicationBuilder
var builder = WebApplication.CreateBuilder(args);
// 配置日志
builder.Logging.AddLog4Net("ConfigFile/log4net.config");
// 得到应用:WebApplication
var app = builder.Build();
// 配置中间件
app.UseStaticFiles();
// 运行主机
app.Run();
你可能在疑惑,在3.0至5.0的版本都是直接调用Host.CreateDefaultBuilder()
方法得到HostBuilder构造器
,然后调用ConfigureWebHostBuilder()
配置WebHost
,然后在上面配置一些服务,构建然后运行。而6.0使用WebApplication.CreateBuilder(args)
方法得到的是一个WebApplicationBuilder
构造器,然后构建运行,他们有什么区别吗?
答:没什么区别,流程都是一样的,WebApplication
对主机和服务做了一个更进一步的封装,使得更加方便配置和学习,而且额外暴露2个Host属性
和WebHost属性
用来配置(这2个属性也是方便之前的版本迁移到6.0的关键)。举个很简单的例子
区别 | 在3.0至5.0的版本中 | 6.0版本中 |
---|---|---|
中间管道的配置 | 必须放在Startup.cs类中的Configure方法中,或者通过ConfigureWebHostDefaults中的webBuilder 来配置服务 | 通过调用app.UseXXXX来配置 |
路由中间件 | 使用app.UseRouting()之后才能app.UseEndpoints() | 因为WebApplication继承了WebIEndpointRouteBuilder可以直接将路由,而无需显式调用 UseEndpoints 或 UseRouting。 |
按照上方Program.cs流程顺序介绍相关类和方法
1. CreateBuilder(args) 方法:
使用默认值来生成构造一个WebApplicationBuilder对象
该方法有3个重载:
WebApplication.CreateBuilder()
:使用预配置的默认值来构造;WebApplication.CreateBuilder(String [])
:根据传入的命令行参数初始化;WebApplication.CreateBuilder(WebApplicationOption)
:根据传入的预配置来构造;
C#
public class WebApplicationOptions
{
public WebApplicationOptions();
// 命令行参数
public string[]? Args { get; init; }
// 环境名称。
public string? EnvironmentName { get; init; }
// 应用程序名称。
public string? ApplicationName { get; init; }
// 内容根路径。
public string? ContentRootPath { get; init; }
// Web 根路径。
public string? WebRootPath { get; init; }
}
WebApplicationBuilder类:
要创建一个WebApplication对象
,需要一个IHost对象
,IHost对象
是通过IHostBuilder创建
的,而WebApplication
需要WebApplicationBuilder
来构建,所以WebApplicationBuilder
还需要一个IHostBuilder对象,我们针对WebApplication
的一切配置,最终都会转移到这个对象上面才能生效,所以这就是为什么WebApplicationBuilder
提供了这6个属性的原因。
构造函数
当通过调用WebApplication.CreateBuilder()
方法的时候,根据命令行的参数传给WebApplicationBuilder
的构造函数,而WebApplicationBuilder
的构造函数内部会做:
- 创建
HostBuilder _hostBuilder
类。 - 创建
BootstrapHostBuilder
对象,调用拓展方法ConfigureWebHostBuilder()
和ConfigureDefaults()
方法,将初始化的设置和服务收集起来,然后把收集到的服务和配置注入到Services成员属性
和Configure成员属性
中。 - 然后会创建承载托管环境的
IWebHostEnvironment
,对于Environment成员属性
初始化。 - 调用
Apply()
方法得到HostBuilderContext上下文
。 - 使用
HostBuilderContext
的WebHostBuilderContext
,创建ConfigureWebHostBuilde
r和ConfigureHostBuilder
并赋值给WebHost和Host属性
。初始化Logging属性
。 - 得到一个
Configure、Environment、WebHost、Host、Logging属性
都被初始化的WebApplication对象
。
WebApplicationBuilder构造函数源码
c#
public class WebApplicationBuilder
{
private readonly HostBuilder _hostBuilder = new HostBuilder();
private WebApplication _application;
// 提供应用程序正在运行的Web托管环境的信息
public IWebHostEnvironment Environment { get; }
// 提供应用程序所需要的服务,即依赖注入容器
public IServiceCollection Services { get; }
// 提供应用程序所需要的配置
public ConfigurationManager Configuration { get; }
// 提供日志记录
public ILoggingBuilder Logging { get; }
// 配置WebHost服务器特定属性,实现IWebHostBuilder
public ConfigureWebHostBuilder WebHost { get; }
// 配置Host特定属性,实现IHostBuilder
public ConfigureHostBuilder Host { get; }
public WebApplicationBuilder(WebApplicationOptions options)
{
//创建BootstrapHostBuilder并利用它收集初始化过程中设置的配置、服务和针对依赖注入容器的设置
var args = options.Args;
var bootstrap = new BootstrapHostBuilder();
bootstrap
.ConfigureDefaults(null)
// 此处用于中间件的注册
.ConfigureWebHostDefaults(webHostBuilder => webHostBuilder.Configure(app =>app.Run(_application.BuildRequestDelegate())))
.ConfigureHostConfiguration(config => {
// 添加命令行配置源
if (args?.Any() == true)
{
config.AddCommandLine(args);
}
// 将WebApplicationOptions配置选项转移到配置中
Dictionary<string, string>? settings = null;
if (options.EnvironmentName is not null) {
(settings ??= new())[HostDefaults.EnvironmentKey] = options.EnvironmentName;
}
if (options.ApplicationName is not null){
(settings ??= new())[HostDefaults.ApplicationKey] = options.ApplicationName;
}
if (options.ContentRootPath is not null){
(settings ??= new())[HostDefaults.ContentRootKey] = options.ContentRootPath;
}
if (options.WebRootPath is not null) {
(settings ??= new())[WebHostDefaults.WebRootKey] = options.EnvironmentName;
}
if (settings != null)
{
config.AddInMemoryCollection(settings);
}
});
// 将BootstrapHostBuilder收集到配置和服务转移到Configuration和Services上
// 将应用到BootstrapHostBuilder上针对依赖注入容器的设置转移到_hostBuilder上
// 得到BuilderContext上下文
bootstrap.Apply(_hostBuilder, Configuration, Services, out var builderContext);
// 如果提供了命令行参数,在Configuration上添加对应配置源
if (options.Args?.Any() == true)
{
Configuration.AddCommandLine(options.Args);
}
// 构建WebHostBuilderContext上下文
// 初始化Host、WebHost和Logging属性
var webHostContext = (WebHostBuilderContext)builderContext.Properties[typeof(WebHostBuilderContext)];
Environment = webHostContext.HostingEnvironment;
Host = new ConfigureHostBuilder(builderContext, Configuration, Services);
WebHost = new ConfigureWebHostBuilder(webHostContext, Configuration, Services);
Logging = new LogginigBuilder(Services);
}
}
简单来说WebApplicationBuilder的作用就是为了提供构建封装的一个HostBuilder对象
注意:记住这6个属性,NetCore的生态库基本上就是围绕这几个来构建的
接下来我们按照这几个属性来逐一分析其作用
IWebHostEnvironment Environment 属性
接口,继承IHostEnvironment
,提供一些该应用程序的环境信息,比如根目录、环境变量、名称等,包含几个属性
WebRootPath
,用于设置和获取Web的根目录,默认是wwwroot的子文件夹(在5.0的时候。是通过Host.CreateDefaultBuilder()方法去设置的);ApplicationName:
应用名称;ContentRootFileProvider:;
ContentRootPath:
;EnvironmentName:
环境名称;
比如我们经常用的判断是否是开发环境就是用的该类
demo
C#
var builder = WebApplication.CreateBuilder(args);
bool isDevelopment = builder.Environment.IsDevelopment();
IServiceCollection Services 属性
依赖注入容器 ,可以注入服务,也可也获取服务实例,继承于ICollection<ServiceDescriptor>泛型接口
,这个我们后续会在依赖注入章节详细描述。
通过往该属性添加系统服务支持,或者注入自己的服务
demo
C#
var builder = WebApplication.CreateBuilder(args);
// 注入Sql数据库支持
builder.Services.AddDbContext<SqlDbContext>();
// 依赖注入
builder.Services.AddSingleton<PersonService>();
ConfigurationManager Configuration 属性
配置管理, 是6.0版本新增的类,更见简单的用于获取和设置系统配置文件,替换掉了5.0版本中IConfigurationBuilder接口
和IConfigurationRoot接口
(6.0是把这2个接口整合在一起了,看源码可以发现ConfigureManager
继承于这2个接口)
ConfigurationManager密封类源码
C#
public sealed class ConfigurationManager : IConfigurationBuilder, IConfigurationRoot, IConfiguration, IDisposable
{
public ConfigurationManager();
public string this[string key] { get; set; }
public void Dispose();
public IEnumerable<IConfigurationSection> GetChildren();
public IConfigurationSection GetSection(string key);
}
public interface IConfigurationBuilder
{
IDictionary<string, object> Properties { get; }
IList<IConfigurationSource> Sources { get; }
IConfigurationBuilder Add(IConfigurationSource source);
IConfigurationRoot Build();
}
增加系统配置文件
当你调用builder.Configuration.AddJsonFile("文件名称")拓展方法
的来增加自定义配置文件的时候(实际上是往调用的IConfigurationBuilder.Add()
方法往IList<IConfigurationSource> Sources { get; }属性
增加了一条数据),会将立即加载提供程序并更新配置,这样可以不用等到Build()
方法,可以避免在部分生成方法多次加载配置源数据。
demo
C#
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddJsonFile("servicesetting.json");
获取系统配置数据
demo
C#
var builder = WebApplication.CreateBuilder(args);
ConfigurationManager config = builder.Configuration;
string value1 = config["DBContextModel:SqlConnection"];
IConfigurationSection value2 = config.GetSection("DBContextModel");
ILoggingBuilder Logging
提供日志记录,包括控制台、调试、事件日志、TraceSource等组件,你是不是在疑惑为什么创建项目的时候appsetting.json配置文件有一个这样的配置?他就和日志记录息息相关
appstting.json文件节点
json
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
具体在日志章节描述
ConfigureWebHostBuilder WebHost 属性
继承于IWebHostBuilder接口
,目的是复用接口,继承于ISupportsStartUp接口
,由于6.0使用的是最小托管模型,所以传统的使用Startup.cs文件
来配置服务和注册中间件已经不支持了,继承此接口的是原因是:
微软原话"但是我们希望用户在采用这种编程方式时得到显式的提醒,所以依然让它实现该接口,并在实现的方法中抛出NotImplementedException类型的异常。"
WebApplication
构造函数中,通过传进来的一些构造参数初始化一个实例赋值给该属性;
构造函数,通过WebHostContext,Services成员属性、Configure成员属性
来初始化
ConfigureWebHostBuilder类源码
C#
public class ConfigureWebHostBuilder : IWebHostBuilder, ISupportsStartup
{
private readonly WebHostBuilderContext _builderContext;
private readonly IServiceCollection _services;
private readonly ConfigurationManager _configuration;
public ConfigureWebHostBuilder(WebHostBuilderContext builderContext, ConfigurationManager configuration, IServiceCollection services)
{
_builderContext = builderContext;
_services = services;
_configuration = configuration;
}
}
ConfigureHostBuilder Host 属性
继承于IHostBuilder接口
,目的是复用接口,他更多的用来配置主机服务
WebApplication构造函数
中,通过bootStrapBuilder
收集到的服务,传进来的一些构造参数初始化一个实例赋值给他;
构造函数,通过HostBuilderContext ,Services成员属性、Configure成员属性
来初始化
-
Services成员属性会直接赋值ConfigureHostBuilder的_services属性
-
Configure成员属性相关Host的配置会被存放在ConfigureHostBuilder内部类的一个_configureActions字段暂时存起来
ConfigureHostBuilder类源码
C#
public class ConfigureHostBuilder : IHostBuilder
{
private readonly ConfigurationManager _configuration;
private readonly IServiceCollection _services;
private readonly HostBuilderContext _context;
private readonly List<Action<IHostBuilder>> _configureActions = new();
internal ConfigureHostBuilder(HostBuilderContext context, ConfigurationManager configuration, IServiceCollection services)
{
_configuration = configuration;
_services = services;
_context = context;
}
}
BootstrapHostBuilder类:
BootstrapHostBuilder
继承于IHostBuilder
,目的是为了构建和初始化IHostBuilder
对象
这个它的作用是收集初始化IHostBuilder
对象提供的设置并将它们分别应用到指定的IServiceCollection、ConfigurationManager和IHostBuilder对象
上,在构造函数中会调用他的Apply()
方法。
2. Build()方法:
简单来说就是将对于WebApplicationBuilder
的一切配置转移到IHostBuilder对象
上,然后得到一个WebApplication对象
注意!!!!!
WebApplication
一旦创建,环境变量、配置都不允许再次改变(虽然我们也用不着,但是知道就好)
这个方法作用:
把WebApplication
的Configure成员属性
和Services成员属性
转移到HostBuil
der上面
C#
// 获取WebApplication对象,用于配置 HTTP 管道和路由的 Web 应用程序
public WebApplication Build()
{
// 在此处连接主机配置。我们不会尝试保留配置,在此处获取本身,因为我们不支持在创建构建器后更改主机值。
_hostBuilder.ConfigureHostConfiguration(builder =>
{
builder.AddInMemoryCollection(_hostConfigurationValues);
});
// 将ConfigurationManager的配置转移到_hostBuilder
_hostBuilder.ConfigureAppConfiguration(builder =>
{
builder.AddConfiguration(Configuration);
foreach (var kv in ((IConfigurationBuilder)Configuration).Properties)
{
builder.Properties[kv.Key] = kv.Value;
}
});
var chainedConfigSource = new TrackingChainedConfigurationSource(Configuration);
_hostBuilder.ConfigureServices((context, services) =>
{
// 简单来说就是把WeApplicationBuilder中的IServiceCollection属性添加到泛型主机中
foreach (var s in _services)
{
services.Add(s);
}
// 把服务列表只能关于主机的服务添加到主机中
// 确保添加的任何托管服务在初始托管服务集之后运行。也就是托管服务在web主机启动前运行
foreach (var s in _services.HostedServices)
{
services.Add(s);
}
// 清除主机托管服务列表
_services.HostedServices.Clear();
// 将任何服务添加到用户可见的服务集合中,
_services.InnerCollection = services;
// 保留主机中的配置
var beforeChainedConfig = true;
var hostBuilderProviders = ((IConfigurationRoot)context.Configuration).Providers;
if (!hostBuilderProviders.Contains(chainedConfigSource.BuiltProvider))
{
((IConfigurationBuilder)Configuration).Sources.Clear();
beforeChainedConfig = false;
}
// 使配置管理器与最终_hostBuilder的配置匹配。
foreach (var provider in hostBuilderProviders)
{
if (ReferenceEquals(provider, chainedConfigSource.BuiltProvider))
{
beforeChainedConfig = false;
}
else
{
IConfigurationBuilder configBuilder = beforeChainedConfig ? _hostConfigurationManager : Configuration;
configBuilder.Add(new ConfigurationProviderSource(provider));
}
}
});
// 在最终主机构建器上运行其他回调
Host.RunDeferredCallbacks(_hostBuilder);
// 构建应用
_builtApplication = new WebApplication(_hostBuilder.Build());
// 将服务集合标记为只读以防止将来修改
_services.IsReadOnly = true;
// 解析_hostBuilder的配置和构建器。
_ = _builtApplication.Services.GetService<IEnumerable<IConfiguration>>();
return _builtApplication;
}
WebApplication类:
应用类
继承4个接口
IHost接口
:所以这就是上文当中说到为什么WebApplicationBuilder
需要一个IConfigureHostBuilder
属性的原因;IApplicationBuilder
:提供配置应用程序请求管道机制的类,所以我们的中间件可以直接注册到WebApplication
;IEndpointRouteBuilder
:定义应用程序中路由生成器的协定。 路由生成器指定应用程序的路由。所以我们无需显示调用UseEndpoint、UseRouting
这2个中间件,这个在6.0的更新中也提到了;IAsyncDisposable
:提供异步释放的接口;
6个重要属性
IServiceProvider
:应用程序的已配置服务。提供在程序运行期间解析的服务类型,简称依赖注入容器;IConfiguration
:应用程序的已配置,可以获取已经配置好的配置源;IWebHostEnvironment
: 托管环境信息;IHostApplicationLifetime
:允许通知使用者应用程序生存期事件;ILogger
:日志服务;ICollection<string>
:HTTP 服务器绑定到的 URL 列表。(IServerAddressesFeature:启动地址);
WebApplication类源码
c#
public sealed class WebApplication : IHost, IApplicationBuilder, IEndpointRouteBuilder, IAsyncDisposable
{
public IServiceProvider Services => _host.Services;
public IConfiguration Configuration => _host.Services.GetRequiredService<IConfiguration>();
public IWebHostEnvironment Environment => _host.Services.GetRequiredService<IWebHostEnvironment>();
public IHostApplicationLifetime Lifetime => _host.Services.GetRequiredService<IHostApplicationLifetime>();
public ILogger Logger { get; }
public ICollection<string> Urls => ServerFeatures.Get<IServerAddressesFeature>()?.Addresses ??
throw new InvalidOperationException($"{nameof(IServerAddressesFeature)} could not be found.");
}
拓展方法
就是各种系统定义好的中间件服务。
3. Run()方法 :
WebApplication.BuildRequestDelegate()方法
前面调用ConfigureWebHostDefaults()
扩展方法提供的委托会将使用BuildRequestDelegate()
方法注册的中间件管道,作为请求处理器,至此一个WebApplication对象完成。
## 4. 总结 :
首先,最小托管模型是6.0微软推出来的一个新的应用模板,为的是方便配置和学习,他只有3句代码,利用他可以生成最小webapi。
第一句是var bulider = WebAppliaction.CreateBuilder();
这句代码的作用是通过调用WebAppliaction的工厂方法CreateBuilder()得到WebApplicationBuilder对象,因为创建一个WebApplication对象需要一个Host,Host则必须由HostBuilder创建,所以WebApplicationBuilder对象的作用是提供一个封装好的HostBuilder对象用来构建IHost,
它含有6个属性以及一个构造函数,属性包括IServiceCollection依赖注入容器Services、ConfigureManage配置管理Configure、IWebHostEnvironment托管环境environment、ILoggingBuilder日志记录logging、ConfigureWebHostBuilder类型webhost、ConfigureHostBuilder类型host,需要这6个属性的目的就是用来提供HostBuilder的创建
构造函数的作用是根据传进来的命令行参数来初始化这些属性,首先他会初始化一个_hostBuilder对象,然后创建一个bootstrapBuilder对象用来调用他的拓展方法收集服务和配置,赋值给services和configure属性,接下里根据bootstrapBuilder对象的一些属性,初始化剩余属性,初始化WebHost和Host。
第二句代码是var app = bulider.bulider();
在这句代码之前,我们可以注入自己的一些服务和系统服务,通过调用AddScope()等依赖注入方法或者使用系统提供的服务方法、如AddController(),
这句代码的作用是,根据得到WebApplicationBuilder对象来创建WebApplication,这句代码最重要的就是,把services和configure属性赋值给HostBuilder,然后我们可以看到这个对象继承了4个接口,一个是IHost接口,这就解释了为什么WebApplicationBuilder需要有一个ConfigurHostBuilder对象,还有一个IApplicationBuilder接口,这个接口是构建中间件管道服务的接口,所以我们的中间件可以直接注册在WebApplication的原因,IEndpointRouteBuilder则是默认构造了路由,还有一个异步释放的接口。
第三句代码是app.Run();
在这句代码之前可以注入中间件服务,比如UseAuthorization()之类的,
这句代码的作用是,通过调用WebApplication内部的BuildRequestDelegation()方法把注册的中间件管道作为请求处理器,至此一个WebApplication对象完成
所以根据这几行代码我们不难看出,WebApplication其实就是对Host的再次封装,只是为了我们更加简单的去配置一些我们需要的服务和中间件