zoukankan      html  css  js  c++  java
  • 跟我一起学.NetCore之Asp.NetCore启动流程浅析

    前言

    一个Asp.NetCore项目,知道大概的启动流程是有必要的,比如后续遇见配置信息覆盖等相关问题时也大概知道是什么原因,了解原因之后,再去搜索引擎找答案,否则目标不明确,茫茫人海怎么会一下找到自己想要的,除非是“偶遇”;“偶遇”太难,一起浅析一个Asp.NetCore 项目的启动流程;

    正文

    先创建一个WebAPI项目,用的是.NetCore3.1,后续的项目例子都统一用.NetCore3.1,除非特殊说明;项目如下:

    如上图所示,一个WebAPI项目启动方式本质也是一个控制台程序,程序入口都是从Main函数开始,就从里面方法看看大概都做了什么,其中择取几个方法源码简要说明主要功能,通过增加代码注释的方式(我觉得这样比较方便对应浏览),完整源代码从以下两个地址获取,通过Everything查找工具比较方便查询代码:

    主项目地址:https://github.com/dotnet/aspnetcore/tree/v3.1.0 

    扩展项目地址:https://github.com/dotnet/extensions/releases/tag/v3.1.6

    1. Host.CreateDefaultBuilder方法

    public static IHostBuilder CreateDefaultBuilder(string[] args)
            {
             // 实例化一个HostBuilder
                var builder = new HostBuilder();
             // 设置根目录
                builder.UseContentRoot(Directory.GetCurrentDirectory());
             // 设置 Host相关配置的配置源
                builder.ConfigureHostConfiguration(config =>
                {
              // 从环境变量中获取,前缀名为DOTNET_
                    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);
              // 如果是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 段相关的配置信息添加到日志配置中
                    logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
                    // 在控制台输出,所以启动程序的时候就看见输出日志
               logging.AddConsole();
              // 在Debug窗口输出
                    logging.AddDebug();
                    logging.AddEventSourceLogger();
    
              // 如果是Windows系统,可以在系统日志中输出日志
                    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;
            }

    2. ConfigureWebHostDefaults 方法

    public static IHostBuilder ConfigureWebHostDefaults(this IHostBuilder builder, Action<IWebHostBuilder> configure)
            {
                return builder.ConfigureWebHost(webHostBuilder =>
                {
              //指定是用的服务器及集成一些默认管道
                    WebHost.ConfigureWebDefaults(webHostBuilder);
              // 调用传入的委托,这里是外部指定Startup类做服务注册和管道配置
                    configure(webHostBuilder);
                });
            }

    2.1  WebHost.ConfigureWebDefaults方法

    internal static void ConfigureWebDefaults(IWebHostBuilder builder)
            {
                builder.ConfigureAppConfiguration((ctx, cb) =>
                {
                    if (ctx.HostingEnvironment.IsDevelopment())
                    {
                //静态文件环境的配置启用
                        StaticWebAssetsLoader.UseStaticWebAssets(ctx.HostingEnvironment, ctx.Configuration);
                    }
                });
                // 指定Kestrel作为默认的Web服务器
                builder.UseKestrel((builderContext, options) =>
                {
                    options.Configure(builderContext.Configuration.GetSection("Kestrel"));
                })// 服务中间的注册,包含路的中间件注册
                .ConfigureServices((hostingContext, services) =>
                {
              // 针对配置节点AllowedHosts改变时的回调
                    // 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();
                })//对使用IIS相关中间件
                .UseIIS()
                .UseIISIntegration();
            }

    3. Build方法,其实这个方法就是根据之前配置构造出一个IHost对象

    public IHost Build()
            {
                if (_hostBuilt)
                {
                    throw new InvalidOperationException("Build can only be called once.");
                }
                _hostBuilt = true;
             // 执行ConfigureHostConfiguration添加的一系列配置回调方法
                BuildHostConfiguration();
             // 运行环境相关创建,如ApplicationName、EnvironmentName、ContentRootPath等
                CreateHostingEnvironment();
             // 构建HostBuilder
                CreateHostBuilderContext();
                // 执行ConfigureAppConfigureation添加的一系列配置回调方法
                BuildAppConfiguration();
             // 注入默认服务如:IHost、ILogging等,执行ConfigureServices添加的一系列回调方法
                CreateServiceProvider();
                return _appServices.GetRequiredService<IHost>();
            }
    
    

    4. Run()方法,开启服务器,之后就可以进行请求了

    综上几个关键方法,从其中Host这个关键词出现很多次,其实在Asp.Net Core应用中是通过配置并启动一个Host来完成应用程序的启动和生命周期的管理。而Host主要就是对Web Server的配置和请求处理管理的管理,简要流程如下图:

    在整个启动流程中,返回的IHostBuilder中暴露配置和注入的相关接口,可用于自己定义扩展,接下来通过打印的方式来看看一下几个暴露方法的执行顺序,其实以上Build方法的时候已经明确了对应方法的顺序;

    改造代码如下:

    Startup方法中的三个方法也增加对应的打印,运行如下:

    如上图,除了Startup中的ConfigureServices会跟随ConfigureWebHostDefaults改变以外,其他方法顺序都是固定。那这些方法主要作用都是什么呢?如下图:

    图中Program.ConfigureServices和Startup.ConfigureServices的执行顺序会根据ConfigureWebHostDefaults的位置改变会交替变动;

    总结

    以上内容只是提取了其中比较关键的流程进行说明,并没有详细解析源代码,这里只是先浅析,后续再继续一起深究源代码;下一节说说依赖注入;

  • 相关阅读:
    flask框架中SQLAlchemy相关
    flask使用外部存储模块之数据库的使用
    flask的基础知识
    docker的下载和使用
    rbac权限管理系统的学习
    redis数据库之五种数据类型的简单操作
    使用django框架进行web项目开发需要了解的知识
    django项目常用外部模块下载和使用
    pwn学习之dl_resolve学习篇
    验证docker的Redis镜像也存在未授权访问漏洞
  • 原文地址:https://www.cnblogs.com/zoe-zyq/p/13362196.html
Copyright © 2011-2022 走看看