ASP.NET Core技术研究-探秘Host主机启动过程

原文:ASP.NET Core技术研究-探秘Host主机启动过程

当我们将原有ASP.NET 应用程序升级迁移到ASP.NET Core之后,我们发现代码工程中多了两个类Program类和Startup类。

接下来我们详细探秘一下通用主机Host的启动过程。

一、Program类的Main函数入口

Program类最重要的功能就是启动主机,这里有一个主机的概念,是ASP.NET Core全新引入的。

主机负责应用程序启动和生存期管理。 同时,主机也是封装应用程序资源的对象:

  • 依赖注入 (DI)
  • Logging
  • Configuration
  • IHostedService 实现

启动主机时,它在 DI 容器中找到 IHostedService 的每个实现,然后调用 IHostedService.StartAsync。 在 web 应用中,其中一个 IHostedService 的实现是启动 HTTP 服务器实现的 web 服务。这里的HTTP服务器默认是Kestrel。

即:ASP.NET Core主机启动时,会启动一个HTTP服务器,默认是Kestrel。启动后监听并响应某个端口的HTTP请求。

我们继续看Program类的代码:

从上述代码可以看到,Main函数中首先调用CreateHostBuilder方法,返回一个IHostBuilder。然后调用IHostBuilder.Build()方法完成

二、Host.CreateDefaultBuilder(args): 构造IHostBuilder的默认实现HostBuilder

在CreateHostBuilder方法内部,首先调用了Host.CreateDefaultBuilder构造了一个HostBuilder,这个我们先看下源码,看看到底Host类内部做了什么操作:

https://github.com/dotnet/extensions/blob/release/3.1/src/Hosting/Hosting/src/Host.cs

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

public static IHostBuilder CreateDefaultBuilder(string[] args)

        {

            var builder = new HostBuilder();

            builder.UseContentRoot(Directory.GetCurrentDirectory());

            builder.ConfigureHostConfiguration(config =>

            {

                config.AddEnvironmentVariables(prefix: "DOTNET_");

                if (args != null)

                {

                    config.AddCommandLine(args);

                }

            });

            builder.ConfigureAppConfiguration((hostingContext, config) =>

            {

                var env = hostingContext.HostingEnvironment;

                config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)

                      .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);

                if (env.IsDevelopment() && !string.IsNullOrEmpty(env.ApplicationName))

                {

                    var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));

                    if (appAssembly != null)

                    {

                        config.AddUserSecrets(appAssembly, optional: true);

                    }

                }

                config.AddEnvironmentVariables();

                if (args != null)

                {

                    config.AddCommandLine(args);

                }

            })

            .ConfigureLogging((hostingContext, logging) =>

            {

                var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);

                // IMPORTANT: This needs to be added *before* configuration is loaded, this lets

                // the defaults be overridden by the configuration.

                if (isWindows)

                {

                    // Default the EventLogLoggerProvider to warning or above

                    logging.AddFilter<EventLogLoggerProvider>(level => level >= LogLevel.Warning);

                }

                logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));

                logging.AddConsole();

                logging.AddDebug();

                logging.AddEventSourceLogger();

                if (isWindows)

                {

                    // Add the EventLogLoggerProvider on windows machines

                    logging.AddEventLog();

                }

            })

            .UseDefaultServiceProvider((context, options) =>

            {

                var isDevelopment = context.HostingEnvironment.IsDevelopment();

                options.ValidateScopes = isDevelopment;

                options.ValidateOnBuild = isDevelopment;

            });

            return builder;

        }

  从上述代码中,可以看到CreateDefaultBuilder内部构造了一个HostBuilder,同时设置了:

  • 将内容根目录(contentRootPath)设置为由 GetCurrentDirectory 返回的路径。
  • 通过以下源加载主机配置
    • 环境变量(DOTNET_前缀)配置
    • 命令行参数配置
  • 通过以下对象加载应用配置
    • appsettings.json
    • appsettings.{Environment}.json
    • 密钥管理器 当应用在 Development 环境中运行时
    • 环境变量
    • 命令行参数
  • 添加日志记录提供程序
    • 控制台
    • 调试
    • EventSource
    • EventLog( Windows环境下)
  • 当环境为“开发”时,启用范围验证和依赖关系验证。

以上构造完成了HostBuilder,针对ASP.NET Core应用,代码继续调用了HostBuilder.ConfigureWebHostDefaults方法。

三、IHostBuilder.ConfigureWebHostDefaults:通过GenericWebHostBuilderHostBuilder增加ASP.NET Core的运行时设置

