翻譯 - ASP.NET Core 基本知識 - 配置(Configuration)

sims發表於2021-03-26

翻譯自 https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-5.0

ASP.NET Core 中的配置使用一個或者多個配置提供程(configuration providers)序實現。配置提供程式從多種鍵值對中的配置源中讀取配置資料:

  • 設定檔案,例如 appsetting.json
  • 環境變數
  • Azure 鍵庫
  • Azure App 配置
  • 命令列引數
  • 自定義提供器,安裝的或者建立的
  • 目錄檔案
  • 記憶體中的 .NET 物件

本話題提供 ASP.NET Core 中關於配置的資訊。更多關於在控制檯應用程式中使用配置的資訊,檢視 .NET Configuration.

預設配置

使用 dotnet new 或者 Visual Studio 建立的 ASP.NET Core web 應用程式生成以下程式碼:

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

CreateDefaultBuilder 按照以下順序為應用程式提供預設配置:

  1. ChainedConfigurationProvider:新增一個現存的 IConfiguration 作為一個源。在一個預設配置例子中,新增主機(host)配置並且設定它作為第一個應用程式配置的源。
  2. appsettings.json 使用 JSON 配置提供器(JSON configuration provider)。
  3. appsetttings.Environment.json 使用 JSON 配置提供器(JSON configuration provider)。例如,appsettings.Production.json 和 appsettings.Developments.json。
  4. App secrets,當應用程式執行在開發 (Development) 環境中。
  5. 環境變數使用  Environment Variables configuration provider
  6. 命令列引數使用 Command-line configuration provider

後新增的配置提供器會覆蓋先前新增的鍵值設定。例如,如果 MyKey 同事在 appsettings.json 和 環境變數中設定,環境變數的值將會被使用。如果使用預設的配置提供器,命令列配置提供器(Command-line configuration provider) 將會覆蓋所有的提供器。

關於 CreateDefaultBuilder 的更多資訊,檢視 Default builder settings

下面的程式碼展示了使能的配置提供器的新增順序:

public class Index2Model : PageModel
{
    private IConfigurationRoot ConfigRoot;

    public Index2Model(IConfiguration configRoot)
    {
        ConfigRoot = (IConfigurationRoot)configRoot;
    }

    public ContentResult OnGet()
    {           
        string str = "";
        foreach (var provider in ConfigRoot.Providers.ToList())
        {
            str += provider.ToString() + "\n";
        }

        return Content(str);
    }
}

appsettings.json

考慮下面的 appsettings.json 檔案:

{
  "Position": {
    "Title": "Editor",
    "Name": "Joe Smith"
  },
  "MyKey":  "My appsettings.json Value",
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

示例 (sample download) 中的程式碼展示了幾個上面的配置設定:

public class TestModel : PageModel
{
    // requires using Microsoft.Extensions.Configuration;
    private readonly IConfiguration Configuration;

    public TestModel(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var myKeyValue = Configuration["MyKey"];
        var title = Configuration["Position:Title"];
        var name = Configuration["Position:Name"];
        var defaultLogLevel = Configuration["Logging:LogLevel:Default"];


        return Content($"MyKey value: {myKeyValue} \n" +
                       $"Title: {title} \n" +
                       $"Name: {name} \n" +
                       $"Default Log Level: {defaultLogLevel}");
    }
}

預設的 JsonConfigurationProvider 按照以下順序載入配置:

1. appsettings.json

2. appsettings.Environment.json:例如,appsettings.Production.json 和 appsettings.Development.json 檔案。檔案的環境版本基於 IHostingEnvironment.EnvironmentName,更多資訊,檢視 Use multiple environments in ASP.NET Core.

appsettings.Environment.json 中的值會覆蓋 appsettings.json 中的值。例如,預設的:

  • 在開發環境中,appsettings.Development.json 配置會覆蓋 appsettings.json 中存在的值
  • 在生產環境中,appsettings.Production.json 的配置會覆蓋 appsettings.json 中存在的值,例如,當部署應用程式到 Azure 上的時候。

使用選項模型繫結繼承配置資料

讀取相關配置值的推薦方式是使用選項模型 (options pattern)。例如,讀取下面的配置值:

"Position": {
    "Title": "Editor",
    "Name": "Joe Smith"
  }

建立 PositionOptions 類:

public class PositionOptions
{
    public const string Position = "Position";

    public string Title { get; set; }
    public string Name { get; set; }
}

一個選項類:

  • 必須是帶有公共無引數的構造方法的非抽象類
  • 所有公共的可讀寫的屬性型別要被繫結
  • 欄位沒有繫結。在前面的程式碼中,Position 沒有繫結。使用 Position 屬性,因此字串 "Position" 就不用在繫結類到一個配置提供器的時候硬編碼在應用程式中

下面的程式碼:

public class Test22Model : PageModel
{
    private readonly IConfiguration Configuration;

    public Test22Model(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var positionOptions = new PositionOptions();
        Configuration.GetSection(PositionOptions.Position).Bind(positionOptions);

        return Content($"Title: {positionOptions.Title} \n" +
                       $"Name: {positionOptions.Name}");
    }
}

在上面的程式碼中,預設的,在應用程式啟動後對於 JSON 配置檔案的改變也會被讀取到。

ConfigurationBinder.Get<T> 繫結和返回指定的型別。ConfigurationBinder.Get<T> 可能比使用 ConfigurationBinder.Bind 更方便。下面的程式碼展示瞭如何使用 ConfigurationBinder.Get<T> 使用 PositionOptions 類:

public class Test21Model : PageModel
{
    private readonly IConfiguration Configuration;
    public PositionOptions positionOptions { get; private set; }

    public Test21Model(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {            
        positionOptions = Configuration.GetSection(PositionOptions.Position)
                                                     .Get<PositionOptions>();

        return Content($"Title: {positionOptions.Title} \n" +
                       $"Name: {positionOptions.Name}");
    }
}

預設的,上面的程式碼會讀取到在應用程式啟動後對於 JSON 配置檔案的更改。

使用 options patter 的一種方法是繫結 Position 區域並且新增到依賴注入服務容器 (dependency injection service container) 中。下面的程式碼中,PositionOptions 在 Configure 中被新增到服務容器中,並繫結到配置:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<PositionOptions>(Configuration.GetSection(
                                        PositionOptions.Position));
    services.AddRazorPages();
}

使用了上面的程式碼,下面的程式碼讀取 position options:

public class Test2Model : PageModel
{
    private readonly PositionOptions _options;

    public Test2Model(IOptions<PositionOptions> options)
    {
        _options = options.Value;
    }

    public ContentResult OnGet()
    {
        return Content($"Title: {_options.Title} \n" +
                       $"Name: {_options.Name}");
    }
}

上面的程式碼在應用程式啟動後不會讀取到對 JSON 配置檔案的更改。如果要讀取應用程式啟動後的更改,可以使用 IOptionsSnapshot

使用預設(default)的配置,appsettings.json 和 appsettings.Environment.json 檔案使能了  reloadOnChange: true 。在應用程式啟動後對 appsettings.json 和 appsettings.Environment.json 的更改會被 JSON configuration provider 讀取到。

檢視本文件中 JSON configuration provider 關於新增更多 JSON 配置檔案的資訊。

合併服務集合

考慮下面的 ConfigureServices 方法,其中註冊服務和配置選項:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<PositionOptions>(
        Configuration.GetSection(PositionOptions.Position));
    services.Configure<ColorOptions>(
        Configuration.GetSection(ColorOptions.Color));

    services.AddScoped<IMyDependency, MyDependency>();
    services.AddScoped<IMyDependency2, MyDependency2>();

    services.AddRazorPages();
}

