zoukankan      html  css  js  c++  java
  • 重新整理 .net core 实践篇————配置应用[一]

    前言

    本来想整理到<<重新整理.net core 计1400篇>>里面去,但是后来一想,整理 .net core 实践篇 是偏于实践,故而分开。

    因为是重新整理,那么就从配置开始整理。以下只是个人理解,如有错误,望请指点谢谢。

    正文

    在我们创建好一个应用的时候,那么出现在我们视野的是一个这样的东西:

    public class Program
    {
    	public static void Main(string[] args)
    	{
    		CreateHostBuilder(args).Build().Run();
    	}
    
    	public static IHostBuilder CreateHostBuilder(string[] args) =>
    		Host.CreateDefaultBuilder(args)
    			.ConfigureWebHostDefaults(webBuilder =>
    			{
    				webBuilder.UseStartup<Startup>();
    			});
    }
    

    看到这种:

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

    显然是建设者模式。

    那么前面的基本就是在做一个构建。

    既然是一个建设者模式,那么就来看一下这个的构建器是什么?

    是一个叫做IHOSTBUILDER 的东西哈,而显然从命名上看这是一个接口哈。那么看下这个接口是啥:

    跟配置相关得到也就是那几个Configure开头的那4个东西。

    后面两个UseServiceProviderFactory后面系列再说。

    //
    // 摘要:
    //     Sets up the configuration for the remainder of the build process and application.
    //     This can be called multiple times and the results will be additive. The results
    //     will be available at Microsoft.Extensions.Hosting.HostBuilderContext.Configuration
    //     for subsequent operations, as well as in Microsoft.Extensions.Hosting.IHost.Services.
    //
    // 参数:
    //   configureDelegate:
    //     The delegate for configuring the Microsoft.Extensions.Configuration.IConfigurationBuilder
    //     that will be used to construct the Microsoft.Extensions.Configuration.IConfiguration
    //     for the application.
    //
    // 返回结果:
    //     The same instance of the Microsoft.Extensions.Hosting.IHostBuilder for chaining.
    IHostBuilder ConfigureAppConfiguration(Action<HostBuilderContext, IConfigurationBuilder> configureDelegate);
    

    这个函数的大致意思是说构件一个IConfigurationBuilder,这个the remainder of the build process and application.的配置。

    // 摘要:
    //     Enables configuring the instantiated dependency container. This can be called
    //     multiple times and the results will be additive.
    //
    // 参数:
    //   configureDelegate:
    //     The delegate which configures the builder.
    //
    // 类型参数:
    //   TContainerBuilder:
    //     The type of builder.
    //
    // 返回结果:
    //     The same instance of the Microsoft.Extensions.Hosting.IHostBuilder for chaining.
    IHostBuilder ConfigureContainer<TContainerBuilder>(Action<HostBuilderContext, TContainerBuilder> configureDelegate);
    

    依赖容器相关的哈。

    ConfigureHostConfiguration 和 ConfigureServices 这两个就不贴了,分别是一些坏境配置和服务配置。

    那么启动一下,看下他们的执行顺序是否和我们代码的书写顺序是否相同,也就是说不管我如何调换顺序他们的执行都是按照某种规则。

    那么这里加上log,通过log的方式来查看执行顺序,如下:

    public class Program
    {
    	public static void Main(string[] args)
    	{
    		CreateHostBuilder(args).Build().Run();
    	}
    
    	public static IHostBuilder CreateHostBuilder(string[] args) =>
    		Host.CreateDefaultBuilder(args)
    			.ConfigureAppConfiguration((builder) =>
    			{
    				Console.WriteLine("ConfigureAppConfiguration");
    			}).ConfigureServices(builder =>
    			{
    				Console.WriteLine("ConfigureServices");
    			}).ConfigureHostConfiguration(builder =>
    			{
    				Console.WriteLine("ConfigureHostConfiguration");
    			})
    			.ConfigureWebHostDefaults(webBuilder =>
    			{
    				Console.WriteLine("ConfigureWebHostDefaults");
    				webBuilder.UseStartup<Startup>();
    			});
    }
    

    startup.cs

    public class Startup
    {
    	public IConfiguration Configuration { get; }
    
    	public Startup(IConfiguration configuration)
    	{
    		Console.WriteLine("Startup");
    		Configuration = configuration;
    	}
    
    	// This method gets called by the runtime. Use this method to add services to the container.
    	// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
    	public void ConfigureServices(IServiceCollection services)
    	{
    		Console.WriteLine("Startup.ConfigureServices");
    		services.AddControllers();
    	}
    
    	// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    	public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    	{
    		Console.WriteLine("Startup.Configure");
    		if (env.IsDevelopment())
    		{
    			app.UseDeveloperExceptionPage();
    		}
    
    		app.UseRouting();
    
    		app.UseEndpoints(endpoints =>
    		{
    			endpoints.MapGet("/", async context =>
    			{
    				await context.Response.WriteAsync("Hello World!");
    			});
    		});
    	}
    }
    

    这里可以发现执行顺序和我们的代码的书写顺序并不是一致的。

    那么我们再次做一个小小的调换,那么会怎么样呢?

    public static IHostBuilder CreateHostBuilder(string[] args) =>
    	Host.CreateDefaultBuilder(args)
    		.ConfigureWebHostDefaults(webBuilder =>
    		{
    			Console.WriteLine("ConfigureWebHostDefaults");
    			webBuilder.UseStartup<Startup>();
    		})
    		.ConfigureServices(builder =>
    		{
    			Console.WriteLine("ConfigureServices");
    		})
    		.ConfigureAppConfiguration((builder) =>
    		{
    			Console.WriteLine("ConfigureAppConfiguration");
    		})
    		.ConfigureHostConfiguration(builder =>
    		{
    			Console.WriteLine("ConfigureHostConfiguration");
    		});
    }
    

    发现变化的只有Startup.ConfigureServices 和 ConfigureServices。

    这个时候我们大体猜出来了这个启动顺序是按照某种执行顺序执行,且Startup.ConfigureServices 和 ConfigureServices 是同一种类型,并且他们的执行顺序和他们的注册顺序保持一致。

    执行顺序如下:

    先看一下CreateDefaultBuilder:

    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;
    }
    

    那么先看下ConfigureWebDefaults 到底干了什么。

    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();
    }
    

    ConfigureWebDefaults 配置了一些必要的组件。

    CreateDefaultBuilder 和 ConfigureWebDefaults 两者可以看到其实就是做一些默认配置让我们的服务能够启动起来。

    而后续的配置也就是注册的顺序执行,只是我们的执行顺序晚点罢了。

    那么再整理一下,也就是说从原理的执行顺序来说是这样的。

    ConfigureHostConfiguration
    
    ConfigureAppConfiguration
    
    ConfigureServices
    
    ConfigureLogging
    
    Configure
    

    以上都是推论。那么直接看源码,因为是构建者模式,所以直接看build。

    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>();
    }
    

    上面的顺序和猜想是一致的。

    这里看下BuildHostConfiguration:

    private List<Action<IConfigurationBuilder>> _configureHostConfigActions = new List<Action<IConfigurationBuilder>>();
    private void BuildHostConfiguration()
    {
    	var configBuilder = new ConfigurationBuilder()
    		.AddInMemoryCollection(); // Make sure there's some default storage since there are no default providers
    
    	foreach (var buildAction in _configureHostConfigActions)
    	{
    		buildAction(configBuilder);
    	}
    	_hostConfiguration = configBuilder.Build();
    }
    

    那么其实我们按照这种配置,那么其实我们的ConfigureHostConfiguration全部方法都是在_configureHostConfigActions 中,到了builder 的时候就按照我们的注册顺序执行。

    那么这里就能解释ConfigureWebHostDefaults 不管顺序都是在最前面,而里面startup的顺序确不一样。

    代码解释一波:

    public static IHostBuilder ConfigureWebHostDefaults(this IHostBuilder builder, Action<IWebHostBuilder> configure)
    {
    	return builder.ConfigureWebHost(webHostBuilder =>
    	{
    		WebHost.ConfigureWebDefaults(webHostBuilder);
    
    		configure(webHostBuilder);
    	});
    }
    

    ConfigureWebHostDefaults 立即执行,那么log自然就先打印出来了,而其他几个都是延迟执行的。他们的执行顺序和注册顺序有关。

    最后再解释一下,webBuilder.UseStartup(); 到底做了什么?

    public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType)
    {
    	var startupAssemblyName = startupType.GetTypeInfo().Assembly.GetName().Name;
    
    	hostBuilder.UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName);
    
    	// Light up the GenericWebHostBuilder implementation
    	if (hostBuilder is ISupportsStartup supportsStartup)
    	{
    		return supportsStartup.UseStartup(startupType);
    	}
    
    	return hostBuilder
    		.ConfigureServices(services =>
    		{
    			if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo()))
    			{
    				services.AddSingleton(typeof(IStartup), startupType);
    			}
    			else
    			{
    				services.AddSingleton(typeof(IStartup), sp =>
    				{
    					var hostingEnvironment = sp.GetRequiredService<IHostEnvironment>();
    					return new ConventionBasedStartup(StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName));
    				});
    			}
    		});
    }
    

    解释一下IsAssignableFrom:

    bool res = {TypeA}.IsAssignableFrom({TypeB}) ;
    
    如果TypeA和TypeB类型一样则返回true;
    
    如果TypeA是TypeB的父类则返回true;
    
    如果TypeB实现了接口TypeA则返回true;
    

    可以看到这里做的是一个IStartup 到 Startup的一个依赖注入,且是单例模式,所以肯定的是Startup 里面的方法会在ConfigureHostConfiguration、ConfigureAppConfiguration之后执行,因为其在ConfigureServices 才开始注入依赖的。

    因为是应用篇,单纯是一些应用需要知道的,所以比较粗糙,具体的详细在原理篇介绍了。上述只是个人的理解,如果错误,望请指点,谢谢。

  • 相关阅读:
    D. Time to Run【构造】
    P3388 割顶 【求割点个数】
    处女座的测验 素数,构造
    处女座与复读机 DP
    求一个分数小数点后指定位数的数字
    安卓开发创建活动,布局,添加按钮,she使用Toast,设菜单,使菜单相关联等操作
    三进制 处女座的砝码 高精度
    上海高校程序设计联赛 D-CSL的字符串 栈模拟
    区间DP经典 石子合并
    区间DP 洛谷P2858牛奶零食
  • 原文地址:https://www.cnblogs.com/aoximin/p/14772551.html
Copyright © 2011-2022 走看看