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

asp.net core 3.x 通用主机是如何承载asp.net core的-上

程序员文章站 2022-04-18 13:36:28
一、前言 上一篇《asp.net core 3.x 通用主机原理及使用》扯了下3.x中的通用主机,刚好有哥们写了篇《.NET Core 3.1和WorkerServices构建Windows服务》可以当做通用主机的案例来看。本篇主要聊下asp.net core 3.x中是如何使用通用主机来承载asp ......

一、前言

上一篇《》扯了下3.x中的通用主机,刚好有哥们写了篇《.net core 3.1和workerservices构建windows服务》可以当做通用主机的案例来看。本篇主要聊下asp.net core 3.x中是如何使用通用主机来承载asp.net core本身的。

注:我是.net framework 4.x跳到.net core 3.x的,基本看源码总结的,可能某些地方理解不到位,所以此文只作为参考别全信哈。

阅读前提(参考老a的博客):

  • 了解配置系统和选项模式
  • 了解通用主机,可以看看上一篇文章
  • 如果能大概理解builder模式和上下文模式就更好了

目录:

  • 通用主机回顾
  • 通用主机是如何承载asp.net core的
  • 核心类及使用要点
  • 总结

二、回顾通用主机

2.1、ihost表示通用主机

微软为我们提供了一个默认实现(microsoft.extensions.hosting.internal.host),它内部主要包含:ioc容器(服务提供器)、生命周期事件处理器、日志记录器、和ihostedservice集合以及启动和停止方法。

2.1.1、ioc服务提供器:

用来存储各种服务对象,这个根容器是所有ihostedservice共享的,各个ihostedservice也可以创建主机的“范围ioc容器”。ioc容器包含微软为我们塞进去的,和我们自己塞进去的各种服务组件。在主机配置阶段微软会塞入:

  • hostingenvironment 主机环境,
    • environmentname主机环境(是开发模式?还是生产模式?)
    • applicationname应用名称
    • contentrootpath主机的内容根路径
    • contentrootfileprovider内容提供器
  • hostbuildercontext 主机创建过程中的上下文对象,在配置主机的多个步骤中传递数据。主要包含:hostingenvironment和appconfiguration应用配置对象
  • appconfiguration 应用配置对象,里面也包含主机配置对象
  • ihostlifetime主机生命周期事件处理器
  • ihostapplicationlifetime 应用生命周期事件处理器
  • internal.host 作为默认主机

所以将来我们定义的类随时可以注入这些对象,这些对象是在hostbuilder中赋值的,请往下看

2.1.2、生命周期事件处理器

将来主机启动/停止时会触发回调相应的几个方法,比如:主机启动了 应用启动了  应用停止了,主机停止了。我们可以自定义一个事件处理器加入到ioc容器中来实现生命周期事件的订阅。

2.1.3、ihostedservice集合

一个ihostedservice的实现类就是一个应用。asp.net core本身就是一个应用。

2.1.4、启动流程

触发相应的生命周期事件、遍历启动所有ihostedservice

2.2、hostbuilder表示主机构造器

它负责主机的配置和生成。它定义了几个委托集合,并提供相应的方法允许我们的代码向集合中加入自己的委托,这些委托主要是用来:

  • 向ioc容器注册服务;
  • 设置主机和应用配置对象的数据源;
  • 配置容器本身(比如替换成别的依赖注入框架);

将来在调用build生成最终的主机时会:

  • 创建主机配置生成器configurationbuilder,然后回调我们的代码提供的委托对配置对象的数据源进行设置,最终通过配置对象生成器.build生成配置对象
  • 创建hostingenvironment(含义上面有说),默认用配置对象进行赋值,然后回调我们的代码提供的委托进一步设置主机环境对象
  • 初始化buildercontext(含义上面有说)
  • 初始化应用配置生成器,套路给主机配置一样
  • 初始化ioc容器,
    • 先把上面说的对象丢入容器中,
    • 注册默认主机, internal.host 
    • 注册选项模式需要的几个服务,
    • 注册日志系统需要的服务
    • 然后回调我们的委托,我们的委托可以注入各种主机需要的服务
    • 配置ioc容器本身,我们对微软提供的ioc进行配置,或干脆替换为第三方依赖注入框架
  • 嘴周从容器中解析得到主机

