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: }
接下來我們會對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的采用的配置來創建的,所以我們可以利用配置的方式來決定執行環境。
對於通過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:\myapp\wwwroot\");
4: Environment.SetEnvironmentVariable("ASPNETCORE_CONTENTROOT", @"c:\myapp\contentroot");
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:\myapp\wwwroot\");
13: Debug.Assert(env.ContentRootPath == @"c:\myapp\contentroot");
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:\myapp\wwwroot\");
8: Debug.Assert(env.ContentRootPath == @"c:\myapp\contentroot");
9: })
10: .UseKestrel()
11: .Build();
對於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();