研究原因:学习 .Net Core 两年有余,实际项目也使用了一年半,自己的技术已经到了瓶颈,需要有一个突破,我觉得首先研究架构师的设计思想,其次分析一下.Net Core的源码,这将会是一个很好的学习方式。

源码版本: .Net Core 3.1.14   ,有aspnetcore 和 extensions 两部分

下载地址:https://github.com/dotnet/aspnetcore  https://github.com/dotnet/extensions

参考资料:https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/startup?view=aspnetcore-3.1            微软官方文档

https://www.cnblogs.com/edison0621/p/11025310.html   .NET Core 3.0之深入源码理解Host(一)  艾心

首先从 Program.cs 代码讲起,可以看到,以前使用 .Net Core 2.0 的时候 创建的是WebHostBuilder对象,而现在变为了HostBuilder

  1. public static void Main(string[] args)
  2. {
  3. CreateHostBuilder(args).Build().Run();
  4. }
  5.  
  6. public static IHostBuilder CreateHostBuilder(string[] args) =>
  7. Host.CreateDefaultBuilder(args)
  8. .ConfigureWebHostDefaults(webBuilder =>
  9. {
  10. webBuilder.UseStartup<Startup>();
  11. });

Main 方法是项目的入口,但是实际上执行了三个函数,调用下面的CreateHostBuilder方法创建IHostBuilder对象,build一个实例,再run,运行。

上半部分Main()

主要就是 build() 和 run() 方法,这个很简单,我们先把后面解释一下。

下半部分CreateHostBuilder()

本篇介绍Program最复杂的就是这个创建IHostBuilder对象的过程,现在来逐一解析。

其实这个CreateHostBuilder 就三步骤

1.初始化一个IHostBuilder

2.把IHostBuidler转化为IWebHostBuilder ( 2.0 是直接创建,这里绕了一个弯 )

3.给IWebHostBuilder 配置一些信息 ( 例如后面可以配置 端口号,日志等)

下半第一步,初始化一个 IHostBuilder 对象

Host.CreateDefaultBuilder(args)  约等于 IHostBuilder builder = Host.CreateDefaultBuilder(args);

点进去这个方法查看源头,指向CreateDefaultBuilder(string[] args)方法。

在 ..\extensions-3.1.14\src\Hosting\Hosting\src\Host.cs 中找到这个方法的源码如下

  1. public static IHostBuilder CreateDefaultBuilder(string[] args)
  2. {
  3. var builder = new HostBuilder();
  4.  
  5. builder.UseContentRoot(Directory.GetCurrentDirectory());
  6. builder.ConfigureHostConfiguration(config =>
  7. {
  8. config.AddEnvironmentVariables(prefix: "DOTNET_");
  9. if (args != null)
  10. {
  11. config.AddCommandLine(args);
  12. }
  13. });
  14.  
  15. builder.ConfigureAppConfiguration((hostingContext, config) =>
  16. {
  17. var env = hostingContext.HostingEnvironment;
  18.  
  19. config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
  20. .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
  21.  
  22. if (env.IsDevelopment() && !string.IsNullOrEmpty(env.ApplicationName))
  23. {
  24. var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
  25. if (appAssembly != null)
  26. {
  27. config.AddUserSecrets(appAssembly, optional: true);
  28. }
  29. }
  30.  
  31. config.AddEnvironmentVariables();
  32.  
  33. if (args != null)
  34. {
  35. config.AddCommandLine(args);
  36. }
  37. })
  38. .ConfigureLogging((hostingContext, logging) =>
  39. {
  40. var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
  41.  
  42. // IMPORTANT: This needs to be added *before* configuration is loaded, this lets
  43. // the defaults be overridden by the configuration.
  44. if (isWindows)
  45. {
  46. // Default the EventLogLoggerProvider to warning or above
  47. logging.AddFilter<EventLogLoggerProvider>(level => level >= LogLevel.Warning);
  48. }
  49.  
  50. logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
  51. logging.AddConsole();
  52. logging.AddDebug();
  53. logging.AddEventSourceLogger();
  54.  
  55. if (isWindows)
  56. {
  57. // Add the EventLogLoggerProvider on windows machines
  58. logging.AddEventLog();
  59. }
  60. })
  61. .UseDefaultServiceProvider((context, options) =>
  62. {
  63. var isDevelopment = context.HostingEnvironment.IsDevelopment();
  64. options.ValidateScopes = isDevelopment;
  65. options.ValidateOnBuild = isDevelopment;
  66. });
  67.  
  68. return builder;
  69. }

好,现在从此方法第一句 var builder = new HostBuilder(); 开始逐一分解

指定Host要使用的根目录

  1. builder.UseContentRoot(Directory.GetCurrentDirectory());