相關的一組的註冊可以被移動到擴充套件方法中去註冊服務。例如,配置服務被新增到下面的類中:

using ConfigSample.Options;
using Microsoft.Extensions.Configuration;

namespace Microsoft.Extensions.DependencyInjection
{
    public static class MyConfigServiceCollectionExtensions
    {
        public static IServiceCollection AddConfig(
             this IServiceCollection services, IConfiguration config)
        {
            services.Configure<PositionOptions>(
                config.GetSection(PositionOptions.Position));
            services.Configure<ColorOptions>(
                config.GetSection(ColorOptions.Color));

            return services;
        }
    }
}

其餘的服務在一個相似的類中被註冊。下面的 ConfigureServices 方法使用的新的擴充套件方法註冊服務:

public void ConfigureServices(IServiceCollection services)
{
    services.AddConfig(Configuration)
            .AddMyDependencyGroup();

    services.AddRazorPages();
}

備註:每一個 services.Add{GROUP_NAME} 擴充套件方法新增和潛在的會配置服務。例如,AddControllersWithViews 新增帶有檢視的 MVC 控制器的服務,AddRazorPages 新增帶有 Razor Pages 的服務。我們推薦應用程式遵守命名的約定。把擴充套件方法統一放到名稱空間 Microsoft.Extensions.DependencyInjection 中去封裝一組服務的註冊。

安全和使用者祕密

資料配置指導:

  • 永遠不要把密碼或者其他敏感資料使用配置提供器儲存在程式碼中或者儲存在文字配置檔案中。在開發過程中,可以使用 Secret Manager 工具儲存祕密資料
  • 不要在開發環境或者測試環境中使用生產環境的祕密資料
  • 在工程外部指定祕密資料,以防被意外的提交到原始碼倉庫中

預設的,使用者祕密配置源是在 JSON 配置源之後註冊的。因此,使用者祕密的鍵值會生效,而不是 appsettings.json 和 appsettings.Environment.json 中的鍵值。

更多關於儲存密碼或者其他敏感資料的資訊:

Azure Key Vault 為 ASP.NET Core 應用程式安全的儲存應用程式的祕密。更多資訊檢視 Azure Key Vault Configuration Provider in ASP.NET Core

環境變數

使用預設的配置,EnvironmentVariablesConfigurationProvider 在讀取 appsettings.json,appsettings.Environment.json,和 user secrets 之後從環境變數載入鍵值對。因此,從環境變數讀取到的鍵值會覆蓋從 appsettings.json, appsettings.Environment.json 和 user secrets 中讀取到的值。

: 分隔符在所有平臺上對於環境便令分級鍵都是不工作的。__ 雙下換線:

  • 所有平臺都支援。例如, Bash 不支援 : 分隔符,但是支援 __
  • 會自動的被一個 : 替換

下面的設定命令:

  • 在 Windows 上設定前面示例(preceding exampl)中的環境鍵值和值
  • 使用示例程式(sample download)測試設定。dotnet run 命令必須在工程目錄中執行
set MyKey="My key from Environment"
set Position__Title=Environment_Editor
set Position__Name=Environment_Rick
dotnet run

上面的環境變數設定:

  • 僅僅在設定他們的命令列視窗中啟動的程式中
  • 不會被使用 Visual Studio 啟動的瀏覽器讀取

下面的  setx 命令可以被用來在 Windows 上設定環境鍵和值。不同於 set,setx 是持久化的。/M 在系統環境設定變數。如果沒有使用 /M 開關,一個使用者的環境變數會被設定。

setx MyKey "My key from setx Environment" /M
setx Position__Title Setx_Environment_Editor /M
setx Position__Name Environment_Rick /M

為了測試上面的命令會覆蓋 appsettings.json 和 asppsettings.Environment.json 的配置,需要做以下操作:

  • 使用 Visual Studio: 退出和重啟 Visual Studio
  • 使用命令列:啟動一個新的命令視窗,輸入 dotnet run

呼叫 AddEnvironmentVariables,使用一個字串指定環境變數的字首:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureAppConfiguration((hostingContext, config) =>
            {
                config.AddEnvironmentVariables(prefix: "MyCustomPrefix_");
            })
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

在上面的程式碼中:

當配置鍵值對被讀取的時候,字首會被去除。

下面的命令測試自定義字首:

set MyCustomPrefix_MyKey="My key with MyCustomPrefix_ Environment"
set MyCustomPrefix_Position__Title=Editor_with_customPrefix
set MyCustomPrefix_Position__Name=Environment_Rick_cp
dotnet run

預設配置 (default configuration) 載入帶有字首為 DOTNET_ 和 ASPNETCORE_ 的環境變數和命令列引數。DOTNET_ 和 ASPNETCORE_ 字首被 ASP.NET Core 用來配置主機和應用程式配置 (host and app configuration),不能用來作為使用者配置。更多關於主機和應用程式的配置,檢視 .NET Generic Host

關於  Azure App Service,在設定 (Settings) > 配置 (Configuration) 頁面選擇 新建應用程式設定 (New application setting)。Azure 應用程式服務設定:

  • 在休息是加密,並通過加密通道傳輸
  • 暴露為環境變數

更多資訊,檢視 Azure Apps: Override app configuration using the Azure Portal

檢視 Connection string prefixes 瞭解關於 Azure 資料庫連線字串的資訊。

 環境變數命名

環境變數的命名反映了 appsettings.json 檔案的結構。層級中的每一個元素使用雙下劃線(推薦的)或者冒號分割開來。當一個元素的結構包含一個陣列的時候,陣列的索引應該被當做是當前路徑中的一個額外的元素名稱。考慮下面的 appsettings.json 檔案和它在環境變數中等價的值。

