应用--Program中的WebApplication

在6.0,微软团队对于NetCore做了很大的改变,其中有一个改变就是推出了新的托管模型--最小托管模型,使用该模型可以创建最小的web应用。(最小webapi请查看
官网

需要掌握:







构造流程

得到
WebApplicationBuilder构造器
-> 配置服务 ->
build()
方法得到
WebApplication对象
-> 配置中间件 -> 运行主机

// 得到应用构造器: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)
    :根据传入的预配置来构造;
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
的构造函数内部会做:

  1. 创建
    HostBuilder _hostBuilder
    类。
  2. 创建
    BootstrapHostBuilder
    对象,调用拓展方法
    ConfigureWebHostBuilder()

    ConfigureDefaults()
    方法,将初始化的设置和服务收集起来,然后把收集到的服务和配置注入到
    Services成员属性

    Configure成员属性
    中。
  3. 然后会创建承载托管环境的
    IWebHostEnvironment
    ,对于
    Environment成员属性
    初始化。
  4. 调用
    Apply()
    方法得到
    HostBuilderContext上下文
  5. 使用
    HostBuilderContext

    WebHostBuilderContext
    ,创建
    ConfigureWebHostBuilde
    r和
    ConfigureHostBuilder
    并赋值给
    WebHost和Host属性
    。初始化
    Logging属性
  6. 得到一个
    Configure、Environment、WebHost、Host、Logging属性
    都被初始化的
    WebApplication对象

WebApplicationBuilder构造函数源码

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

var builder = WebApplication.CreateBuilder(args); 
bool isDevelopment = builder.Environment.IsDevelopment();
IServiceCollection Services 属性

依赖注入容器
,可以注入服务,也可也获取服务实例,继承于
ICollection<ServiceDescriptor>泛型接口
,这个我们后续会在依赖注入章节详细描述。

通过往该属性添加系统服务支持,或者注入自己的服务

demo

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密封类源码

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

var builder = WebApplication.CreateBuilder(args); 
builder.Configuration.AddJsonFile("servicesetting.json");

获取系统配置数据

demo

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文件节点

 "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },

具体在日志章节描述

ConfigureWebHostBuilder WebHost 属性

继承于
IWebHostBuilder接口
,目的是复用接口,继承于
ISupportsStartUp接口
,由于6.0使用的是最小托管模型,所以传统的使用
Startup.cs文件
来配置服务和注册中间件已经不支持了,继承此接口的是原因是:

微软原话“但是我们希望用户在采用这种编程方式时得到显式的提醒,所以依然让它实现该接口,并在实现的方法中抛出NotImplementedException类型的异常。”

WebApplication
构造函数中,通过传进来的一些构造参数初始化一个实例赋值给该属性;

构造函数,通过
WebHostContext,Services成员属性、Configure成员属性
来初始化

ConfigureWebHostBuilder类源码

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类源码

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上面

WebApplication.Build()方法源码

 // 获取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类源码

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的再次封装,只是为了我们更加简单的去配置一些我们需要的服务和中间件

标签: none

添加新评论