ASP.NET Core 6框架揭秘實例演示[25]:配置與承載環境的應用
- 2022 年 3 月 21 日
- 筆記
- .NET 6, .NET Core, [02] 編程技巧, Asp.Net, Asp.Net Core, ASP.NET Core 6框架揭秘
與服務註冊一樣,針對配置的設置同樣可以採用三種不同的編程模式。第一種是利用WebApplicationBuilder的Host屬性返回的IHostBuilder對象,它可以幫助我們設置面向宿主和應用的配置。IWebHostBuilder介面上面同樣提供了一系列用來對配置進行設置的方法,我們可以將這些方法應用到WebApplicationBuilder的WebHost屬性返回的IWebHostBuilder對象上。不過還是那句話,既然推薦使用Mininal API,最好還是採用最新的編程方式。(本篇提供的實例已經匯總到《ASP.NET Core 6框架揭秘-實例演示版》)
[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為它們定義了對應的屬性。通過第14章「服務承載」可知,前三個配置項的名稱同樣以靜態只讀欄位的形式定義在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對象並將其攜帶資訊輸出到控制台上。我們命令行的方式啟動該程式,並指定了與承載環境相關的四個參數。
除了命令行參數,使用環境變數同樣能達到相同的目的,當時應用的名稱目前無法通過對應的配置進行設置。對於上面創建的這個演示程式,我們現在換一種方式啟動它。如圖2所示,在執行「dotnet run」命令啟動程式之前,我們為承載環境的四個屬性設置了對應的環境變數。從輸出的結果可以看出,除了應用名稱依然是入口程式集名稱外,承載環境的其他三個屬性與我們設置的環境變數是一致的。
[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}");