设置和获取与执行环境相关的信息?
HostingEnvironment是承载应用当前执行环境的描述,它是对所有实现了IHostingEnvironment接口的所有类型以及对应对象的统称。如下面的代码片段所示,一个HostingEnvironment对象承载的执行环境的描述信息体现在定义这个接口的6个属性上。ApplicationName和EnvironmentName分别代表当前应用的名称和执行环境的名称。WebRootPath和ContentRootPath是指向两个根目录的路径,前者指向的目录用于存放可供外界通过HTTP请求访问的资源,后者指向的目录存放的则是应用自身内部所需的资源。至于这个接口的ContentRootFileProvider和WebRootFileProvider属性返回的则是针对这两个目录的FileProvider对象。如下所示的HostingEnvironment类型是对IHostingEnvironment接口的默认实现。[本文已经同步到《ASP.NET Core框架揭秘》之中]
1: public interface IHostingEnvironment
2: {
3: string ApplicationName { get; set; }
4: string EnvironmentName { get; set; }
5: IFileProvider ContentRootFileProvider { get; set; }
6: string ContentRootPath { get; set; }
7: IFileProvider WebRootFileProvider { get; set; }
8: string WebRootPath { get; set; }
9: }
10:
11: public class HostingEnvironment : IHostingEnvironment
12: {
13: string ApplicationName { get; set; }
14: string EnvironmentName { get; set; }
15: IFileProvider ContentRootFileProvider { get; set; }
16: string ContentRootPath { get; set; }
17: IFileProvider WebRootFileProvider { get; set; }
18: string WebRootPath { get; set; }
19: }
一、ApplicationEnvironment
接下来我们会对HostingEnvironment对象承载的执行环境描述信息的来源进行详细介绍,不过在此之前我们有必要来了解另一个名为ApplicationEnvironment的类型,它定义在 “Microsoft.Extensions.PlatformAbstractions”这个NuGet包中。我们从其命名也可以看出这个对象描述的也是与执行环境相关的信息,而它承载的这些信息提下在如下四个属性成员上,它们分别表示应用的名称、基路径、版本和采用的.NET Framework。
1: public class ApplicationEnvironment
2: {
3: public string ApplicationName { get; }
4: public string ApplicationBasePath { get; }
5: public string ApplicationVersion { get; }
6: public FrameworkName RuntimeFramework { get; }
7: }
如果需要获取一个ApplicationEnvironment对象来描述当前执行环境,我们需要使用到如下这个名为PlatformServices的对象,它的Application属性返回的就是我们所需的ApplicationEnvironment对象。因为该类型并不存在一个公共的构函数,所以我们不能直接实例化一个PlatformServices对象,不过我们可以利用Default属性得到这个单例对象。
1: public class PlatformServices
2: {
3: private PlatformServices();
4: public ApplicationEnvironment Application { get; }
5: public static PlatformServices Default { get; }
6: }
对于一个ApplicationEnvironment对象来说,它的ApplicationName、ApplicationVersion和RuntimeFramework属性决定于定义了程序入口Main方法的程序集,具体来说ApplicationName和ApplicationVersion分别返回这个程序集名称和版本,而这个编译这个程序集采用的.NET Framework的版本对应的正是RuntimeFramework属性。至于ApplicationBasePath属性,它返回的实际上是AppContext的BaseDirectoryPath属性对应的路径,运行时使用这个基础路径来解析被加载的目标程序集的真实路径。针对这四个属性的取值可以通过下面这段程序来验证。
1: public class Program
2: {
3: public static void Main()
4: {
5: Assembly assembly = typeof(Program).GetTypeInfo().Assembly;
6: AssemblyName assemblyName = assembly.GetName();
7: ApplicationEnvironment env = PlatformServices.Default.Application;
8:
9: Debug.Assert(env.ApplicationBasePath == AppContext.BaseDirectory);
10: Debug.Assert(env.ApplicationName == assemblyName.Name);
11: Debug.Assert(env.ApplicationVersion == assemblyName.Version.ToString());
12: Debug.Assert(env.RuntimeFramework.ToString() == assembly.GetCustomAttribute<TargetFrameworkAttribute>().FrameworkName);
13: }
14: }
如果我们没有对应用的名称做显式设置,当前HostingEnvironment的ApplicationName属性体现的应用名称来源于这个ApplicationEnvironment对象的同名属性。HostingEnvironment包括ApplicationName在内的四个属性(不包括WebRootFileProvider和ContentRootFileProvider属性,因为它们决定于对应ContentRootPath和WebRootPath属性)都可以通过WebHostOptions来设置。通过前面一章的介绍我们知道WebHostOptions对象是根据WebHostBuilder的采用的配置来创建的,所以我们可以利用配置的方式来决定执行环境。
二、Configuration和WebHostOptions
对于通过HostingEnvironment的四个属性(ApplicationName、EnvironmentName、WebRootPath和ContentRootPath) 承载的四个与执行环境相关的设置,在WebHostOptions对象上都具有对应的属性,后者是前者的数据来源。由于WebHostOptions对象是WebHostBuilder根据它采用的配置来创建的,所以这些设置最初来源于使用的配置。值得一提的是,如果EnvironmentName属性未作显式设置,它使用的默认值为“Production”。
由于WebHostBuilder会采用环境变量作为配置来源,并且采用“ASPNETCORE_”作为环境变量过滤采用的前缀,所以我们完全可以按照如下的方式通过设置环境变量的方式来初始化由HostingEnvironment承载的执行环境选项。
1: Environment.SetEnvironmentVariable("ASPNETCORE_APPLICATIONNAME", "MyApp");
2: Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", "Staging");
3: Environment.SetEnvironmentVariable("ASPNETCORE_WEBROOT", @"c:myappwwwroot");
4: Environment.SetEnvironmentVariable("ASPNETCORE_CONTENTROOT", @"c:myappcontentroot");
5:
6: new WebHostBuilder()
7: .UseConfiguration(new ConfigurationBuilder().AddJsonFile("weboptions.json"))
8: .ConfigureServices(svcs => {
9: IHostingEnvironment env = svcs.BuildServiceProvider().GetRequiredService<IHostingEnvironment>();
10: Debug.Assert(env.ApplicationName == "MyApp");
11: Debug.Assert(env.EnvironmentName == "Staging");
12: Debug.Assert(env.WebRootPath == @"c:myappwwwroot");
13: Debug.Assert(env.ContentRootPath == @"c:myappcontentroot");
14: })
15: .UseKestrel()
16: .Build();
虽然WebHostBuilder默认使用环境变量作为配置源,但是我们可以显式地创建一个Configuration对象并通过调用它的扩展方法UseConfiguration进行“导入”。对于上面这段程序,如果我们将配置定义在一个具有如下结构的JSON文件(weboptions.json),我们只需要在创建WebHost之前按照如下的方式调用UseConfiguration方法将对应配置导入进来即可。
weboptions.json:
1: {
2: "applicationName": "MyApp",
3: "environment" : "Staging",
4: "webRoot" : "c:\myapp\wwwroot",
5: "contentRoot" : "c:\myapp\contentroot"
6: }
Program
1: new WebHostBuilder()
2: .UseConfiguration(new ConfigurationBuilder().AddJsonFile("weboptions.json").Build())
3: .ConfigureServices(svcs => {
4: IHostingEnvironment env = svcs.BuildServiceProvider().GetRequiredService<IHostingEnvironment>();
5: Debug.Assert(env.ApplicationName == "MyApp");
6: Debug.Assert(env.EnvironmentName == "Staging");
7: Debug.Assert(env.WebRootPath == @"c:myappwwwroot");
8: Debug.Assert(env.ContentRootPath == @"c:myappcontentroot");
9: })
10: .UseKestrel()
11: .Build();
三、特殊的ApplicationName
对于HostingEnvironment的这四个属性来说,表示应用名称的ApplicationName比较特殊。虽然它的初始值来源于配置,当我们调用Configure方法或者UseStartup方法是,这个属性会被覆盖。如下这段程序与上面不同之处在于创建WebHost之前调用Configure方法,我们采用环境变量设置的应用名(“MyApp”)将失效。
1: Environment.SetEnvironmentVariable("ASPNETCORE_APPLICATIONNAME", "MyApp");
2: new WebHostBuilder()
3: .ConfigureServices(svcs => {
4: IHostingEnvironment env = svcs.BuildServiceProvider().GetRequiredService<IHostingEnvironment>();
5: Debug.Assert(env.ApplicationName != "MyApp");
6: })
7: .UseKestrel()
8: .Configure(app => {})
9: .Build();
其实这个问题的答案我们在《应用的入口——Startup》中已经给出了。如下所示的是WebHostBuilder用于注册Startup的两个扩展方法Configure和UseStartup的定义,我们可以清楚地看到在创建并注册Startup之前,它们都会设置当前应用的名称。
1: public static class WebHostBuilderExtensions
2: {
3: public static IWebHostBuilder Configure(this IWebHostBuilder hostBuilder, Action<IApplicationBuilder> configureApp)
4: {
5: var startupAssemblyName = configureApp.GetMethodInfo().DeclaringType.GetTypeInfo().Assembly.GetName().Name;
6:
7: return hostBuilder
8: .UseSetting("applicationName", startupAssemblyName)
9: …
10: }
11:
12: public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType)
13: {
14: var startupAssemblyName = startupType.GetTypeInfo().Assembly.GetName().Name;
15: return hostBuilder
16: .UseSetting("ApplicationName", startupAssemblyName)
17: ...
18: }
19: }
如果我们调用WebHostBuilder的UseStartup方法设置了一个启动类,那么这个类型所在的程序集名称将作为当前应用的名称。如果我们通过Configure方法并提供了一个Action<IApplicationBuilder>类型的委托对象,那么这个委托对象对应方法被定义在哪个类型中,这个类型所在的程序基名称将会作为应用名称。对于后一种情况,我们可以采用如下两种方式来提供这个Action<IApplicationBuilder>对象,最终将会导致设置的应用名称完全不同。
1: public static class Startup
2: {
3: public static void Configure(IApplicationBuilder app);
4: }
5:
6: //Configure(app=>Startup.Configure(app))
7: new WebHostBuilder()
8: .ConfigureServices(svcs => {
9: IHostingEnvironment env = svcs.BuildServiceProvider().GetRequiredService<IHostingEnvironment>();
10: Debug.Assert(env.ApplicationName == Assembly.GetEntryAssembly().GetName().Name);
11: })
12: .UseKestrel()
13: .Configure(app=>Startup.Configure(app))
14: .Build();
15:
16: //Configure(Startup.Configure)
17: new WebHostBuilder()
18: .ConfigureServices(svcs => {
19: IHostingEnvironment env = svcs.BuildServiceProvider().GetRequiredService<IHostingEnvironment>();
20: Debug.Assert(env.ApplicationName == typeof(Startup).GetTypeInfo().Assembly.GetName().Name);
21: })
22: .UseKestrel()
23: .Configure(Startup.Configure)
24: .Build();