【.Net Core】.Net Core 源码分析与深入理解 - 入口 Program.cs (一)
研究原因:学习 .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
public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
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 中找到这个方法的源码如下
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; }
好,现在从此方法第一句 var builder = new HostBuilder(); 开始逐一分解
指定Host要使用的根目录
builder.UseContentRoot(Directory.GetCurrentDirectory());
配置初始化环境变量,如appsettings.json等。
builder.ConfigureHostConfiguration(config => ......
builder.ConfigureAppConfiguration((hostingContext, config) => ......
配置.Net Core 默认的Log
.ConfigureLogging((hostingContext, logging) => ......
配置开发环境模式下启用作用域验证
.UseDefaultServiceProvider((context, options) => { var isDevelopment = context.HostingEnvironment.IsDevelopment(); options.ValidateScopes = isDevelopment; options.ValidateOnBuild = isDevelopment; });
下半第二步:把IHostBuidler对象转化为IWebHostBuilder
这一步骤是通过Program.cs/CreateHostBuilder中的 .ConfigureWebHostDefaults()方法,点进去看看
这个方法如果你按照上面写的目录去找,是找不到的。查找了很久,才发现这个方法其实是在
.....\aspnetcore-3.1.14\src\DefaultBuilder\src 目录下,其实是调用在这里,很有欺骗性T.T,这里推荐一个搜文件的工具:searcheverything。
打开这个方法,源码如下,非常简单。
public static class GenericHostBuilderExtensions { public static IHostBuilder ConfigureWebHostDefaults(this IHostBuilder builder, Action<IWebHostBuilder> configure) { return builder.ConfigureWebHost(webHostBuilder => { WebHost.ConfigureWebDefaults(webHostBuilder); configure(webHostBuilder); }); } }
看到这里豁然开朗,知道了前面的意思了,看起来似乎很难理解,其实上面的方法可以理解为
var webHostBuilder = new GenericWebHostBuilder(builder); configure(webHostBuilder);
下半第三步:给IWebHostBuilder 配置一些信息
把 hostBuilder 转化为 webHostBudiler对像,然后调用参数传入的委托 configure(webHostBuilder) , 这个configure实际就是Program.cs里面的
webBuilder.UseStartup<Startup>();
当然,你还可以使用委托配置log,配置端口,自定义各种配置,然后都到源码里这个方法装配
上半部分Main()
好,现在下半部分CreateHostBuilder()方法已经完全执行完了,回到上半部分的 Main()方法。
build方法在 ......\extensions-3.1.14\src\Hosting\Hosting\src\HostBuilder 中
public IHost Build() { if (_hostBuilt) { throw new InvalidOperationException("Build can only be called once."); } _hostBuilt = true; BuildHostConfiguration(); CreateHostingEnvironment(); CreateHostBuilderContext(); BuildAppConfiguration(); CreateServiceProvider(); return _appServices.GetRequiredService<IHost>(); }
build() 方法只会执行一次,创建各种配置过的对象。
run 方法在 ......\extensions-3.1.14\src\Hosting\Abstractions\src\HostingAbstractionsHostExtensions 中
public static void Run(this IHost host) { host.RunAsync().GetAwaiter().GetResult(); }
Run方法运行应用程序,阻止调用线程,使方法一直运行,提供服务,直到主机关闭
hist.RunAsync() 中的 RunAsync()方法如下,host 的创建和销毁,可以通过设置CancellationToken 的值,使程序自动关闭
public static async Task RunAsync(this IHost host, CancellationToken token = default) { try { await host.StartAsync(token); await host.WaitForShutdownAsync(token); } finally { if (host is IAsyncDisposable asyncDisposable) { await asyncDisposable.DisposeAsync(); } else { host.Dispose(); } } }
总结:
看起来非常复杂,但是经过细细分析,发现其实很简单,就是创建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未来的生态变的更好吧。