构造完成HostBuilder之后,针对ASP.NET Core应用,继续调用了HostBuilder.ConfigureWebHostDefaults方法。这是一个ASP.NET Core的一个扩展方法:

我们继续看ConfigureWebHostDefaults扩展方法内部做了哪些事情:

源码连接:https://github.com/dotnet/aspnetcore/blob/master/src/DefaultBuilder/src/GenericHostBuilderExtensions.cs

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

using System;

using Microsoft.AspNetCore.Hosting;

using Microsoft.AspNetCore;

namespace Microsoft.Extensions.Hosting

{

    /// <summary>

    /// Extension methods for configuring the IWebHostBuilder.

    /// </summary>

    public static class GenericHostBuilderExtensions

    {

        /// <summary>

        /// Initializes a new instance of the <see cref="IWebHostBuilder"/> class with pre-configured defaults.

        /// </summary>

        /// <remarks>

        ///   The following defaults are applied to the <see cref="IWebHostBuilder"/>:

        ///     use Kestrel as the web server and configure it using the application‘s configuration providers,

        ///     adds the HostFiltering middleware,

        ///     adds the ForwardedHeaders middleware if ASPNETCORE_FORWARDEDHEADERS_ENABLED=true,

        ///     and enable IIS integration.

        /// </remarks>

        /// <param name="builder">The <see cref="IHostBuilder" /> instance to configure</param>

        /// <param name="configure">The configure callback</param>

        /// <returns>The <see cref="IHostBuilder"/> for chaining.</returns>

        public static IHostBuilder ConfigureWebHostDefaults(this IHostBuilder builder, Action<IWebHostBuilder> configure)

        {

            return builder.ConfigureWebHost(webHostBuilder =>

            {

                WebHost.ConfigureWebDefaults(webHostBuilder);

                configure(webHostBuilder);

            });

        }

    }

}

? 2020 GitHub, Inc.

  首先,通过类GenericHostWebHostBuilderExtensions,对IHostBuilder扩展一个方法:ConfigureWebHost:builder.ConfigureWebHost

     在这个扩展方法中实现了对IWebHostBuilder的依赖注入:即将GenericWebHostBuilder实例传入方法ConfigureWebHostDefaults内部

     代码连接:https://github.com/dotnet/aspnetcore/blob/release/3.1/src/Hosting/Hosting/src/GenericHostWebHostBuilderExtensions.cs

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

using System;

using Microsoft.AspNetCore.Hosting;

using Microsoft.Extensions.DependencyInjection;

namespace Microsoft.Extensions.Hosting

{

    public static class GenericHostWebHostBuilderExtensions

    {

        public static IHostBuilder ConfigureWebHost(this IHostBuilder builder, Action<IWebHostBuilder> configure)

        {

            var webhostBuilder = new GenericWebHostBuilder(builder);

            configure(webhostBuilder);

            builder.ConfigureServices((context, services) => services.AddHostedService<GenericWebHostService>());

            return builder;

        }

    }

}

   通过GenericWebHostBuilder的构造函数GenericWebHostBuilder(buillder),将已有的HostBuilder增加了ASP.NET Core运行时设置。

可以参考代码:https://github.com/dotnet/aspnetcore/blob/release/3.1/src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs

 。。。

   先看到这,让我们回到ConfigureWebHostDefaults:

将上面两段代码合并一下进行理解:ConfigureWebHostDefaults做了两件事情:

1. 扩展IHostBuilder增加ConfigureWebHost,引入IWebHostBuilder的实现GenericWebHostBuilder,将已有的HostBuilder增加ASP.NET Core运行时的设置。

2. ConfigureWebHost代码中的configure(webhostBuilder):对注入的IWebHostBuilder,调用 WebHost.ConfigureWebDefaults(webHostBuilder),启用各类设置,如下代码解读:

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

