Fork me on Gitee

【.Net Core】.Net Core 源码分析与深入理解 - 入口 Program.cs (一)

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

源码版本: .Net Core 3.1.14   ,有aspnetcoreextensions 两部分

下载地址: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未来的生态变的更好吧。

 

posted @ 2021-04-15 17:40  Roushan_IT  阅读(1892)  评论(4编辑  收藏  举报