2.3、host.createdefaultbuilder进一步简化主机的配置和创建

  • 创建hostbuilder
  • 设置内容根为当前应用程序根目录
  • 将以"dotnet_"的环境变量和命令行参数(如果有)作为“主机配置”的数据源
  • 以以下数据作为“应用配置”的数据源
    • json文件appsettings.json和appsettings.{env.environmentname}.json
    • 如果是开发模式,则添加一个特殊的json文件作为数据源,这个文件存储如:账号 密码 数据库连接字符串等比较机密的信息(文件查找路径有点复杂,后期补充)
    • 添加环境变量作为配置源(没细看,也许是整个系统的环境变量)
    • 尝试添加命令行参数配置源
  • 添加日志记录系统需要的服务以及相关的配置
  • 使用默认的serviceprovider,当是开发模式时:在创建对象阶段开启依赖注入范围验证

以上为通用主机的大致内容,先有个印象,将来需要扩展时在研究源码

三、通用主机是如何承载asp.net core的

asp.net core 3.x开始直接使用通用主机,主要思路是对通用主机做跟web相关配置(添加跟web相关的配置源和注册服务)关键是会将genericwebhostservice注册到ioc容器中。对于通用主机来说所谓的应用就是一个实现了ihostedservice的类。genericwebhostservice就是这样一个类,它就基本代表了asp.net core。对于通用主机来说asp.net core不过是一个应用而已。将来通用主机启动时自然是启动genericwebhostservice

注:genericwebhostservice会在启动时创建applicationbuilder,然后对其进行配置(中间件管道),然后生成一个application,最后拿到配置好的iserver(如kestrlserver)然后传入application并启动它,server开始监听http请求,后续请求抵达时由application对象负责将请求传入中间件管道*(大致这么个流程,还没详细去看genericwebhostservice源码)

先来看个图:

asp.net core 3.x 通用主机是如何承载asp.net core的-上

 核心任务是:

  1. 创建genericwebhostbuilder,它是对通用主机构建器hostbuilder的一个包装,提供跟web相关的配置源的设置和服务的注册。构造函数中会做些初始化,默认配置
  2. 通过静态方法host.configurewebdefaults对genericwebhostbuilder做进一步的默认配置
  3. 调用用户代码(我们自己写的)对genericwebhostbuilder做进一步配置

所以文章后面部分我们将这个用来承载asp.net core的通用主机称为“web主机”,把通用主机配置器hostbuilder的包装类genericwebhostbuilder称为“web主机配置器”

四、genericwebhostbuilder

它包装了hostbuilder,所以可以把它理解为一个特殊的hostbuilder,特殊在它提供web相关的配置api,本质上还是向通用主机配置对象hostbuilder添加各种服务和配置源

4.1、getsetting、usesetting

genericwebhostbuilder有个iconfiguration类型的属性,可以把它理解为跟web相关的配置对象,它会作为通用主机的配置源,由于通用主机的配置源又是应用配置的数据源,因此最后:应用配置对象 = 通用主机配置对象 + (web主机配置对象)genericwebhostbuilder._config  + 应用配置对象;
_config在构造函数中被初始化,唯一数据源是以"aspnetcore_"为前缀的环境变量
另外配置对象还在executehostingstartups被使用到,后面会详细讲
getsetting、usesetting这俩方法分别从_config读取和写入配置,所以我们可以在配置主机时更方便的向配置中加入一些值,也可以替换一些值来影响一些组件的配置

asp.net core 3.x 通用主机是如何承载asp.net core的-上asp.net core 3.x 通用主机是如何承载asp.net core的-上

所以我们可以在配置web主机时通过usesetting来快速设置/替换某些配置值,也可以在任意能获得genericwebhostbuilder的地方调用getsetting方便的获取配置值

4.2、iwebhostenvironment、webhostoptions、webhostbuildercontext

在web主机配置过程中的多个步骤中经常会使用到这几个对象,我们对web主机进行配置时,提供的委托的参数也经常会携带者节参数,因此看看这几个东西是啥。

iwebhostenvironment
web主机环境相关,它还继承ihostenvironment,所以它包含以下内容:

  • environmentname 主机运行环境,是开发?生产?
  • applicationname 应用名
  • contentrootpath 内容根,默认是应用程序所在目录
  • contentrootfileprovider 内容根对应的文件提供器
  • webrootpath  web根,默认wwwroot,
  • webrootfileprovider web根对应的文件提供器