appsettings.json

{
    "SmtpServer": "smtp.example.com",
    "Logging": [
        {
            "Name": "ToEmail",
            "Level": "Critical",
            "Args": {
                "FromAddress": "MySystem@example.com",
                "ToAddress": "SRE@example.com"
            }
        },
        {
            "Name": "ToConsole",
            "Level": "Information"
        }
    ]
}

環境變數 (environment variables)

setx SmtpServer=smtp.example.com
setx Logging__0__Name=ToEmail
setx Logging__0__Level=Critical
setx Logging__0__Args__FromAddress=MySystem@example.com
setx Logging__0__Args__ToAddress=SRE@example.com
setx Logging__1__Name=ToConsole
setx Logging__1__Level=Information

生成的 launchSettings.json 檔案中環境變數的設定

在 launchSettings.json 中設定的環境變數會覆蓋那些在系統環境中設定的值。例如,ASP.NET Core web 模板會生成一個 lauchSettings.json 檔案,檔案中設定了

endpoint 配置:

"applicationUrl": "https://localhost:5001;http://localhost:5000"

對 applicationUrl 的配置設定環境變數 ASPNETCORE_URLS  並覆蓋環境中的值。

Escape environment variables on Linux

在 linux 上,URL 環境變數的值必須被 escape 後系統才能夠解析它。使用 linux systemd-escape 工具生成 http:--localhost:5001

groot@terminus:~$ systemd-escape http://localhost:5001
http:--localhost:5001

顯示環境變數

下面的程式碼在應用程式啟動的時候輸出顯示了環境變數和對應的值,在除錯環境設定的時候非常有用:

public static void Main(string[] args)
{
    var host = CreateHostBuilder(args).Build();

    var config = host.Services.GetRequiredService<IConfiguration>();

    foreach (var c in config.AsEnumerable())
    {
        Console.WriteLine(c.Key + " = " + c.Value);
    }
    host.Run();
}

命令列

使用預設的配置,CommandLineConfigurationProvider 在下列配置源之後從命令列引數鍵值對中載入配置:

  • appsettings.json 和 appsettings.Environment.json 檔案
  • 開發環境中的 App secrets
  • 環境變數

預設的,在命令列中配置的值會覆蓋其它所有配置提供的值。

命令列引數

下面的命令使用 = 設定鍵和值:

dotnet run MyKey="My key from command line" Position:Title=Cmd Position:Name=Cmd_Rick

下面的命令使用 / 設定鍵值:

dotnet run /MyKey "Using /" /Position:Title=Cmd_ /Position:Name=Cmd_Rick

下面的命令使用 -- 設定鍵值:

dotnet run --MyKey "Using --" --Position:Title=Cmd-- --Position:Name=Cmd--Rick

鍵值:

  • 必須緊跟 = ,或當值在一個空格後面的時候,鍵必須有個 -- 或者 / 字首
  • 當使用 = 的時候,值不是必須要有的,例如 MySetting=

在相同的命令列中,不要把使用 = 的鍵值對和使用空格的鍵值對的命令列引數混淆。

轉換對映

切換對映允許鍵名替換邏輯。可以給 AddCommandLine 方法提供一個切換替換的字典。

當切換對映字典被用到的時候,字典被用來檢查匹配命令列引數提供的鍵。日過命令列的鍵在字典中被找到,字典中的值就會被傳回用來設定應用程式配置的鍵值對。切換對映要求任何的命令列鍵使用一個單獨的破折號作為字首。

切換對映字典鍵的規則:

  • 切換必須以 - 或者 -- 開頭
  • 切換對映字典不能包含重複的鍵

 

使用一個切換對映字典,需要把它傳遞給 AddCommandLine 方法:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args)
    {
        var switchMappings = new Dictionary<string, string>()
         {
             { "-k1", "key1" },
             { "-k2", "key2" },
             { "--alt3", "key3" },
             { "--alt4", "key4" },
             { "--alt5", "key5" },
             { "--alt6", "key6" },
         };

        return Host.CreateDefaultBuilder(args)
            .ConfigureAppConfiguration((hostingContext, config) =>
            {
                config.AddCommandLine(args, switchMappings);
            })
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
    }
}

下面的程式碼展示了被替換的鍵的值:

public class Test3Model : PageModel
{
    private readonly IConfiguration Config;

    public Test3Model(IConfiguration configuration)
    {
        Config = configuration;
    }

    public ContentResult OnGet()
    {
        return Content(
                $"Key1: '{Config["Key1"]}'\n" +
                $"Key2: '{Config["Key2"]}'\n" +
                $"Key3: '{Config["Key3"]}'\n" +
                $"Key4: '{Config["Key4"]}'\n" +
                $"Key5: '{Config["Key5"]}'\n" +
                $"Key6: '{Config["Key6"]}'");
    }
}

下面的命令用來測試鍵替換:

dotnet run -k1 value1 -k2 value2 --alt3=value2 /alt4=value3 --alt5 value5 /alt6 value6

對於使用切換對映的應用程式,呼叫 CreateDefaultBuilder 不應該傳遞引數。CreateDefaultBuilder 方法的 AddCommandLine 的呼叫不包括對映切換,沒有方法可以傳遞切換對映的字典給 CreateDefaultBuilder。解決方法是允許 ConfigurationBuilder 方法的 AddCommandLine 同時處理引數和切換對映字典而不是傳遞引數給 CreateDefaultBuilder。

分層配置資料

配置 API 通過使用在配置鍵中的分界符扁平化分層資料來讀取配置資料。

示例程式 (sample download) 包含下面的 appsettings.json 檔案:

{
  "Position": {
    "Title": "Editor",
    "Name": "Joe Smith"
  },
  "MyKey":  "My appsettings.json Value",
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

下面示例 (sample download) 中的程式碼展示了一些配置設定:

public class TestModel : PageModel
{
    // requires using Microsoft.Extensions.Configuration;
    private readonly IConfiguration Configuration;

    public TestModel(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var myKeyValue = Configuration["MyKey"];
        var title = Configuration["Position:Title"];
        var name = Configuration["Position:Name"];
        var defaultLogLevel = Configuration["Logging:LogLevel:Default"];


        return Content($"MyKey value: {myKeyValue} \n" +
                       $"Title: {title} \n" +
                       $"Name: {name} \n" +
                       $"Default Log Level: {defaultLogLevel}");
    }
}

讀取分層配置資料的首選方式是使用選項模型。更多資訊,檢視本文件中的繫結分層配置資料 (Bind hierarchical configuration data)。

GetSection 和 GetChildren 方法可以用來分離配置資料中的分割槽和分割槽中的子部分。這些方法在之後的 GetSection, GetChildren, and Exists 中會描述到。

配置的鍵和值

配置的鍵:

  • 不區分大小寫。例如 ConnectionString 和 connectionstring 被認為是等價的鍵
  • 如果一個鍵和值在多個配置提供器中被設定,最後一個提供器的值將會被使用。更多資訊,檢視預設配置 (Default configuration)
  • 分層鍵
    - 在配置 API 內部,一個冒號分隔符 (:) 在所有平臺都能工作
    - 在環境變數中,一個冒號分隔符可能不會在所有平臺上工作。雙下劃線 (__) 被所有平臺支援,並且會被自動的轉換為一個冒號 (:)
    - 在 Azure 鍵倉庫中,分層的鍵使用 -- 作為一個分隔符。當祕密資料被載入到應用程式配置的時候,Azure Key Vault configuration provider 自動使用一個冒號 (:) 替換 (--)。
  • ConfigurationBinder 支援繫結陣列到在配置鍵中使用陣列索引的物件。陣列繫結在 Bind an array to a class 部分描述。

配置值:

  • 是字串
  • Null 值不能儲存在配置中或者繫結到物件

配置提供器

下面的表格中顯示了 ASP.NET Core 應用程式中可以使用的配置提供器

Provider Providers configuration from
Azure Key Vault configuration provider Azure Key Valut
Azure App configuration provider Azure App Configuration
Command-line configuration provider Command-line parameters
Custom configuration provider Custom source
Environment Variables configuration provider Environment variables
File configuration provider INI,JSON,XML 檔案
Key-per-file configuration provider 字典檔案
Memory configuration provider 記憶體中的集合
User secrets 使用者配置目錄中的檔案

配置源的讀取的順序按照它們的配置提供器被指定的順序。在程式碼中對配置提供器排序來滿足應用程式要求的配置源的順序。

一個典型的配置提供器的順序是:

  1. appsettings.json
  2. appsettings.Environment.json
  3. User secrets
  4. 使用 Environment Variables configuration provider 的環境變數
  5. 使用 Command-line configuration provider 的命令列引數

一個常用的實踐是在一系列的配置提供器的最後新增命令列配置提供器,用來覆蓋其它提供器的配置設定

上面的提供器的順序在預設配置 (default configuration) 中使用。

連線字串字首

配置 API 對於四種連線字串環境變數有特殊的處理規則。這些連線字串會根據應用程式環境解析來配置 Azure 連線字串。下面表格中的帶有字首的環境變數在應用程式使用預設配置 (default configuration)或者沒有字首沒有應用到 AddEnvironmentVariables 的情況下會被載入到應用程式中。

Connection string prefix Provider
CUSTOMCONNSTR_ 自定義提供器
MYSQLCONNSTR_ MySQL
SQLAZURECONNSTR_ Azure SQL Database
SQLCONNSTR_ SQL Server

當一個環境變數被發現並且使用表格中四種字首的任一種被載入到配置中的時候:

  • 通過移除環境變數的字首建立配置的鍵,新增一個配置鍵的區域(ConnectionStrings)
  • 一個新的配置的鍵值對被建立,這個鍵值對代表了資料庫連線提供器 (CUSTOMCONNSTR_ 除外,由於沒有固定的提供器)
環境變數鍵 轉換後的配置鍵 提供器配置入口
CUSTOMCONNSTR_{KEY} ConnectionStrings:{KEY} 配置入口沒有建立
MYSQLCONNSTR_{KEY} ConnectionString:{KEY}

Key:ConnectionStrings:

{KEY}_ProviderName:

Value:MySql.DataMySqlClient

SQLAZURECONNSTR_{KEY} ConnectionStrings:{KEY}

Key:ConnectionStrings:

{KEY}_ProviderName:

Value:System.Data.SqlClient

SQLCONNSTR_{KEY} ConnectionStrings:{KEY}

Key:ConnectionStrings:

{KEY}_ProviderName:

Value:System.Data.SqlClient

 檔案配置提供器

FileConfigurationProvider 是從檔案系統載入配置的基類。下面的配置提供器都從 FileConfigurationProvider 類繼承而來。

INI 配置提供器

IniConfigurationProvider 在執行時從 INI 檔案中載入鍵值對的配置。

下面的程式碼清空了所有配置提供器,新增了一些配置提供器:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureAppConfiguration((hostingContext, config) =>
            {
                config.Sources.Clear();

                var env = hostingContext.HostingEnvironment;

                config.AddIniFile("MyIniConfig.ini", optional: true, reloadOnChange: true)
                      .AddIniFile($"MyIniConfig.{env.EnvironmentName}.ini",
                                     optional: true, reloadOnChange: true);

                config.AddEnvironmentVariables();

                if (args != null)
                {
                    config.AddCommandLine(args);
                }
            })
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

 在上面的程式碼中,檔案 MyIniCofig.ini 和 MyIniConfig.Environment.ini 中的配置會被以下配置覆蓋:

示例程式(sample download)包含以下 MyIniConfig.ini 檔案:

MyKey="MyIniConfig.ini Value"

[Position]
Title="My INI Config title"
Name="My INI Config name"

[Logging:LogLevel]
Default=Information
Microsoft=Warning

下面的程式碼來自示例程式(sample download),展示了前面配置設定:

public class TestModel : PageModel
{
    // requires using Microsoft.Extensions.Configuration;
    private readonly IConfiguration Configuration;

    public TestModel(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var myKeyValue = Configuration["MyKey"];
        var title = Configuration["Position:Title"];
        var name = Configuration["Position:Name"];
        var defaultLogLevel = Configuration["Logging:LogLevel:Default"];


        return Content($"MyKey value: {myKeyValue} \n" +
                       $"Title: {title} \n" +
                       $"Name: {name} \n" +
                       $"Default Log Level: {defaultLogLevel}");
    }
}

JSON 配置提供器

JsonConfigurationProvider 從 JSON 檔案中載入鍵值對

過載可以指定:

  • 檔案是否可選
  • 檔案改變配置是否重新載入

考慮下面的程式碼:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureAppConfiguration((hostingContext, config) =>
            {
                config.AddJsonFile("MyConfig.json", 
                    optional: true, 
                    reloadOnChange: true);
            })
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

上面的程式碼:

一般的,你並不希望一個自定義的 JSON 檔案去覆蓋 Environment variables configuration provider 和 Command-line configuration provider 中的配置設定值。

下面的程式碼清除了所有的配置提供器並且新增了一些配置提供器:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureAppConfiguration((hostingContext, config) =>
            {
                config.Sources.Clear();

                var env = hostingContext.HostingEnvironment;

                config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                      .AddJsonFile($"appsettings.{env.EnvironmentName}.json", 
                                     optional: true, reloadOnChange: true);

                config.AddJsonFile("MyConfig.json", optional: true, reloadOnChange: true)
                      .AddJsonFile($"MyConfig.{env.EnvironmentName}.json",
                                     optional: true, reloadOnChange: true);

                config.AddEnvironmentVariables();

                if (args != null)
                {
                    config.AddCommandLine(args);
                }
            })
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

在上面的程式碼中,MyConfig.json 和 MyConfig.Environment.json 檔案中的設定:

示例程式(sample download)包含以下 MyConfig.json 檔案:

{
  "Position": {
    "Title": "My Config title",
    "Name": "My Config Smith"
  },
  "MyKey":  "MyConfig.json Value",
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

下面的示例程式(sample download)中的程式碼展示了上面配置設定:

public class TestModel : PageModel
{
    // requires using Microsoft.Extensions.Configuration;
    private readonly IConfiguration Configuration;

    public TestModel(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var myKeyValue = Configuration["MyKey"];
        var title = Configuration["Position:Title"];
        var name = Configuration["Position:Name"];
        var defaultLogLevel = Configuration["Logging:LogLevel:Default"];


        return Content($"MyKey value: {myKeyValue} \n" +
                       $"Title: {title} \n" +
                       $"Name: {name} \n" +
                       $"Default Log Level: {defaultLogLevel}");
    }
}

XML 配置提供器

XmlConfigurationProvider 在執行時從 XML 檔案中載入鍵值對配置。

下面的程式碼清空了所有的配置提供器並且新增了一些配置提供器:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureAppConfiguration((hostingContext, config) =>
            {
                config.Sources.Clear();

                var env = hostingContext.HostingEnvironment;

                config.AddXmlFile("MyXMLFile.xml", optional: true, reloadOnChange: true)
                      .AddXmlFile($"MyXMLFile.{env.EnvironmentName}.xml",
                                     optional: true, reloadOnChange: true);

                config.AddEnvironmentVariables();

                if (args != null)
                {
                    config.AddCommandLine(args);
                }
            })
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

在上面的程式碼中,MyXMLFile.xml 和 MyXMLFile.Environment.xml 檔案中的設定會被以下配置中的設定覆蓋:

示例程式(sample download)包含下面的 MyXMLFile.xml 檔案:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <MyKey>MyXMLFile Value</MyKey>
  <Position>
    <Title>Title from  MyXMLFile</Title>
    <Name>Name from MyXMLFile</Name>
  </Position>
  <Logging>
    <LogLevel>
      <Default>Information</Default>
      <Microsoft>Warning</Microsoft>
    </LogLevel>
  </Logging>
</configuration>

下面示例程式(sample download)中的程式碼展示了一些上面配置設定:

public class TestModel : PageModel
{
    // requires using Microsoft.Extensions.Configuration;
    private readonly IConfiguration Configuration;

    public TestModel(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var myKeyValue = Configuration["MyKey"];
        var title = Configuration["Position:Title"];
        var name = Configuration["Position:Name"];
        var defaultLogLevel = Configuration["Logging:LogLevel:Default"];


        return Content($"MyKey value: {myKeyValue} \n" +
                       $"Title: {title} \n" +
                       $"Name: {name} \n" +
                       $"Default Log Level: {defaultLogLevel}");
    }
}

那些使用相同元素名稱的重複的元素,如果使用 name 屬性用來區分的話,也是可以工作的:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <section name="section0">
    <key name="key0">value 00</key>
    <key name="key1">value 01</key>
  </section>
  <section name="section1">
    <key name="key0">value 10</key>
    <key name="key1">value 11</key>
  </section>
</configuration>

下面的配置讀取前面的配置檔案,展示了鍵和值:

public class IndexModel : PageModel
{
    private readonly IConfiguration Configuration;

    public IndexModel(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var key00 = "section:section0:key:key0";
        var key01 = "section:section0:key:key1";
        var key10 = "section:section1:key:key0";
        var key11 = "section:section1:key:key1";

        var val00 = Configuration[key00];
        var val01 = Configuration[key01];
        var val10 = Configuration[key10];
        var val11 = Configuration[key11];

        return Content($"{key00} value: {val00} \n" +
                       $"{key01} value: {val01} \n" +
                       $"{key10} value: {val10} \n" +
                       $"{key10} value: {val11} \n"
                       );
    }
}

屬性可以用來提供值:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <key attribute="value" />
  <section>
    <key attribute="value" />
  </section>
</configuration>

前面的配置檔案使用下面的鍵和值載入:

  • key: attribute
  • section: key:attribute

Key-per-file 配置提供器

KeyPerFileConfigurationProvider 使用目錄檔案作為鍵值對配置。檔名稱作為鍵。檔案的內容作為值。Key-per-file 配置提供器在 Docker 託管的場景中使用。

為了啟動 key-per-file 配置,可以呼叫 ConfigurationBuilder 例項的擴充套件方法 AddKeyPerFile。檔案的 directoryPath 必須是絕對路徑。

過載允許指定:

  • 使用 Action<KeyPerFileConfigurationSource> 代理來配置源
  • 目錄和目錄的路徑是否可選

雙下劃線(__)用來在檔名中作為配置鍵的分隔符。例如,檔名 Loggin_LogLevel_System 會生成配置鍵 Logging:LogLevel:System。

在建立主機的時候呼叫 ConfigureAppConfiguration 來指定應用程式配置:

.ConfigureAppConfiguration((hostingContext, config) =>
{
    var path = Path.Combine(
        Directory.GetCurrentDirectory(), "path/to/files");
    config.AddKeyPerFile(directoryPath: path, optional: true);
})

記憶體配置提供器

MemoryConfigurationProvider 使用記憶體中的集合作為配置鍵值對。

下面的程式碼新增一個記憶體集合到配置系統:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args)
    {
        var Dict = new Dictionary<string, string>
        {
           {"MyKey", "Dictionary MyKey Value"},
           {"Position:Title", "Dictionary_Title"},
           {"Position:Name", "Dictionary_Name" },
           {"Logging:LogLevel:Default", "Warning"}
        };

        return Host.CreateDefaultBuilder(args)
            .ConfigureAppConfiguration((hostingContext, config) =>
            {
                config.AddInMemoryCollection(Dict);
            })
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();,
            });
    }
}