配置初始化环境变量,如appsettings.json等。

  1. builder.ConfigureHostConfiguration(config => ......
  2.  
  3. builder.ConfigureAppConfiguration((hostingContext, config) => ......

配置.Net Core 默认的Log

  1. .ConfigureLogging((hostingContext, logging) => ......

配置开发环境模式下启用作用域验证

  1. .UseDefaultServiceProvider((context, options) =>
  2. {
  3. var isDevelopment = context.HostingEnvironment.IsDevelopment();
  4. options.ValidateScopes = isDevelopment;
  5. options.ValidateOnBuild = isDevelopment;
  6. });

下半第二步:把IHostBuidler对象转化为IWebHostBuilder 

这一步骤是通过Program.cs/CreateHostBuilder中的 .ConfigureWebHostDefaults()方法,点进去看看

这个方法如果你按照上面写的目录去找,是找不到的。查找了很久,才发现这个方法其实是在

.....\aspnetcore-3.1.14\src\DefaultBuilder\src 目录下,其实是调用在这里,很有欺骗性T.T,这里推荐一个搜文件的工具:searcheverything。

打开这个方法,源码如下,非常简单。

  1. public static class GenericHostBuilderExtensions
  2. {
  3. public static IHostBuilder ConfigureWebHostDefaults(this IHostBuilder builder, Action<IWebHostBuilder> configure)
  4. {
  5. return builder.ConfigureWebHost(webHostBuilder =>
  6. {
  7. WebHost.ConfigureWebDefaults(webHostBuilder);
  8.  
  9. configure(webHostBuilder);
  10. });
  11. }
  12. }

看到这里豁然开朗,知道了前面的意思了,看起来似乎很难理解,其实上面的方法可以理解为

  1. var webHostBuilder = new GenericWebHostBuilder(builder);
  2. configure(webHostBuilder);

下半第三步:给IWebHostBuilder 配置一些信息

把 hostBuilder 转化为 webHostBudiler对像,然后调用参数传入的委托 configure(webHostBuilder) , 这个configure实际就是Program.cs里面的

  1. webBuilder.UseStartup<Startup>();

当然,你还可以使用委托配置log,配置端口,自定义各种配置,然后都到源码里这个方法装配

上半部分Main()

好,现在下半部分CreateHostBuilder()方法已经完全执行完了,回到上半部分的 Main()方法。

build方法在 ......\extensions-3.1.14\src\Hosting\Hosting\src\HostBuilder 中

  1. public IHost Build()
  2. {
  3. if (_hostBuilt)
  4. {
  5. throw new InvalidOperationException("Build can only be called once.");
  6. }
  7. _hostBuilt = true;
  8.  
  9. BuildHostConfiguration();
  10. CreateHostingEnvironment();
  11. CreateHostBuilderContext();
  12. BuildAppConfiguration();
  13. CreateServiceProvider();
  14.  
  15. return _appServices.GetRequiredService<IHost>();
  16. }

build() 方法只会执行一次,创建各种配置过的对象。

run 方法在 ......\extensions-3.1.14\src\Hosting\Abstractions\src\HostingAbstractionsHostExtensions 中

  1. public static void Run(this IHost host)
  2. {
  3. host.RunAsync().GetAwaiter().GetResult();
  4. }

Run方法运行应用程序,阻止调用线程,使方法一直运行,提供服务,直到主机关闭

hist.RunAsync() 中的 RunAsync()方法如下,host 的创建和销毁,可以通过设置CancellationToken 的值,使程序自动关闭

  1. public static async Task RunAsync(this IHost host, CancellationToken token = default)
  2. {
  3. try
  4. {
  5. await host.StartAsync(token);
  6.  
  7. await host.WaitForShutdownAsync(token);
  8. }
  9. finally
  10. {
  11. if (host is IAsyncDisposable asyncDisposable)
  12. {
  13. await asyncDisposable.DisposeAsync();
  14. }
  15. else
  16. {
  17. host.Dispose();
  18. }
  19.  
  20. }
  21. }

总结:

看起来非常复杂,但是经过细细分析,发现其实很简单,就是创建builder ,通过builder生成host ,最后执行host。

其他思考:

为什么.Net Core 3.1 与 2.X 比 生成 host 的过程 改的更加复杂???

结合巨硬后来的各种操作比如.Net5 .Net6,我明白了,虽然需要的是IWebHostBuilder,但我还是要抽象一个IHostBuilder

这样可以更灵活的配置,使.Net Core 不再只是为了 web服务,未来应该巨硬是想统一化生态园,WPF、Xamarin、Unity 等技术会渐渐整合起来.

期待今年11月份的 .Net 6 LTS版本 ,C#设计的这么牛皮的语言不能让他没落,多输出内容,让.Net未来的生态变的更好吧。

【.Net Core】.Net Core 源码分析与深入理解 - 入口 Program.cs (一)的更多相关文章

  1. 学习JUC源码(3)——Condition等待队列(源码分析结合图文理解)

    前言 在Java多线程中的wait/notify通信模式结尾就已经介绍过,Java线程之间有两种种等待/通知模式,在那篇博文中是利用Object监视器的方法(wait(),notify().notif ...

  2. Spring Boot 2.x 启动全过程源码分析(上)入口类剖析

    Spring Boot 的应用教程我们已经分享过很多了,今天来通过源码来分析下它的启动过程,探究下 Spring Boot 为什么这么简便的奥秘. 本篇基于 Spring Boot 2.0.3 版本进 ...

  3. Java ArrayList源码分析(有助于理解数据结构)

    arraylist源码分析 1.数组介绍 数组是数据结构中很基本的结构,很多编程语言都内置数组,类似于数据结构中的线性表 在java中当创建数组时会在内存中划分出一块连续的内存,然后当有数据进入的时候 ...

  4. 学习JUC源码(1)——AQS同步队列(源码分析结合图文理解)

    前言 最近结合书籍<Java并发编程艺术>一直在看AQS的源码,发现AQS核心就是:利用内置的FIFO双向队列结构来实现线程排队获取int变量的同步状态,以此奠定了很多并发包中大部分实现基 ...

  5. ci之 core下CodeIgniter源码分析(1)

    ci 执行流程 index.php 文件 加载codeigniter文件 codeigniter部分里面加载的: 加载配置文件constants 加载全局公共函数core/Common.php 文件 ...

  6. quartz源码分析之深刻理解job,sheduler,calendar,trigger及listener之间的关系

    org.quartz包 包org.quartz是Quartz的主包,包含了客户端接口. 其中接口有: Calendar接口: 定义了一个关联Trigger可能(或者不可能)触发的时间空间.它没有定义触 ...

  7. Django源码分析之程序执行入口分析

    一般我们开启一个django项目,最简单的方法是进入project 目录,这时目录结构是这样的 然后我们执行python manage.py runserver,程序就开始执行了. 那django是如 ...

  8. nova创建虚拟机源码分析系列之六 api入口create方法

    openstack 版本:Newton 注:博文图片采用了很多大牛博客图片,仅作为总结学习,非商用.该图全面的说明了nova创建虚机的过程,从逻辑的角度清晰的描述了前端请求创建虚拟机之后发生的一系列反 ...

  9. Dubbo 源码分析 - 服务引用

    1. 简介 在上一篇文章中,我详细的分析了服务导出的原理.本篇文章我们趁热打铁,继续分析服务引用的原理.在 Dubbo 中,我们可以通过两种方式引用远程服务.第一种是使用服务直联的方式引用服务,第二种 ...

  10. 涨姿势:Spring Boot 2.x 启动全过程源码分析

    目录 SpringApplication 实例 run 方法运行过程 总结 上篇<Spring Boot 2.x 启动全过程源码分析(一)入口类剖析>我们分析了 Spring Boot 入 ...

随机推荐

  1. [转帖]Mars II - Microarchitectures - Phytium

    https://en.wikichip.org/wiki/phytium/microarchitectures/mars_ii Edit Values Mars II µarch General In ...

  2. [转帖]2.6 The jcmd Utility

    https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/tooldescr006.html#BABEJDGE The j ...

  3. Linux下面rsync 实现 完全一致的同步方法

    1. 在某些特殊的linux机器上面, 比如龙芯后者是飞腾服务器,部分工具不太好用, 需要使用x86弄好之后进行同步过去, 这个时候scp 最简单但是网络流量非常大, 不如使用rsync, rsync ...

  4. 大数据平台Bug Bash大扫除最佳实践

    一.背景 随着越来越多的"新人"在日常工作以及大促备战中担当大任,我们发现仅了解自身系统业务已不能满足日常系统开发运维需求.为此,大数据平台部门组织了一次Bug Bash活动,既能 ...

  5. 如何去掉 node.js 获取MySQL数据产生的RowDataPacket

    如何去掉 node.js 获取MySQL数据产生的RowDataPacket 利用JSON.stringify()把对象转为对象字符串,可去掉RowDataPacket. router.post('/ ...

  6. 01 vue子组件调用父组件中的方法

    vue子组件,调用父组件中有三种方法哈!下面我们一起来讲解. 第一种使用 直接在子组件中通过this.$parent.父组件中的方法.来调用父组件的方法 第一种的缺点是不能够传递参数哈.它只能够调用方 ...

  7. 【JS 逆向百例】网洛者反爬练习平台第一题:JS 混淆加密,反 Hook 操作

    关注微信公众号:K哥爬虫,持续分享爬虫进阶.JS/安卓逆向等技术干货! 声明 本文章中所有内容仅供学习交流,抓包内容.敏感网址.数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后 ...

  8. Windows 核心编程笔记 [2] 字符串

    1. ANSI 和 Unicode Windows 中涉及字符串的函数有两个版本 1)ANSI版本的函数会把字符串转换为Unicode形式,再从内部调用函数的Unicode版本 2)Unicode版本 ...

  9. 从零开始配置 vim(15)——状态栏配置

    vim 下侧有一个状态栏,会显示当前打开的文件等一系列内容,只是我们很少去关注它.而且原生的vim也支持对状态栏进行自定义.这篇文章主要介绍如何自定义状态栏 设置状态栏 我们可以采用 set stat ...

  10. 大语言模型的预训练4:指示学习Instruction Learning详解以及和Prompt Learning,In-content Learning区别

    大语言模型的预训练[4]:指示学习Instruction Learning:Entailment-oriented.PLM oriented.human-oriented详解以及和Prompt Lea ...