internal static void ConfigureWebDefaults(IWebHostBuilder builder)

       {

           builder.ConfigureAppConfiguration((ctx, cb) =>

           {

               if (ctx.HostingEnvironment.IsDevelopment())

               {

                   StaticWebAssetsLoader.UseStaticWebAssets(ctx.HostingEnvironment, ctx.Configuration);

               }

           });

           builder.UseKestrel((builderContext, options) =>

           {

               options.Configure(builderContext.Configuration.GetSection("Kestrel"));

           })

           .ConfigureServices((hostingContext, services) =>

           {

               // Fallback

               services.PostConfigure<HostFilteringOptions>(options =>

               {

                   if (options.AllowedHosts == null || options.AllowedHosts.Count == 0)

                   {

                       // "AllowedHosts": "localhost;127.0.0.1;[::1]"

                       var hosts = hostingContext.Configuration["AllowedHosts"]?.Split(new[] { ‘;‘ }, StringSplitOptions.RemoveEmptyEntries);

                       // Fall back to "*" to disable.

                       options.AllowedHosts = (hosts?.Length > 0 ? hosts : new[] { "*" });

                   }

               });

               // Change notification

               services.AddSingleton<IOptionsChangeTokenSource<HostFilteringOptions>>(

                           new ConfigurationChangeTokenSource<HostFilteringOptions>(hostingContext.Configuration));

               services.AddTransient<IStartupFilter, HostFilteringStartupFilter>();

               if (string.Equals("true", hostingContext.Configuration["ForwardedHeaders_Enabled"], StringComparison.OrdinalIgnoreCase))

               {

                   services.Configure<ForwardedHeadersOptions>(options =>

                   {

                       options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;

                       // Only loopback proxies are allowed by default. Clear that restriction because forwarders are

                       // being enabled by explicit configuration.

                       options.KnownNetworks.Clear();

                       options.KnownProxies.Clear();

                   });

                   services.AddTransient<IStartupFilter, ForwardedHeadersStartupFilter>();

               }

               services.AddRouting();

           })

           .UseIIS()

           .UseIISIntegration();

       }

内部实现了:

3. 返回ConfigureWebHostDefaults代码中的configure(webHostBuilder):执行Program类中的webBuilder.UseStartup<Startup>();

  第三章节中,以上过程完成了IHostBuilder.ConfigureWebHostDefaults,通过GenericWebHostBuilder对HostBuilder增加ASP.NET Core的运行时设置。

接下来继续Build和Run的过程。

四、CreateHostBuilder(args).Build().Run();

  CreateHostBuilder返回的IHostBuilder,我们通过代码Debug,看一下具体的类型:Microsoft.Extensions.Hosting.HostBuilder,这样进一步验证了前三个章节的代码。

  1. Build的过程

先看下Build的源码:https://github.com/dotnet/extensions/blob/release/3.1/src/Hosting/Hosting/src/HostBuilder.cs

Build的过程主要完成了:

  • BuildHostConfiguration: 构造配置系统,初始化 IConfiguration _hostConfiguration;
  • CreateHostingEnvironment:构建主机HostingEnvironment环境信息,包含ApplicationName、EnvironmentName、ContentRootPath等
  • CreateHostBuilderContext:创建主机Build上下文HostBuilderContext,上下文中包含:HostingEnvironment和Configuration
  • BuildAppConfiguration:构建应用程序配置
  • CreateServiceProvider:创建依赖注入服务提供程序,  即依赖注入容器

  2. Run的过程

我们先通过Debug,看一下Host的信息:Microsoft.Extensions.Hosting.Internal.Host

这个Run方法也是一个扩展方法:HostingAbstractionsHostExtensions.Run

代码链接:https://github.com/dotnet/extensions/blob/release/3.1/src/Hosting/Abstractions/src/HostingAbstractionsHostExtensions.cs

其实内部转调的还是Host.StartAsync方法,在内部启动了DI依赖注入容器中所有注册的服务。

代码链接:https://github.com/dotnet/extensions/blob/release/3.1/src/Hosting/Hosting/src/Internal/Host.cs

整个Host主机的启动过程还是非常复杂的,我们只是简单的在代码层面研究了一遍,感觉只是有了个大致的轮廓,具体怎么执行的,是不是如上面代码的解释,还需要深入继续研究。

接下来下一篇文章准备把源码单步调试看看。加深对ASP.NET Core底层技术原理的理解,只有理解了底层技术实现,我们在应用层才能更好、正确的使用。

周国庆

2020/4/6

原文地址:https://www.cnblogs.com/lonelyxmas/p/12688641.html

时间: 2024-10-06 11:01:36

ASP.NET Core技术研究-探秘Host主机启动过程的相关文章

ASP.NET Core技术研究-探秘依赖注入框架

原文:ASP.NET Core技术研究-探秘依赖注入框架 ASP.NET Core在底层内置了一个依赖注入框架,通过依赖注入的方式注册服务.提供服务.依赖注入不仅服务于ASP.NET Core自身,同时也是应用程序的服务提供者. 毫不夸张的说,ASP.NET Core通过依赖注入实现了各种服务对象的注册和创建,同时也实现了面向抽象的编程模式和编程体验,提升了应用程序的扩展性. 今天,我们普及一下ASP.NET Core中依赖注入的一些基本知识. 一.服务的注册 我们通过创建一个ASP.NET C