下面示例程式(sample download)中的程式碼展示了前面新增的配置中的設定:

public class TestModel : PageModel
{
    // requires using Microsoft.Extensions.Configuration;
    private readonly IConfiguration Configuration;

    public TestModel(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var myKeyValue = Configuration["MyKey"];
        var title = Configuration["Position:Title"];
        var name = Configuration["Position:Name"];
        var defaultLogLevel = Configuration["Logging:LogLevel:Default"];


        return Content($"MyKey value: {myKeyValue} \n" +
                       $"Title: {title} \n" +
                       $"Name: {name} \n" +
                       $"Default Log Level: {defaultLogLevel}");
    }
}

上面的程式碼中,config.AddInMemoryCollection(Dict) 在預設配置提供器(default configuration providers)之後新增。配置提供器的載入順序的示例,請檢視  JSON configuration provider

檢視 Bind an array 另一個使用 MemoryConfigurationProvider 的例子。

Kestrel endpoint 配置

Kestrel 指定的 endpoint 配置會覆蓋所有 cross-server endpoint 的配置。Cross-server endpoint 配置包含:

考慮下面在 ASP.NET Core web 應用程式中使用的 appsetting.json 檔案:

{
  "Kestrel": {
    "Endpoints": {
      "Https": {
        "Url": "https://localhost:9999"
      }
    }
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

當前面高亮標記的配置在 ASP.NET Core web 應用程式中被使用,並且應用程式使用下面的 cross-server endpoint 配置在命令列中啟動:

dotnet run --urls="https://localhost:7777"

Kestrel 會繫結到 appsettings.json 檔案中的 endpoint 特定的的配置(https://localhost:9999),而不是 https://localhost:7777。

考慮指定 Kestrel enpoint 配置作為一個環境變數:

set Kestrel__Endpoints__Https__Url=https://localhost:8888

在上面這個環境變數中,Https 是 Kestrel 指定的 endpoint 的名稱。前面的 appsettings.json 檔案也定義了指定的 endpoint 名稱 Https. 預設的,使用 Environment Variables configuration provider 的環境變數在 appsettings.Environment.json 之後被讀取,因此,上面的環境變數被用作 Https endpoint。

 

GetValue

ConfigurationBinder.GetValue<T> 使用指定的鍵從配置檔案中獲取一個單獨的值,並轉換為指定型別:

public class TestNumModel : PageModel
{
    private readonly IConfiguration Configuration;

    public TestNumModel(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var number = Configuration.GetValue<int>("NumberKey", 99);
        return Content($"{number}");
    }
}

在上面的程式碼中,如果 NumberKey 在配置中沒有被發現,預設值 99 將會被使用。

GetSection, GetChildren,and Exists

下面的示例中,考慮以下 MySubsection.json 檔案:

{
  "section0": {
    "key0": "value00",
    "key1": "value01"
  },
  "section1": {
    "key0": "value10",
    "key1": "value11"
  },
  "section2": {
    "subsection0": {
      "key0": "value200",
      "key1": "value201"
    },
    "subsection1": {
      "key0": "value210",
      "key1": "value211"
    }
  }
}

下面的程式碼新增 MySubsection.json 到配置提供器中:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureAppConfiguration((hostingContext, config) =>
            {
                config.AddJsonFile("MySubsection.json", 
                    optional: true, 
                    reloadOnChange: true);
            })
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

GetSection

IConfiguration.GetSection 使用一個子區域鍵返回一個配置子區域:

下面的程式碼返回 section1 的值:

public class TestSectionModel : PageModel
{
    private readonly IConfiguration Config;

    public TestSectionModel(IConfiguration configuration)
    {
        Config = configuration.GetSection("section1");
    }

    public ContentResult OnGet()
    {
        return Content(
                $"section1:key0: '{Config["key0"]}'\n" +
                $"section1:key1: '{Config["key1"]}'");
    }
}

下面的程式碼返回 section2:subsection0 的值:

public class TestSection2Model : PageModel
{
    private readonly IConfiguration Config;

    public TestSection2Model(IConfiguration configuration)
    {
        Config = configuration.GetSection("section2:subsection0");
    }

    public ContentResult OnGet()
    {
        return Content(
                $"section2:subsection0:key0 '{Config["key0"]}'\n" +
                $"section2:subsection0:key1:'{Config["key1"]}'");
    }
}

GetSection 從不返回 null。如果沒有匹配的區域,一個空的 IConfigurationSection 被返回。

當 GetSection 返回一個匹配的區域,值(Value)不會被填充。當一個區域存在的時候,會返回一個鍵(Key)和路徑(Path)。

GetChildren 和 Exists

下面的程式碼呼叫 IConfiguration.GetChildren,並返回 section2:subsection0 對應的值:

public class TestSection4Model : PageModel
{
    private readonly IConfiguration Config;

    public TestSection4Model(IConfiguration configuration)
    {
        Config = configuration;
    }

    public ContentResult OnGet()
    {
        string s = null;
        var selection = Config.GetSection("section2");
        if (!selection.Exists())
        {
            throw new System.Exception("section2 does not exist.");
        }
        var children = selection.GetChildren();

        foreach (var subSection in children)
        {
            int i = 0;
            var key1 = subSection.Key + ":key" + i++.ToString();
            var key2 = subSection.Key + ":key" + i.ToString();
            s += key1 + " value: " + selection[key1] + "\n";
            s += key2 + " value: " + selection[key2] + "\n";
        }
        return Content(s);
    }
}

上面的程式碼呼叫 ConfigurationExtensions.Exists 驗證指定區域是否存在。

繫結一個陣列

ConfigurationBinder.Bind 支援使用配置中鍵中的陣列索引繫結陣列到一組物件。任何暴露一個數字型別鍵的段格式的陣列,都是能夠繫結到一個 POCO 類的陣列。

考慮來自示例程式(sample download)中的 MyArray.json 檔案:

{
  "array": {
    "entries": {
      "0": "value00",
      "1": "value10",
      "2": "value20",
      "4": "value40",
      "5": "value50"
    }
  }
}

下面的程式碼新增 MyArray.json 檔案到配置提供器中:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureAppConfiguration((hostingContext, config) =>
            {
                config.AddJsonFile("MyArray.json", 
                    optional: true, 
                    reloadOnChange: true);
            })
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

下面的程式碼讀取配置並且顯示出配置中的值:

public class ArrayModel : PageModel
{
    private readonly IConfiguration Config;
    public ArrayExample _array { get; private set; }

    public ArrayModel(IConfiguration config)
    {
        Config = config;
    }

    public ContentResult OnGet()
    {
        _array = Config.GetSection("array").Get<ArrayExample>();
        string s = null;

        for (int j = 0; j < _array.Entries.Length; j++)
        {
            s += $"Index: {j}  Value:  {_array.Entries[j]} \n";
        }

        return Content(s);
    }
}

上面的程式碼輸出下面的內容:

Index: 0  Value: value00
Index: 1  Value: value10
Index: 2  Value: value20
Index: 3  Value: value40
Index: 4  Value: value50

上面的輸出中,索引 3 對應的值是 value40,和檔案 MyArray.json 中的 “4”:"value40" 相對應。繫結陣列的索引是連續的,而不是繫結到配置鍵的值作為索引。配置繫結沒有繫結空值或者為繫結物件建立空值的能力。

下面的程式碼使用 AddInMemoryCollection 擴充套件方法載入 array:entries 配置:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args)
    {
        var arrayDict = new Dictionary<string, string>
        {
            {"array:entries:0", "value0"},
            {"array:entries:1", "value1"},
            {"array:entries:2", "value2"},
            //              3   Skipped
            {"array:entries:4", "value4"},
            {"array:entries:5", "value5"}
        };

        return Host.CreateDefaultBuilder(args)
            .ConfigureAppConfiguration((hostingContext, config) =>
            {
                config.AddInMemoryCollection(arrayDict);
            })
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
    }
}

下面的程式碼讀取 arrayDict Dictionary 中的配置並且顯示輸出對應的值:

public class ArrayModel : PageModel
{
    private readonly IConfiguration Config;
    public ArrayExample _array { get; private set; }

    public ArrayModel(IConfiguration config)
    {
        Config = config;
    }

    public ContentResult OnGet()
    {
        _array = Config.GetSection("array").Get<ArrayExample>();
        string s = null;

        for (int j = 0; j < _array.Entries.Length; j++)
        {
            s += $"Index: {j}  Value:  {_array.Entries[j]} \n";
        }

        return Content(s);
    }
}

上面的程式碼輸出以下內容:

Index: 0  Value: value0
Index: 1  Value: value1
Index: 2  Value: value2
Index: 3  Value: value4
Index: 4  Value: value5

索引 #3 的繫結物件對應的配置資料是鍵 array:4 以及它對應的值。當包含一個陣列的配置資料被繫結時,配置鍵對應的陣列索引在建立物件時被用來迭代配置資料。配置資料不能引用一個空值,並且當一個陣列在配置鍵跳過一個或者多個索引的時候,一個空的實體是不能被建立的。

缺失的索引為 #3 的配置專案可以在繫結到 ArrayExample 例項之前通過任意的配置提供器讀取索引 $3 鍵值對來提供。

考慮示例程式中的 Value3.json 檔案:

{
  "array:entries:3": "value3"
}

下面的程式碼包含 Value3.json 的配置和 arrayDict Dictionary:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args)
    {
        var arrayDict = new Dictionary<string, string>
        {
            {"array:entries:0", "value0"},
            {"array:entries:1", "value1"},
            {"array:entries:2", "value2"},
            //              3   Skipped
            {"array:entries:4", "value4"},
            {"array:entries:5", "value5"}
        };

        return Host.CreateDefaultBuilder(args)
            .ConfigureAppConfiguration((hostingContext, config) =>
            {
                config.AddInMemoryCollection(arrayDict);
                config.AddJsonFile("Value3.json",
                                    optional: false, reloadOnChange: false);
            })
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
    }
}