webhostoptions
web主机选项对象,虽然是这个名字,但是并没有应用选项模式,且它是internal修饰的,因此我们写代码通常不会访问到它,但是它在genericwebhostbuilder有些作用的
它内部包含根web相关的配置:

  • applicationname:应用程序名
  • preventhostingstartup、hostingstartupassemblies、hostingstartupexcludeassemblies:插件/模块相关属性,参考《asp.net core 3.x 模块化开发之hostingstartup
  • environment:应用程序当前运行,是开发模式?是生产环境?
  • startupassembly:启动类startup所在程序集
  • webroot:web静态资源目录,通常对应那个wwwroot目录
  • contentrootpath:内容根路径,通常对应应用程序所在目录,

webhostbuildercontext
= iwebhostenvironment + iconfiguration

private webhostbuildercontext getwebhostbuildercontext(hostbuildercontext context)
genericwebhostbuilder通过这个方法来创建webhostbuildercontext,web主机配置器内部多个方法都会调用此方法。此方法执行过程大致步骤如下:

  • 使用主机配置器上下文的配置对象来创建一个webhostoptions,所以webhostoptions上面那些属性都是通过配置来赋值的,由于hostbuildercontext的配置对象现在=主机配置对象 + 应用配置对象 + web主机配置对象,可想而知,我们可以通过多种途径来配置webhostoptions的属性
  • 创建webhostbuildercontext
    • configuration = context.configuration,
    • hostingenvironment = new hostingenvironment()
  • 通过hostingenvironmentextensions.initialize对webhostbuildercontext.hostingenvironment进行初始化,其实就是将webhostoptions中相应的属性赋值上去
  • 最后hostbuildercontext和webhostoptions被存储到hostbuildercontext.properties缓存起来

所以我们平时可以通过多种途径来配置内容根、web根、应用名、运行环境(开发?生成?)
也可以在配置web主机时的委托中来通过webhostbuildercontext来访问到这些属性和对应的文件提供器
由于ihostingenvironment会以单例注册到容器,因此我们将来可以直接注入hostingenvironment或者web主机环境对象

4.3、public iwebhostbuilder configure(action<webhostbuildercontext, iapplicationbuilder> configure)

调用此方法传入一个委托,这个委托主要用来配置中间件管道,将来通用主机在启动时会启动代表asp.net core的genericwebhostservice,这时我们这个委托就会被调用。所以配置管道的代码是在hostbuilder.build().run()这个run阶段执行,并不是在build这步

这个方法跟usestartup(下面会说)是冲突的,意思只能用其中一个,

要了解这个方法的原理,得先说说genericwebhostserviceoptions,它是一个选项对象,看定义:

 1 namespace microsoft.aspnetcore.hosting{
 3     internal class genericwebhostserviceoptions{
 5        public action<iapplicationbuilder> configureapplication { get; set; }
 6        public webhostoptions webhostoptions { get; set; }
 8        public aggregateexception hostingstartupexceptions { get; set; }
10     }
11 }

此对象应用了选项模式,(第5行)configureapplication其实就代表了那个用来配置中间件管道的委托

 1 public iwebhostbuilder configure(action<webhostbuildercontext, iapplicationbuilder> configure){
 2             _builder.configureservices((context, services) => {
 3                 services.configure<genericwebhostserviceoptions>(options => {
 4                     var webhostbuildercontext = getwebhostbuildercontext(context);
 5                     options.configureapplication = app => configure(webhostbuildercontext, app);
 6                 });
 7             });
 8 
 9             return this;
10         }

所以无论是startup中的configre方法,还是这里传入的委托,最终都会以一个委托的形式赋值到genericwebhostserviceoptions.configureapplication属性上,而这个委托将来在主机启动阶段被调用,最终实现允许用户配置中间件管道的目的

为什么要提供两种配置中间件管道的方式呢?因为直接在program.main里配置更简单,但是封装性不好,通过单独的startup类更清晰。

所以我们配置中间件管道时除了可以在startup.configre中配置,也可以直接在program.main里配置主机时通过genericwebhostbuilder.configure进行配置

 

未完待续....