與服務注冊一樣,針對配置的設置同樣可以采用三種不同的編程模式。第一種是利用WebApplicationBuilder的Host屬性返回的IHostBuilder對象,它可以幫助我們設置面向宿主和應用的配置。IWebHostBuilder接口上面同樣提供了一系列用來對配置進行設置的方法,我們可以將這些方法應用到WebApplicationBuilder的WebHost屬性返回的IWebHostBuilder對象上。不過還是那句話,既然推薦使用Mininal API,最好還是采用最新的編程方式。[本文節選《ASP.NET Core 6框架揭秘》第15章]
[S1513]基于環境變量的配置初始化(源代碼)
[S1514]以鍵值對形式讀取和修改配置(源代碼)
[S1515]注冊配置源(利用IWebHostBuilder)(源代碼)
[S1516]注冊配置源(Minimal API)(源代碼)
[S1517]默認的承載環境(源代碼)
[S1518]通過配置定制承載環境(源代碼)
[S1519]利用WebApplicationOptions定制承載環境(源代碼)
[S1513]基于環境變量的配置初始化
應用啟動的時候會將當前的環境變量作為配置源來創建承載最初配置數據的IConfiguration對象,但它只會選擇名稱以“ASPNETCORE_”為前綴的環境變量(通過靜態類型Host的CreateDefaultBuilder方法創建的HostBuilder默認選擇的是前綴為“DOTNET_”的環境變量)。在演示針對環境變量的初始化配置之前,需要先解決配置的消費問題,即如何獲取配置數據。如下面的代碼片段所示,我們設置兩個環境變量,它們的名稱分別為"ASPNETCORE_FOO"和"ASPNETCORE_BAR"。在調用WebApplication的CreateBuilder方法創建出WebApplicationBuilder對象之后,我們將它的Configuration屬性提取出來。由調試斷言可以看出這兩個環境變量被成功轉移到配置中了。代表承載應用的WebApplication構建出來后,其Configuration屬性返回的IConfiguration對象上同樣包含著相同的配置。
using?System.Diagnostics;Environment.SetEnvironmentVariable("ASPNETCORE_FOO",?"123");
Environment.SetEnvironmentVariable("ASPNETCORE_BAR",?"456");var?builder?=?WebApplication.CreateBuilder(args);
IConfiguration?configuration?=?builder.Configuration;
Debug.Assert(configuration["foo"]?==?"123");
Debug.Assert(configuration["bar"]?==?"456");var?app?=?builder.Build();
configuration?=?app.Configuration;
Debug.Assert(configuration["foo"]?==?"123");
Debug.Assert(configuration["bar"]?==?"456");
[S1514]以鍵值對形式讀取和修改配置
我們知道IConfiguration對象是以字典的結構來存儲配置數據的,我們可以利用該對象提供的索引以鍵值對的形式來讀取和修改配置。在ASP.NET Core應用中,我們可以通過調用定義在IWebHostBuilder接口的GetSetting方法和UseSetting方法達到相同的目的。
public?interface?IWebHostBuilder
{string?GetSetting(string?key);IWebHostBuilder?UseSetting(string?key,?string?value);...
}
如下面的代碼片段所示,我們可以通過利用WebApplicationBuilder的WebHost屬性將對應的IWebHostBuilder對象提取出來,通過調用其GetSetting方法將以環境變量設置的配置提取出來。通過調用其UseSetting方法提供的鍵值對會保存到應用的配置中。配置最終的狀態被固定下來后轉移到了構建的WebApplication對象上。
using?System.Diagnostics;var?builder?=?WebApplication.CreateBuilder(args);
builder.WebHost.UseSetting("foo",?"abc");
builder.WebHost.UseSetting("bar",?"xyz");Debug.Assert(builder.WebHost.GetSetting("foo")?==?"abc");
Debug.Assert(builder.WebHost.GetSetting("bar")?==?"xyz");IConfiguration?configuration?=?builder.Configuration;
Debug.Assert(configuration["foo"]?==?"abc");
Debug.Assert(configuration["bar"]?==?"xyz");var?app?=?builder.Build();
configuration?=?app.Configuration;
Debug.Assert(configuration["foo"]?==?"abc");
Debug.Assert(configuration["bar"]?==?"xyz");
[S1515]注冊配置源(利用IWebHostBuilder)
配置系統最大的特點是可以注冊不同的配置源。針對配置源的注冊同樣可以利用三種編程方式來實現,第一種就是利用WebApplicationBuilder的Host屬性返回的IHostBuilder對象,并調用其的ConfigureHostConfiguration和ConfigureAppConfiguration方法完成針對宿主和應用的配置,其中自然包含針對配置源的注冊。IWebHostBuilder接口也提供如下這個等效的ConfigureAppConfiguration方法。如代碼片段所示,該方法提供的參數是一個Action<WebHostBuilderContext, IConfigurationBuilder>委托,這意味著我們可以就承載上下文對配置做針對性設置。如果提供的設置與當前承載上下文無關,我們還可以調用另一個參數類型為Action<IConfigurationBuilder>的ConfigureAppConfiguration方法重載。
public?interface?IWebHostBuilder
{IWebHostBuilder?ConfigureAppConfiguration(Action<WebHostBuilderContext,?IConfigurationBuilder>?configureDelegate);
}public?static?class?WebHostBuilderExtensions
{public?static?IWebHostBuilder?ConfigureAppConfiguration(this?IWebHostBuilder?hostBuilder,?Action<IConfigurationBuilder>?configureDelegate);
}
我們可以利用WebApplicationBuilder的WebHost屬性返回對應的IWebHostBuilder對象,并采用如下的方式利用這個對象注冊配置源。
using?System.Diagnostics;var?builder?=?WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureAppConfiguration(config?=>?config.AddInMemoryCollection(new?Dictionary<string,?string>{["foo"]?=?"123",["bar"]?=?"456"}));
var?app?=?builder.Build();
Debug.Assert(app.Configuration["foo"]?==?"123");
Debug.Assert(app.Configuration["bar"]?==?"456");
[S1516]注冊配置源(Minimal API)
由于WebApplicationBuilder的Configuration屬性返回的ConfigurationManager自身就是一個IConfigurationBuilder對象,所以最直接的方式就是按照如下的方式將配置源注冊到它上面,這也是我們提供的編程方式。值得一提的是,如果調用WebApplication類型的CreateBuilder或者Create方法時傳入了命令行參數,會自動添加針對命令行參數的配置源。
using?System.Diagnostics;var?builder?=?WebApplication.CreateBuilder(args);
builder.Configuration.AddInMemoryCollection(new?Dictionary<string,?string>
{["foo"]?=?"123",["bar"]?=?"456"
});
var?app?=?builder.Build();
Debug.Assert(app.Configuration["foo"]?==?"123");
Debug.Assert(app.Configuration["bar"]?==?"456");
[S1517]默認的承載環境
如下面的代碼片段所示,派生于IHostEnvironment接口的IWebHostEnvironment接口定義了WebRootPath和WebRootFileProvider屬性,前者表示用于存放Web資源文件根目錄的路徑,后者則返回該路徑對應的IFileProvider對象。如果我們希望外部可以采用HTTP請求的方式直接訪問某個靜態文件(如JavaScript、CSS和圖片文件等),只需要將它存放于WebRootPath屬性表示的目錄之下即可。當前承載環境之間反映在WebApplicationBuilder類型如下所示的Environment屬性中。代表承載應用的WebApplication類型同樣具有這樣一個屬性。
public?interface?IWebHostEnvironment?:?IHostEnvironment
{string?WebRootPath?{?get;?set;?}IFileProvider?WebRootFileProvider?{?get;?set;?}
}public?sealed?class?WebApplicationBuilder
{public?IWebHostEnvironment?Environment?{?get;?}...
}public?sealed?class?WebApplication
{public?IWebHostEnvironment?Environment?{?get;?}...
}
我們簡單介紹與承載環境相關的六個屬性(包含定義在IHostEnvironment接口中的四個屬性)是如何設置的。IHostEnvironment 接口的ApplicationName代表當前應用的名稱,它的默認值為入口程序集的名稱。EnvironmentName表示當前應用所處部署環境的名稱,其中開發(Development)、預發(Staging)和產品(Production)是三種典型的部署環境。根據不同的目的可以將同一個應用部署到不同的環境中,在不同環境中部署的應用往往具有不同的設置。在默認情況下,環境的名稱為“Production”。ASP.NET Core應用會將所有的內容文件存儲在同一個目錄下,這個目錄的絕對路徑通過IWebHostEnvironment接口的ContentRootPath屬性來表示,而ContentRootFileProvider屬性則返回針對這個目錄的PhysicalFileProvider對象。部分內容文件可以直接作為Web資源(如JavaScript、CSS和圖片等)供客戶端以HTTP請求的方式獲取,存放此種類型內容文件的絕對目錄通過IWebHostEnvironment接口的WebRootPath屬性來表示,而針對該目錄的PhysicalFileProvider自然可以通過對應的WebRootFileProvider屬性來獲取。
在默認情況下,由ContentRootPath屬性表示的內容文件的根目錄就是當前工作目錄。如果該目錄下存在一個名為“wwwroot”的子目錄,那么它將用來存放Web資源,WebRootPath屬性將返回這個目錄。如果這樣的子目錄不存在,那么WebRootPath屬性會返回Null。針對這兩個目錄的默認設置體現在如下所示的代碼片段中。
using?System.Diagnostics;
using?System.Reflection;var?builder?=?WebApplication.CreateBuilder();
var?environment?=?builder.Environment;Debug.Assert(Assembly.GetEntryAssembly()?.GetName().Name?==?environment.ApplicationName);
var?currentDirectory?=?Directory.GetCurrentDirectory();Debug.Assert(Equals(?environment.ContentRootPath,??currentDirectory));
Debug.Assert(Equals(environment.ContentRootPath,?currentDirectory));var?wwwRoot?=?Path.Combine(currentDirectory,?"wwwroot");
if?(Directory.Exists(wwwRoot))
{Debug.Assert(Equals(environment.WebRootPath,?wwwRoot));
}
else
{Debug.Assert(environment.WebRootPath?==?null);
}static?bool?Equals(string?path1,?string?path2)?=>string.Equals(path1.Trim(Path.DirectorySeparatorChar),?path2.Trim(Path.DirectorySeparatorChar),StringComparison.OrdinalIgnoreCase);
[S1518]通過配置定制承載環境
IWebHostEnvironment對象承載的與承載環境相關的屬性(ApplicationName、EnvironmentName、ContentRootPath和WebRootPath)可以通過配置的方式進行定制,對應配置項的名稱分別為“applicationName”、“environment”、“contentRoot”和“webroot”。靜態類WebHostDefaults為它們定義了對應的屬性。前三個配置項的名稱同樣以靜態只讀字段的形式定義在HostDefaults類型中。
public?static?class?WebHostDefaults
{public?static?readonly?string?EnvironmentKey?=?"environment";public?static?readonly?string?ContentRootKey?=?"contentRoot";public?static?readonly?string?ApplicationKey?=?"applicationName";public?static?readonly?string?WebRootKey?=?"webroot";;
}public?static?class?HostDefaults
{public?static?readonly?string?EnvironmentKey?=?"environment";public?static?readonly?string?ContentRootKey?=?"contentRoot";public?static?readonly?string?ApplicationKey?=?"applicationName";
}
由于應用初始化過程中的很多操作都與當前的承載環境有關,所以承載環境必須在啟動應用最初的環境就被確定下來,并在整個應用生命周期內都不能改變。如果我們希望采用配置的方式來控制當前應用的承載環境,相應的設置必須在WebApplicationBuilder對象創建之前執行,在之后試圖修改相關的配置都會拋出異常。按照這個原則,我們可以采用命令行參數的方式對承載環境進行設置。
var?app?=?WebApplication.Create(args);
var?environment?=?app.Environment;Console.WriteLine($"ApplicationName:{environment.ApplicationName}");
Console.WriteLine($"ContentRootPath:{environment.ContentRootPath}");
Console.WriteLine($"WebRootPath:{environment.WebRootPath}");
Console.WriteLine($"EnvironmentName:{environment.EnvironmentName}");
上面的演示程序利用命令行參數的方式控制承載環境的四個屬性。如代碼片段所示,我們將命令行參數傳入WebApplication類型的Create方法創建了一個WebApplication對象,然后從中提取出代表承載環境的IWebHostEnvironment對象并將其攜帶信息輸出到控制臺上。我們命令行的方式啟動該程序,并指定了與承載環境相關的四個參數。
圖1 利用命令行參數定義承載環境
除了命令行參數,使用環境變量同樣能達到相同的目的,應用的名稱目前無法通過對應的配置進行設置。對于上面創建的這個演示程序,我們現在換一種方式啟動它。如圖2所示,在執行“dotnet run”命令啟動程序之前,我們為承載環境的四個屬性設置了對應的環境變量。從輸出的結果可以看出,除了應用名稱依然是入口程序集名稱外,承載環境的其他三個屬性與我們設置的環境變量是一致的。
圖2 利用環境變量定義承載環境
[S1519]利用WebApplicationOptions定制承載環境
承載環境除了可以采用利用上面演示的兩種方式進行設置外,我們也可以使用如下這個WebApplicationOptions配置選項。如代碼片段所示,WebApplicationOptions定義了四個屬性,分別代表命令行參數數組、環境名稱、應用名稱和內容根目錄路徑。WebApplicationBuilder具有如下這個參數類型為WebApplicationOptions的CreateBuilder方法。
public?class?WebApplicationOptions
{public?string[]?Args?{?get;?set;?}public?string?EnvironmentName?{?get;?set;?}public?string?ApplicationName?{?get;?set;?}public?string?ContentRootPath?{?get;?set;?}
}public?sealed?class?WebApplication
{public?static?WebApplicationBuilder?CreateBuilder(WebApplicationOptions?options);...
}
如果利用WebApplicationOptions來對應用所在的承載環境進行設置,上面演示的程序可以改寫成如下的形式。由于WebApplicationOptions并不包含WebRootPath對應的配置選項,如果程序運行后會發現承載環境的這個屬性為空。由于IWebHostEnvironment服務提供的應用名稱會被視為一個程序集名稱,針對它的設置會影響類型的加載,所以我們基本上不會設置應用的名稱。
var?options?=?new?WebApplicationOptions
{Args?=?args,ApplicationName?=?"MyApp",ContentRootPath?=?Path.Combine(Directory.GetCurrentDirectory(),?"contents"),EnvironmentName?=?"staging"
};
var?app?=?WebApplication.CreateBuilder(options).Build();
var?environment?=?app.Environment;
Console.WriteLine($"ApplicationName:{environment.ApplicationName}");
Console.WriteLine($"ContentRootPath:{environment.ContentRootPath}");
Console.WriteLine($"WebRootPath:{environment.WebRootPath}");
Console.WriteLine($"EnvironmentName:{environment.EnvironmentName}");