下面的程式碼讀取前面的配置並且顯示配置值:

public class ArrayModel : PageModel
{
    private readonly IConfiguration Config;
    public ArrayExample _array { get; private set; }

    public ArrayModel(IConfiguration config)
    {
        Config = config;
    }

    public ContentResult OnGet()
    {
        _array = Config.GetSection("array").Get<ArrayExample>();
        string s = null;

        for (int j = 0; j < _array.Entries.Length; j++)
        {
            s += $"Index: {j}  Value:  {_array.Entries[j]} \n";
        }

        return Content(s);
    }
}

上面的程式碼輸出以下內容:

Index: 0  Value: value0
Index: 1  Value: value1
Index: 2  Value: value2
Index: 3  Value: value3
Index: 4  Value: value4
Index: 5  Value: value5

自定義配置提供器不需要實現陣列繫結。

自定義配置提供器

示例程式展示瞭如何建立一個基本的配置提供器,使用 Entity Framework (EF) 從資料庫中讀取鍵值對配置。

提供器有以下特性:

  • EF 記憶體資料庫僅僅用於展示目的。如果要使用需要連線字串的資料庫,可以實現一個次要的 ConfigurationBuilder 支援來自其它配置提供器的連線字串
  • 提供器在啟動時讀取一個資料庫的表到配置中。提供器獲取資料庫不基於基本的 per-key
  • Reload-on-change 沒有實現,因此在應用程式啟動後更新資料庫對應用程式的配置沒有影響