.NET Core技术研究-最实用最常用的配置读取方式

原文:.NET Core技术研究-最实用最常用的配置读取方式 升级ASP.NET Core后,配置的读取是第一个要明确的技术.原先的App.Config.Web.Config.自定义Config在ASP.NET Core中如何正常使用.有必要好好总结整理一下,相信大家都会用到. 首先,看一下ASP.NET Core中的配置提供程序(Configuration Providers): 一.配置提供程序(Configuration Providers) ASP.NET Core 使用一个或多个配置提

.NET Core技术研究-中间件的由来和使用

我们将原有ASP.NET应用升级到ASP.NET Core的过程中,会遇到一个新的概念:中间件. 中间件是ASP.NET Core全新引入的概念.中间件是一种装配到应用管道中以处理请求和响应的软件. 每个组件: 选择是否将请求传递到管道中的下一个组件. 可在管道中的下一个组件前后执行工作. 单独看以上中间件的定义,一个很直观的感觉:中间件是HTTP请求管道中的一层层的AOP扩展. 在展开介绍中间件之前,我们先回顾一下ASP.NET中HttpHandler和HttpModule的处理方式.  一.

.NET Core技术研究-HttpContext访问的正确姿势

将ASP.NET升级到ASP.NET Core之后,相信大家都会遇到HttpContext.Current无法使用的问题.这也是我们迁移ASP.NET Core必须解决的问题. 本文我们详细讨论一下,使用HttpContext的正确姿势. 先列一下使用HttpContext的具体场景: 1. 在Controller层访问HttpContext 2. 在中间件中使用HttpContext 3. 在数据访问层使用HttpContext 4. 在后台线程中访问HttpContext 5. 在Razor

[转].NET Core技术研究-HttpContext访问的正确姿势

将ASP.NET升级到ASP.NET Core之后,相信大家都会遇到HttpContext.Current无法使用的问题.这也是我们迁移ASP.NET Core必须解决的问题. 本文我们详细讨论一下,使用HttpContext的正确姿势. 先列一下使用HttpContext的具体场景: 1. 在Controller层访问HttpContext 2. 在中间件中使用HttpContext 3. 在数据访问层使用HttpContext 4. 在后台线程中访问HttpContext 5. 在Razor

ASP.NET Core 1.0 基础与应用启动

.NET Core http://dotnet.github.io/[https://github.com/dotnet/coreclr] ASP.NET Core 1.0 https://get.asp.net/ Documentation:https://docs.asp.net/en/latest/index.html MVC:https://docs.asp.net/projects/mvc/en/latest/overview.html EF: http://docs.efprojec

asp.net core 3.x 身份验证-2启动阶段的配置

注册服务.配置选项.添加身份验证方案 在Startup.ConfigureServices执行services.AddAuthentication() 注册如下服务(便于理解省略了部分辅助服务): services.TryAddScoped<IAuthenticationService, AuthenticationService>(); services.TryAddScoped<IAuthenticationHandlerProvider, AuthenticationHandler

asp.net core 3.x 通用主机原理及使用

原文:asp.net core 3.x 通用主机原理及使用 一.前言 只是讲asp.net core 3.x通用主机的大致原理,这些东西是通过查看源码以及自己根据经验总结得来的,在文章中不会深入源码,因为个人觉得懂原理就晓得扩展点,后期碰到有需求的时候再仔细去研究源码也不迟.阅读前你应该先去了解下(推荐博客园老A的博客): asp.net core中的依赖注入. 配置, 讲解的方式是: 概述 逐一介绍核心类及扩展方式 通常我们如何使用 总结 二.概述 以前的控制台应用程序.winform程序启动

.NetCore技术研究-一套代码同时支持.NET Framework和.NET Core

在.NET Core的迁移过程中,我们将原有的.NET Framework代码迁移到.NET Core.如果线上只有一个小型的应用还好,迁移升级完成后,只需要维护.NET Core这个版本的代码. 但是,如果是一个大型分布式应用,几百台Server,上千个.NET 应用进程.这种场景下,在一定的时期内,我们需要同时维护.NET Framework和.NET Core两套代码,同一个产品 特性,需要分别在两套代码中实现,这种代码同步的工作量是非常大的.因此,在这种场景下,有必要使用同一套代码既支持