定義一個 EFConfigurationValue 實體用來儲存資料庫中的配置值。

Models/EFConfigurationValue.cs:

 

public class EFConfigurationValue
{
    public string Id { get; set; }
    public string Value { get; set; }
}

新增一個 EFConfigurationContext 用來儲存和訪問配置值。

EFConfigurationProvider/EFConfigurationContext.cs:

// using Microsoft.EntityFrameworkCore;

public class EFConfigurationContext : DbContext
{
    public EFConfigurationContext(DbContextOptions options) : base(options)
    {
    }

    public DbSet<EFConfigurationValue> Values { get; set; }
}

建立一個實現 IConfigurationSource 介面的類:

EFConfigurationProvider/EFConfigurationSource.cs:

// using Microsoft.EntityFrameworkCore;
// using Microsoft.Extensions.Configuration;

public class EFConfigurationSource : IConfigurationSource
{
    private readonly Action<DbContextOptionsBuilder> _optionsAction;

    public EFConfigurationSource(Action<DbContextOptionsBuilder> optionsAction)
    {
        _optionsAction = optionsAction;
    }

    public IConfigurationProvider Build(IConfigurationBuilder builder)
    {
        return new EFConfigurationProvider(_optionsAction);
    }
}

通過繼承 ConfigurationProvider 建立一個自定義的配置提供器。配置提供器初始化一個空的資料庫。由於 configuration keys are case-insensitive 的原因,

使用 case-insensitive 的比較器(StringComparer.OrdinalIgnoreCase)建立的字典被用來初始化資料庫。

EFConfigurationProvider/EFConfigurationProvider.cs:

// using Microsoft.EntityFrameworkCore;
// using Microsoft.Extensions.Configuration;

public class EFConfigurationProvider : ConfigurationProvider
{
    public EFConfigurationProvider(Action<DbContextOptionsBuilder> optionsAction)
    {
        OptionsAction = optionsAction;
    }

    Action<DbContextOptionsBuilder> OptionsAction { get; }

    public override void Load()
    {
        var builder = new DbContextOptionsBuilder<EFConfigurationContext>();

        OptionsAction(builder);

        using (var dbContext = new EFConfigurationContext(builder.Options))
        {
            dbContext.Database.EnsureCreated();

            Data = !dbContext.Values.Any()
                ? CreateAndSaveDefaultValues(dbContext)
                : dbContext.Values.ToDictionary(c => c.Id, c => c.Value);
        }
    }

    private static IDictionary<string, string> CreateAndSaveDefaultValues(
        EFConfigurationContext dbContext)
    {
        // Quotes (c)2005 Universal Pictures: Serenity
        // https://www.uphe.com/movies/serenity
        var configValues = 
            new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
            {
                { "quote1", "I aim to misbehave." },
                { "quote2", "I swallowed a bug." },
                { "quote3", "You can't stop the signal, Mal." }
            };

        dbContext.Values.AddRange(configValues
            .Select(kvp => new EFConfigurationValue 
                {
                    Id = kvp.Key,
                    Value = kvp.Value
                })
            .ToArray());

        dbContext.SaveChanges();

        return configValues;
    }
}

AddEFConfiguration 的一個擴充套件方法允許新增配置源到 ConfigurationBuilder.

Extension/EntityFrameworkExtensions.cs:

// using Microsoft.EntityFrameworkCore;
// using Microsoft.Extensions.Configuration;

public static class EntityFrameworkExtensions
{
    public static IConfigurationBuilder AddEFConfiguration(
        this IConfigurationBuilder builder, 
        Action<DbContextOptionsBuilder> optionsAction)
    {
        return builder.Add(new EFConfigurationSource(optionsAction));
    }
}

下面的程式碼展示了在 Program.cs 中如何使用 EFConfigurationProvider:

// using Microsoft.EntityFrameworkCore;

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureAppConfiguration((hostingContext, config) =>
        {
            config.AddEFConfiguration(
                options => options.UseInMemoryDatabase("InMemoryDb"));
        })

在 Startup 中獲取配置

下面的程式碼在 Startup 的方法中顯示了配置資料:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddRazorPages();
        Console.WriteLine($"MyKey : {Configuration["MyKey"]}");
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        Console.WriteLine($"Position:Title : {Configuration["Position:Title"]}");

        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Error");
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapRazorPages();
        });
    }
}

使用 startup 約定的方法獲取配置的示例,請檢視 App startup: Convenience methods

在 Razor Pages 中獲取配置

下面的程式碼在 Razor Page 中顯示配置:

 

@page
@model Test5Model
@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration

Configuration value for 'MyKey': @Configuration["MyKey"]

在下面的程式碼中,使用了 Configure 方法把 MyOptions 新增到服務容器中,並且繫結到配置:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<MyOptions>(Configuration.GetSection("MyOptions"));

    services.AddRazorPages();
}

下面的程式碼使用 @inject Razor directive 指令獲取和顯示選項的值:

@page
@model SampleApp.Pages.Test3Model
@using Microsoft.Extensions.Options
@inject IOptions<MyOptions> optionsAccessor


<p><b>Option1:</b> @optionsAccessor.Value.Option1</p>
<p><b>Option2:</b> @optionsAccessor.Value.Option2</p>

在 MVC view 檔案中獲取配置

下面的程式碼在一個 MVC view 中展示配置資料:

@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration

Configuration value for 'MyKey': @Configuration["MyKey"]

使用代理配置選項

代理中的選項配置會覆蓋配置提供器中的值。

使用代理配置選項在示例程式中作為 Example 2 展示。

在下面的程式碼中,IConfigureOptions<TOptions> 服務被新增到服務容器中。它使用一個代理配置 MyOptions 的值:

 

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<MyOptions>(myOptions =>
    {
        myOptions.Option1 = "Value configured in delegate";
        myOptions.Option2 = 500;
    });

    services.AddRazorPages();
}

下面的程式碼展示了選項的值:

public class Test2Model : PageModel
{
    private readonly IOptions<MyOptions> _optionsDelegate;

    public Test2Model(IOptions<MyOptions> optionsDelegate )
    {
        _optionsDelegate = optionsDelegate;
    }

    public ContentResult OnGet()
    {
        return Content($"Option1: {_optionsDelegate.Value.Option1} \n" +
                       $"Option2: {_optionsDelegate.Value.Option2}");
    }
}

在上面的程式碼中,Option1 和 Option2 的值在 appsetting.json 指定,然後會被配置代理覆蓋。

主機與應用配置

在應用程式配置和啟動前,一個主機被配置和啟動。主機負責應用程式的啟動和生命週期的管理。應用程式和主機都是使用本文中描述的配置提供器配置。主機配置的鍵值對也包含在應用程式配置中。更多關於當主機建立的時候如果使用配置提供器以及不同配置源如何影響主機配置,請檢視 ASP.NET Core fundamentals

預設主機配置

更多關於使用 Web Host 時的預設配置的細節,檢視 ASP.NET Core 2.2 version of this topic

  • 主機配置
    字首為 DOTNET_(例如 DOTNET_ENVIRONMENT) 的環境變數使用 Environment Variables configuration provider。當配置鍵值對被載入的時候,字首 (DOTNET_) 會被去除。
    命令列引數使用 Command-line configuration provider
  • Web 主機的建立(ConfigureWebHostDefaults)
    Kestrel 被用來作為 web 伺服器,使用應用程式配置提供器
    新增主機過濾中介軟體
    如果 ASPNETCORE_FORWARDEDHEADERS_ENABLED 環境變數設定為 true 時,新增 Forwarded Headers Middleware 中介軟體
  • 使能 IIS 整合

其它配置

本文只和 app configuration 有關。其它執行和託管 ASP.NET Core 應用程式方面的配置檔案並不涵蓋:

launchSettings.json 中的環境變數設定會覆蓋系統中的環境變數。

更過關於從更早版本 ASP.NET Core 遷移應用程式配置檔案的資訊,請檢視 Migrate from ASP.NET to ASP.NET Core

從外部程式集新增配置

一個 IHostingStartup 的實現允許在 startup 中從一個外部的程式集的 Startup 類的外部新增更多的配置到一個應用程式中。更多資訊請檢視 Use hosting startup assemblies in ASP.NET Core

相關文章