ASP.NET Core 中的選項模式

注意

這不是這篇文章的最新版本。 如需目前版本,請參閱 本文的 ASP.NET Core 8.0 版本。

作者:Rick Anderson

選項模式會使用類別來提供相關設定群組的強型別存取。 當組態設定依案例隔離到不同的類別時,應用程式會遵守兩個重要的軟體工程準則:

  • 封裝
    • 相依於組態設定的類別只會相依於其使用的組態設定。
  • 關注點分離
    • 應用程式不同部分的設定不會彼此相依或結合。

選項也提供驗證設定資料的機制。 如需詳細資訊,請參閱選項驗證一節。

本文提供 ASP.NET Core 中的選項模式相關資訊。 如需在主控台應用程式中使用選項模式的詳細資訊,請參閱 .NET 中的選項模式

繫結階層式設定

讀取相關設定值的慣用方式是使用選項模式 (部分機器翻譯)。 例如,為讀取下列設定值:

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

建立下列 PositionOptions 類別:

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

    public string Title { get; set; } = String.Empty;
    public string Name { get; set; } = String.Empty;
}

選項類別:

  • 必須是非抽象的。
  • 具有繫結了組態中對應項目的類型的公開讀寫屬性。
  • 具有其繫結至組態中相符項目的讀寫屬性。
  • 沒有繫結它的欄位。 在上述程式碼中,Position 未繫結: 會使用 Position 欄位,因此當您將類別繫結到設定提供者時,不需要以硬式編碼方式將字串 "Position" 放在應用程式中。

下列程式碼範例:

  • 呼叫 ConfigurationBinder.Bind 以將 PositionOptions 類別繫結到 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 還方便。 下列程式碼會示範如何搭配 PositionOptions 類別使用 ConfigurationBinder.Get<T>

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 設定檔所做的變更。

繫結也允許抽象類別的具體化。 請考慮下列使用抽象類別 SomethingWithAName 的程式碼:

namespace ConfigSample.Options;

public abstract class SomethingWithAName
{
    public abstract string? Name { get; set; }
}

public class NameTitleOptions(int age) : SomethingWithAName
{
    public const string NameTitle = "NameTitle";

    public override string? Name { get; set; }
    public string Title { get; set; } = string.Empty;

    public int Age { get; set; } = age;
}

下列程式碼會顯示 NameTitleOptions 設定值:

public class Test33Model : PageModel
{
    private readonly IConfiguration Configuration;

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

    public ContentResult OnGet()
    {
        var nameTitleOptions = new NameTitleOptions(22);
        Configuration.GetSection(NameTitleOptions.NameTitle).Bind(nameTitleOptions);

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

呼叫 Bind 不像呼叫 Get<> 來得嚴格:

  • Bind 允許抽象的具體化。
  • Get<> 必須建立執行個體本身。

選項模式

使用選項模式的替代方式是繫結 Position 區段並將其新增至相依性插入服務容器。 在下列程式碼中,PositionOptions 會使用 Configure 新增到服務容器,並繫結到設定:

using ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<PositionOptions>(
    builder.Configuration.GetSection(PositionOptions.Position));

var app = builder.Build();

使用上述程式碼,下列程式碼會讀取位置選項:

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

選項介面

IOptions<TOptions>

IOptionsSnapshot<TOptions>

IOptionsMonitor<TOptions>

設定後案例可讓您在所有 IConfigureOptions<TOptions> 設定發生時設定或變更選項。

IOptionsFactory<TOptions> 負責建立新的選項執行個體。 它有單一 Create 方法。 預設實作會接受所有已註冊的 IConfigureOptions<TOptions>IPostConfigureOptions<TOptions>,並先執行所有設定,接著執行設定後作業。 它會區別 IConfigureNamedOptions<TOptions>IConfigureOptions<TOptions>,且只會呼叫適當的介面。

IOptionsMonitorCache<TOptions> 會由 IOptionsMonitor<TOptions> 用來快取 TOptions 執行個體。 IOptionsMonitorCache<TOptions> 會使監視器中的選項執行個體失效,以便重新計算值 (TryRemove)。 值可以使用 TryAdd 手動導入。 Clear 方法用於應該視需要重新建立所有具名執行個體時。

使用 IOptionsSnapshot 讀取更新的資料

使用 IOptionsSnapshot<TOptions>

  • 在要求的存留期內存取及快取選項時,會針對每個要求計算一次選項。
  • 可能造成顯著的效能損失,因為這是有限範圍的服務,而且會依要求重新計算。 如需詳細資訊,請參閱此 GitHub 問題改善組態繫結的效能
  • 當使用支援讀取更新設定值的設定提供者時,會在應用程式啟動後讀取設定的變更。

IOptionsMonitorIOptionsSnapshot 之間的差異在於:

  • IOptionsMonitor 是可隨時擷取目前選項值的 Singleton 服務,這一點在 Singleton 相依性方面特別有用。
  • IOptionsSnapshot有限範圍服務,會在建構 IOptionsSnapshot<T> 物件時提供選項的快照集。 選項快照集的設計目的是要與暫時性和有限範圍的相依性搭配使用。

下列程式碼會使用 IOptionsSnapshot<TOptions>

public class TestSnapModel : PageModel
{
    private readonly MyOptions _snapshotOptions;

    public TestSnapModel(IOptionsSnapshot<MyOptions> snapshotOptionsAccessor)
    {
        _snapshotOptions = snapshotOptionsAccessor.Value;
    }

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

下列程式碼會註冊 MyOptions 繫結的設定執行個體:

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<MyOptions>(
    builder.Configuration.GetSection("MyOptions"));

var app = builder.Build();

在上述程式碼中,會讀取在應用程式啟動之後對 JSON 設定檔所做的變更。

IOptionsMonitor

下列程式碼會註冊 MyOptions 繫結的設定執行個體。

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<MyOptions>(
    builder.Configuration.GetSection("MyOptions"));

var app = builder.Build();

下列範例會使用 IOptionsMonitor<TOptions>

public class TestMonitorModel : PageModel
{
    private readonly IOptionsMonitor<MyOptions> _optionsDelegate;

    public TestMonitorModel(IOptionsMonitor<MyOptions> optionsDelegate )
    {
        _optionsDelegate = optionsDelegate;
    }

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

在上述程式碼中,根據預設值,會讀取在應用程式啟動之後對 JSON 設定檔所做的變更。

使用 IConfigureNamedOptions 的具名選項支援

具名選項:

  • 當多個設定區段繫結至相同的屬性時會很有用。
  • 會區分大小寫。

請考量下列 appsettings.json 檔案:

{
  "TopItem": {
    "Month": {
      "Name": "Green Widget",
      "Model": "GW46"
    },
    "Year": {
      "Name": "Orange Gadget",
      "Model": "OG35"
    }
  }
}

不要建立兩個類別來繫結 TopItem:MonthTopItem:Year,而是為各個區段使用以下類別:

public class TopItemSettings
{
    public const string Month = "Month";
    public const string Year = "Year";

    public string Name { get; set; } = string.Empty;
    public string Model { get; set; } = string.Empty;
}

下列程式碼會設定具名選項:

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<TopItemSettings>(TopItemSettings.Month,
    builder.Configuration.GetSection("TopItem:Month"));
builder.Services.Configure<TopItemSettings>(TopItemSettings.Year,
    builder.Configuration.GetSection("TopItem:Year"));

var app = builder.Build();

下列程式碼會顯示具名選項:

public class TestNOModel : PageModel
{
    private readonly TopItemSettings _monthTopItem;
    private readonly TopItemSettings _yearTopItem;

    public TestNOModel(IOptionsSnapshot<TopItemSettings> namedOptionsAccessor)
    {
        _monthTopItem = namedOptionsAccessor.Get(TopItemSettings.Month);
        _yearTopItem = namedOptionsAccessor.Get(TopItemSettings.Year);
    }

    public ContentResult OnGet()
    {
        return Content($"Month:Name {_monthTopItem.Name} \n" +
                       $"Month:Model {_monthTopItem.Model} \n\n" +
                       $"Year:Name {_yearTopItem.Name} \n" +
                       $"Year:Model {_yearTopItem.Model} \n"   );
    }
}

所有選項都是具名執行個體。 IConfigureOptions<TOptions> 執行個體會視為以 Options.DefaultName 執行個體為目標,也就是 string.EmptyIConfigureNamedOptions<TOptions> 也會實作 IConfigureOptions<TOptions>IOptionsFactory<TOptions> 的預設實作有邏輯可以適當地使用每個項目。 null 具名選項用來以所有具名執行個體為目標,而不是特定的具名執行個體。 ConfigureAllPostConfigureAll 都使用了這個慣例。

OptionsBuilder API

OptionsBuilder<TOptions> 會用於設定 TOptions 執行個體。 因為 OptionsBuilder 僅為初始 AddOptions<TOptions>(string optionsName) 呼叫的單一參數,而不是出現在所有後續呼叫的參數,所以其可簡化建立具名選項的程序。 選項驗證及接受服務依存性的 ConfigureOptions 多載,只可透過 OptionsBuilder 使用。

選項驗證一節所使用的是 OptionsBuilder

如需新增自訂存放庫 的資訊,請參閱 使用 AddOptions 來設定自訂存放庫。

使用 DI 服務來設定選項

服務可以透過相依性插入存取,並且以兩種方式設定選項:

我們建議您將設定委派傳遞到 Configure,因為建立服務更複雜。 建立一個類型,相當於在呼叫 Configure 時架構所進行的動作。 呼叫 Configure 會註冊暫時性泛型 IConfigureNamedOptions<TOptions>,其具有會接受所指定泛型服務類型的建構函式。

選項驗證

選項驗證可驗證選項值。

請考量下列 appsettings.json 檔案:

{
  "MyConfig": {
    "Key1": "My Key One",
    "Key2": 10,
    "Key3": 32
  }
}

下列類別用於繫結至 "MyConfig" 設定區段並套用數個 DataAnnotations 規則:

public class MyConfigOptions
{
    public const string MyConfig = "MyConfig";

    [RegularExpression(@"^[a-zA-Z''-'\s]{1,40}$")]
    public string Key1 { get; set; }
    [Range(0, 1000,
        ErrorMessage = "Value for {0} must be between {1} and {2}.")]
    public int Key2 { get; set; }
    public int Key3 { get; set; }
}

下列程式碼範例:

using OptionsValidationSample.Configuration;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

builder.Services.AddOptions<MyConfigOptions>()
            .Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
            .ValidateDataAnnotations();

var app = builder.Build();

ValidateDataAnnotations 擴充方法定義在 Microsoft.Extensions.Options.DataAnnotations NuGet 套件中。 對於使用 Microsoft.NET.Sdk.Web SDK 的 Web 應用程式,此套件會從共用架構隱含參考。

下列程式碼會顯示設定值或驗證錯誤:

public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;
    private readonly IOptions<MyConfigOptions> _config;

    public HomeController(IOptions<MyConfigOptions> config,
                          ILogger<HomeController> logger)
    {
        _config = config;
        _logger = logger;

        try
        {
            var configValue = _config.Value;

        }
        catch (OptionsValidationException ex)
        {
            foreach (var failure in ex.Failures)
            {
                _logger.LogError(failure);
            }
        }
    }

    public ContentResult Index()
    {
        string msg;
        try
        {
            msg = $"Key1: {_config.Value.Key1} \n" +
                  $"Key2: {_config.Value.Key2} \n" +
                  $"Key3: {_config.Value.Key3}";
        }
        catch (OptionsValidationException optValEx)
        {
            return Content(optValEx.Message);
        }
        return Content(msg);
    }

下列程式碼會使用委派來套用更複雜的驗證規則:

using OptionsValidationSample.Configuration;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

builder.Services.AddOptions<MyConfigOptions>()
            .Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
            .ValidateDataAnnotations()
        .Validate(config =>
        {
            if (config.Key2 != 0)
            {
                return config.Key3 > config.Key2;
            }

            return true;
        }, "Key3 must be > than Key2.");   // Failure message.

var app = builder.Build();

IValidateOptions<TOptions>IValidatableObject

下列類別會實作 IValidateOptions<TOptions>

public class MyConfigValidation : IValidateOptions<MyConfigOptions>
{
    public MyConfigOptions _config { get; private set; }

    public  MyConfigValidation(IConfiguration config)
    {
        _config = config.GetSection(MyConfigOptions.MyConfig)
            .Get<MyConfigOptions>();
    }

    public ValidateOptionsResult Validate(string name, MyConfigOptions options)
    {
        string? vor = null;
        var rx = new Regex(@"^[a-zA-Z''-'\s]{1,40}$");
        var match = rx.Match(options.Key1!);

        if (string.IsNullOrEmpty(match.Value))
        {
            vor = $"{options.Key1} doesn't match RegEx \n";
        }

        if ( options.Key2 < 0 || options.Key2 > 1000)
        {
            vor = $"{options.Key2} doesn't match Range 0 - 1000 \n";
        }

        if (_config.Key2 != default)
        {
            if(_config.Key3 <= _config.Key2)
            {
                vor +=  "Key3 must be > than Key2.";
            }
        }

        if (vor != null)
        {
            return ValidateOptionsResult.Fail(vor);
        }

        return ValidateOptionsResult.Success;
    }
}

IValidateOptions 可讓您將驗證碼移出 Program.cs 並移至一個類別。

使用上述程式碼時,會使用下列程式碼在 Program.cs 中啟用驗證:

using Microsoft.Extensions.Options;
using OptionsValidationSample.Configuration;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

builder.Services.Configure<MyConfigOptions>(builder.Configuration.GetSection(
                                        MyConfigOptions.MyConfig));

builder.Services.AddSingleton<IValidateOptions
                              <MyConfigOptions>, MyConfigValidation>();

var app = builder.Build();

選項驗證也支援 IValidatableObject。 若要在類別本身執行類別的類別層級驗證:

ValidateOnStart

選項驗證會在第一次建立 IOptions<TOptions>IOptionsSnapshot<TOptions>IOptionsMonitor<TOptions> 實作時執行。 若渴望執行選項驗證,請在應用程式啟動時,在 Program.cs 中呼叫 ValidateOnStart

builder.Services.AddOptions<MyConfigOptions>()
    .Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
    .ValidateDataAnnotations()
    .ValidateOnStart();

選項設定後作業

使用 IPostConfigureOptions<TOptions> 來設定設定後作業。 設定後作業會在所有 IConfigureOptions<TOptions> 設定發生後執行:

using OptionsValidationSample.Configuration;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

builder.Services.AddOptions<MyConfigOptions>()
                .Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig));

builder.Services.PostConfigure<MyConfigOptions>(myOptions =>
{
    myOptions.Key1 = "post_configured_key1_value";
});

PostConfigure 可用來後置設定具名選項:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<TopItemSettings>(TopItemSettings.Month,
    builder.Configuration.GetSection("TopItem:Month"));
builder.Services.Configure<TopItemSettings>(TopItemSettings.Year,
    builder.Configuration.GetSection("TopItem:Year"));

builder.Services.PostConfigure<TopItemSettings>("Month", myOptions =>
{
    myOptions.Name = "post_configured_name_value";
    myOptions.Model = "post_configured_model_value";
});

var app = builder.Build();

使用 PostConfigureAll 後置設定所有設定執行個體:

using OptionsValidationSample.Configuration;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

builder.Services.AddOptions<MyConfigOptions>()
                .Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig));

builder.Services.PostConfigureAll<MyConfigOptions>(myOptions =>
{
    myOptions.Key1 = "post_configured_key1_value";
});

Program.cs 中的存取選項

若要存取 Program.cs 中的 IOptions<TOptions>IOptionsMonitor<TOptions>,請在 WebApplication.Services 上呼叫 GetRequiredService

var app = builder.Build();

var option1 = app.Services.GetRequiredService<IOptionsMonitor<MyOptions>>()
    .CurrentValue.Option1;

其他資源

作者:Kirk LarkinRick Anderson

選項模式會使用類別來提供相關設定群組的強型別存取。 當組態設定依案例隔離到不同的類別時,應用程式會遵守兩個重要的軟體工程準則:

  • 封裝
    • 相依於組態設定的類別只會相依於其使用的組態設定。
  • 關注點分離
    • 應用程式不同部分的設定不會彼此相依或結合。

選項也提供驗證設定資料的機制。 如需詳細資訊,請參閱選項驗證一節。

本文提供 ASP.NET Core 中的選項模式相關資訊。 如需在主控台應用程式中使用選項模式的詳細資訊,請參閱 .NET 中的選項模式

繫結階層式設定

讀取相關設定值的慣用方式是使用選項模式 (部分機器翻譯)。 例如,為讀取下列設定值:

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

建立下列 PositionOptions 類別:

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

    public string Title { get; set; } = String.Empty;
    public string Name { get; set; } = String.Empty;
}

選項類別:

  • 必須為非抽象,且具有公用的無參數建構函式。
  • 型別的所有公用讀取/寫入屬性都會繫結。
  • 欄位繫結。 在上述程式碼中,Position 未繫結: 會使用 Position 欄位,因此當您將類別繫結到設定提供者時,不需要以硬式編碼方式將字串 "Position" 放在應用程式中。

下列程式碼範例:

  • 呼叫 ConfigurationBinder.Bind 以將 PositionOptions 類別繫結到 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 還方便。 下列程式碼會示範如何搭配 PositionOptions 類別使用 ConfigurationBinder.Get<T>

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 設定檔所做的變更。

使用選項模式的替代方式是繫結 Position 區段並將其新增至相依性插入服務容器。 在下列程式碼中,PositionOptions 會使用 Configure 新增到服務容器,並繫結到設定:

using ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<PositionOptions>(
    builder.Configuration.GetSection(PositionOptions.Position));

var app = builder.Build();

使用上述程式碼,下列程式碼會讀取位置選項:

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

選項介面

IOptions<TOptions>

IOptionsSnapshot<TOptions>

IOptionsMonitor<TOptions>

設定後案例可讓您在所有 IConfigureOptions<TOptions> 設定發生時設定或變更選項。

IOptionsFactory<TOptions> 負責建立新的選項執行個體。 它有單一 Create 方法。 預設實作會接受所有已註冊的 IConfigureOptions<TOptions>IPostConfigureOptions<TOptions>,並先執行所有設定,接著執行設定後作業。 它會區別 IConfigureNamedOptions<TOptions>IConfigureOptions<TOptions>,且只會呼叫適當的介面。

IOptionsMonitorCache<TOptions> 會由 IOptionsMonitor<TOptions> 用來快取 TOptions 執行個體。 IOptionsMonitorCache<TOptions> 會使監視器中的選項執行個體失效,以便重新計算值 (TryRemove)。 值可以使用 TryAdd 手動導入。 Clear 方法用於應該視需要重新建立所有具名執行個體時。

使用 IOptionsSnapshot 讀取更新的資料

使用 IOptionsSnapshot<TOptions>

  • 在要求的存留期內存取及快取選項時,會針對每個要求計算一次選項。
  • 可能造成顯著的效能損失,因為這是有限範圍的服務,而且會依要求重新計算。 如需詳細資訊,請參閱此 GitHub 問題改善組態繫結的效能
  • 當使用支援讀取更新設定值的設定提供者時,會在應用程式啟動後讀取設定的變更。

IOptionsMonitorIOptionsSnapshot 之間的差異在於:

  • IOptionsMonitor 是可隨時擷取目前選項值的 Singleton 服務,這一點在 Singleton 相依性方面特別有用。
  • IOptionsSnapshot有限範圍服務,會在建構 IOptionsSnapshot<T> 物件時提供選項的快照集。 選項快照集的設計目的是要與暫時性和有限範圍的相依性搭配使用。

下列程式碼會使用 IOptionsSnapshot<TOptions>

public class TestSnapModel : PageModel
{
    private readonly MyOptions _snapshotOptions;

    public TestSnapModel(IOptionsSnapshot<MyOptions> snapshotOptionsAccessor)
    {
        _snapshotOptions = snapshotOptionsAccessor.Value;
    }

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

下列程式碼會註冊 MyOptions 繫結的設定執行個體:

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<MyOptions>(
    builder.Configuration.GetSection("MyOptions"));

var app = builder.Build();

在上述程式碼中,會讀取在應用程式啟動之後對 JSON 設定檔所做的變更。

IOptionsMonitor

下列程式碼會註冊 MyOptions 繫結的設定執行個體。

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<MyOptions>(
    builder.Configuration.GetSection("MyOptions"));

var app = builder.Build();

下列範例會使用 IOptionsMonitor<TOptions>

public class TestMonitorModel : PageModel
{
    private readonly IOptionsMonitor<MyOptions> _optionsDelegate;

    public TestMonitorModel(IOptionsMonitor<MyOptions> optionsDelegate )
    {
        _optionsDelegate = optionsDelegate;
    }

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

在上述程式碼中,根據預設值,會讀取在應用程式啟動之後對 JSON 設定檔所做的變更。

使用 IConfigureNamedOptions 的具名選項支援

具名選項:

  • 當多個設定區段繫結至相同的屬性時會很有用。
  • 會區分大小寫。

請考量下列 appsettings.json 檔案:

{
  "TopItem": {
    "Month": {
      "Name": "Green Widget",
      "Model": "GW46"
    },
    "Year": {
      "Name": "Orange Gadget",
      "Model": "OG35"
    }
  }
}

不要建立兩個類別來繫結 TopItem:MonthTopItem:Year,而是為各個區段使用以下類別:

public class TopItemSettings
{
    public const string Month = "Month";
    public const string Year = "Year";

    public string Name { get; set; } = string.Empty;
    public string Model { get; set; } = string.Empty;
}

下列程式碼會設定具名選項:

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<TopItemSettings>(TopItemSettings.Month,
    builder.Configuration.GetSection("TopItem:Month"));
builder.Services.Configure<TopItemSettings>(TopItemSettings.Year,
    builder.Configuration.GetSection("TopItem:Year"));

var app = builder.Build();

下列程式碼會顯示具名選項:

public class TestNOModel : PageModel
{
    private readonly TopItemSettings _monthTopItem;
    private readonly TopItemSettings _yearTopItem;

    public TestNOModel(IOptionsSnapshot<TopItemSettings> namedOptionsAccessor)
    {
        _monthTopItem = namedOptionsAccessor.Get(TopItemSettings.Month);
        _yearTopItem = namedOptionsAccessor.Get(TopItemSettings.Year);
    }

    public ContentResult OnGet()
    {
        return Content($"Month:Name {_monthTopItem.Name} \n" +
                       $"Month:Model {_monthTopItem.Model} \n\n" +
                       $"Year:Name {_yearTopItem.Name} \n" +
                       $"Year:Model {_yearTopItem.Model} \n"   );
    }
}

所有選項都是具名執行個體。 IConfigureOptions<TOptions> 執行個體會視為以 Options.DefaultName 執行個體為目標,也就是 string.EmptyIConfigureNamedOptions<TOptions> 也會實作 IConfigureOptions<TOptions>IOptionsFactory<TOptions> 的預設實作有邏輯可以適當地使用每個項目。 null 具名選項用來以所有具名執行個體為目標,而不是特定的具名執行個體。 ConfigureAllPostConfigureAll 都使用了這個慣例。

OptionsBuilder API

OptionsBuilder<TOptions> 會用於設定 TOptions 執行個體。 因為 OptionsBuilder 僅為初始 AddOptions<TOptions>(string optionsName) 呼叫的單一參數,而不是出現在所有後續呼叫的參數,所以其可簡化建立具名選項的程序。 選項驗證及接受服務依存性的 ConfigureOptions 多載,只可透過 OptionsBuilder 使用。

選項驗證一節所使用的是 OptionsBuilder

如需新增自訂存放庫 的資訊,請參閱 使用 AddOptions 來設定自訂存放庫。

使用 DI 服務來設定選項

服務可以透過相依性插入存取,並且以兩種方式設定選項:

我們建議您將設定委派傳遞到 Configure,因為建立服務更複雜。 建立一個類型,相當於在呼叫 Configure 時架構所進行的動作。 呼叫 Configure 會註冊暫時性泛型 IConfigureNamedOptions<TOptions>,其具有會接受所指定泛型服務類型的建構函式。

選項驗證

選項驗證可驗證選項值。

請考量下列 appsettings.json 檔案:

{
  "MyConfig": {
    "Key1": "My Key One",
    "Key2": 10,
    "Key3": 32
  }
}

下列類別用於繫結至 "MyConfig" 設定區段並套用數個 DataAnnotations 規則:

public class MyConfigOptions
{
    public const string MyConfig = "MyConfig";

    [RegularExpression(@"^[a-zA-Z''-'\s]{1,40}$")]
    public string Key1 { get; set; }
    [Range(0, 1000,
        ErrorMessage = "Value for {0} must be between {1} and {2}.")]
    public int Key2 { get; set; }
    public int Key3 { get; set; }
}

下列程式碼範例:

using OptionsValidationSample.Configuration;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

builder.Services.AddOptions<MyConfigOptions>()
            .Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
            .ValidateDataAnnotations();

var app = builder.Build();

ValidateDataAnnotations 擴充方法定義在 Microsoft.Extensions.Options.DataAnnotations NuGet 套件中。 對於使用 Microsoft.NET.Sdk.Web SDK 的 Web 應用程式,此套件會從共用架構隱含參考。

下列程式碼會顯示設定值或驗證錯誤:

public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;
    private readonly IOptions<MyConfigOptions> _config;

    public HomeController(IOptions<MyConfigOptions> config,
                          ILogger<HomeController> logger)
    {
        _config = config;
        _logger = logger;

        try
        {
            var configValue = _config.Value;

        }
        catch (OptionsValidationException ex)
        {
            foreach (var failure in ex.Failures)
            {
                _logger.LogError(failure);
            }
        }
    }

    public ContentResult Index()
    {
        string msg;
        try
        {
            msg = $"Key1: {_config.Value.Key1} \n" +
                  $"Key2: {_config.Value.Key2} \n" +
                  $"Key3: {_config.Value.Key3}";
        }
        catch (OptionsValidationException optValEx)
        {
            return Content(optValEx.Message);
        }
        return Content(msg);
    }

下列程式碼會使用委派來套用更複雜的驗證規則:

using OptionsValidationSample.Configuration;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

builder.Services.AddOptions<MyConfigOptions>()
            .Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
            .ValidateDataAnnotations()
        .Validate(config =>
        {
            if (config.Key2 != 0)
            {
                return config.Key3 > config.Key2;
            }

            return true;
        }, "Key3 must be > than Key2.");   // Failure message.

var app = builder.Build();

IValidateOptions<TOptions>IValidatableObject

下列類別會實作 IValidateOptions<TOptions>

public class MyConfigValidation : IValidateOptions<MyConfigOptions>
{
    public MyConfigOptions _config { get; private set; }

    public  MyConfigValidation(IConfiguration config)
    {
        _config = config.GetSection(MyConfigOptions.MyConfig)
            .Get<MyConfigOptions>();
    }

    public ValidateOptionsResult Validate(string name, MyConfigOptions options)
    {
        string? vor = null;
        var rx = new Regex(@"^[a-zA-Z''-'\s]{1,40}$");
        var match = rx.Match(options.Key1!);

        if (string.IsNullOrEmpty(match.Value))
        {
            vor = $"{options.Key1} doesn't match RegEx \n";
        }

        if ( options.Key2 < 0 || options.Key2 > 1000)
        {
            vor = $"{options.Key2} doesn't match Range 0 - 1000 \n";
        }

        if (_config.Key2 != default)
        {
            if(_config.Key3 <= _config.Key2)
            {
                vor +=  "Key3 must be > than Key2.";
            }
        }

        if (vor != null)
        {
            return ValidateOptionsResult.Fail(vor);
        }

        return ValidateOptionsResult.Success;
    }
}

IValidateOptions 可讓您將驗證碼移出 Program.cs 並移至一個類別。

使用上述程式碼時,會使用下列程式碼在 Program.cs 中啟用驗證:

using Microsoft.Extensions.Options;
using OptionsValidationSample.Configuration;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

builder.Services.Configure<MyConfigOptions>(builder.Configuration.GetSection(
                                        MyConfigOptions.MyConfig));

builder.Services.AddSingleton<IValidateOptions
                              <MyConfigOptions>, MyConfigValidation>();

var app = builder.Build();

選項驗證也支援 IValidatableObject。 若要在類別本身執行類別的類別層級驗證:

ValidateOnStart

選項驗證會在第一次建立 IOptions<TOptions>IOptionsSnapshot<TOptions>IOptionsMonitor<TOptions> 實作時執行。 若渴望執行選項驗證,請在應用程式啟動時,在 Program.cs 中呼叫 ValidateOnStart

builder.Services.AddOptions<MyConfigOptions>()
    .Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
    .ValidateDataAnnotations()
    .ValidateOnStart();

選項設定後作業

使用 IPostConfigureOptions<TOptions> 來設定設定後作業。 設定後作業會在所有 IConfigureOptions<TOptions> 設定發生後執行:

using OptionsValidationSample.Configuration;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

builder.Services.AddOptions<MyConfigOptions>()
                .Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig));

builder.Services.PostConfigure<MyConfigOptions>(myOptions =>
{
    myOptions.Key1 = "post_configured_key1_value";
});

PostConfigure 可用來後置設定具名選項:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<TopItemSettings>(TopItemSettings.Month,
    builder.Configuration.GetSection("TopItem:Month"));
builder.Services.Configure<TopItemSettings>(TopItemSettings.Year,
    builder.Configuration.GetSection("TopItem:Year"));

builder.Services.PostConfigure<TopItemSettings>("Month", myOptions =>
{
    myOptions.Name = "post_configured_name_value";
    myOptions.Model = "post_configured_model_value";
});

var app = builder.Build();

使用 PostConfigureAll 後置設定所有設定執行個體:

using OptionsValidationSample.Configuration;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

builder.Services.AddOptions<MyConfigOptions>()
                .Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig));

builder.Services.PostConfigureAll<MyConfigOptions>(myOptions =>
{
    myOptions.Key1 = "post_configured_key1_value";
});

Program.cs 中的存取選項

若要存取 Program.cs 中的 IOptions<TOptions>IOptionsMonitor<TOptions>,請在 WebApplication.Services 上呼叫 GetRequiredService

var app = builder.Build();

var option1 = app.Services.GetRequiredService<IOptionsMonitor<MyOptions>>()
    .CurrentValue.Option1;

其他資源

作者:Kirk LarkinRick Anderson

選項模式會使用類別來提供相關設定群組的強型別存取。 當組態設定依案例隔離到不同的類別時,應用程式會遵守兩個重要的軟體工程準則:

  • 封裝
    • 相依於組態設定的類別只會相依於其使用的組態設定。
  • 關注點分離
    • 應用程式不同部分的設定不會彼此相依或結合。

選項也提供驗證設定資料的機制。 如需詳細資訊,請參閱選項驗證一節。

本主題提供 ASP.NET Core 中的選項模式相關資訊。 如需在主控台應用程式中使用選項模式的詳細資訊,請參閱 .NET 中的選項模式

檢視或下載範例程式碼 \(英文\) (如何下載)

繫結階層式設定

讀取相關設定值的慣用方式是使用選項模式 (部分機器翻譯)。 例如,為讀取下列設定值:

  "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" 放在應用程式中。

下列程式碼範例:

  • 呼叫 ConfigurationBinder.Bind 以將 PositionOptions 類別繫結到 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 還方便。 下列程式碼會示範如何搭配 PositionOptions 類別使用 ConfigurationBinder.Get<T>

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 設定檔所做的變更。

使用選項模式的替代方式是繫結 Position 區段並將其新增至相依性插入服務容器。 在下列程式碼中,PositionOptions 會使用 Configure 新增到服務容器,並繫結到設定:

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

使用上述程式碼,下列程式碼會讀取位置選項:

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

選項介面

IOptions<TOptions>

IOptionsSnapshot<TOptions>

IOptionsMonitor<TOptions>

設定後案例可讓您在所有 IConfigureOptions<TOptions> 設定發生時設定或變更選項。

IOptionsFactory<TOptions> 負責建立新的選項執行個體。 它有單一 Create 方法。 預設實作會接受所有已註冊的 IConfigureOptions<TOptions>IPostConfigureOptions<TOptions>,並先執行所有設定,接著執行設定後作業。 它會區別 IConfigureNamedOptions<TOptions>IConfigureOptions<TOptions>,且只會呼叫適當的介面。

IOptionsMonitorCache<TOptions> 會由 IOptionsMonitor<TOptions> 用來快取 TOptions 執行個體。 IOptionsMonitorCache<TOptions> 會使監視器中的選項執行個體失效,以便重新計算值 (TryRemove)。 值可以使用 TryAdd 手動導入。 Clear 方法用於應該視需要重新建立所有具名執行個體時。

使用 IOptionsSnapshot 讀取更新的資料

使用 IOptionsSnapshot<TOptions>,在要求的存留期內存取及快取選項時,會針對每個要求計算一次選項。 當使用支援讀取更新設定值的設定提供者時,會在應用程式啟動後讀取設定的變更。

IOptionsMonitorIOptionsSnapshot 之間的差異在於:

  • IOptionsMonitor 是可隨時擷取目前選項值的 Singleton 服務,這一點在 Singleton 相依性方面特別有用。
  • IOptionsSnapshot有限範圍服務,會在建構 IOptionsSnapshot<T> 物件時提供選項的快照集。 選項快照集的設計目的是要與暫時性和有限範圍的相依性搭配使用。

下列程式碼會使用 IOptionsSnapshot<TOptions>

public class TestSnapModel : PageModel
{
    private readonly MyOptions _snapshotOptions;

    public TestSnapModel(IOptionsSnapshot<MyOptions> snapshotOptionsAccessor)
    {
        _snapshotOptions = snapshotOptionsAccessor.Value;
    }

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

下列程式碼會註冊 MyOptions 繫結的設定執行個體:

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

    services.AddRazorPages();
}

在上述程式碼中,會讀取在應用程式啟動之後對 JSON 設定檔所做的變更。

IOptionsMonitor

下列程式碼會註冊 MyOptions 繫結的設定執行個體。

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

    services.AddRazorPages();
}

下列範例會使用 IOptionsMonitor<TOptions>

public class TestMonitorModel : PageModel
{
    private readonly IOptionsMonitor<MyOptions> _optionsDelegate;

    public TestMonitorModel(IOptionsMonitor<MyOptions> optionsDelegate )
    {
        _optionsDelegate = optionsDelegate;
    }

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

在上述程式碼中,根據預設值,會讀取在應用程式啟動之後對 JSON 設定檔所做的變更。

使用 IConfigureNamedOptions 的具名選項支援

具名選項:

  • 當多個設定區段繫結至相同的屬性時會很有用。
  • 會區分大小寫。

請考量下列 appsettings.json 檔案:

{
  "TopItem": {
    "Month": {
      "Name": "Green Widget",
      "Model": "GW46"
    },
    "Year": {
      "Name": "Orange Gadget",
      "Model": "OG35"
    }
  }
}

不要建立兩個類別來繫結 TopItem:MonthTopItem:Year,而是為各個區段使用以下類別:

public class TopItemSettings
{
    public const string Month = "Month";
    public const string Year = "Year";

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

下列程式碼會設定具名選項:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<TopItemSettings>(TopItemSettings.Month,
                                       Configuration.GetSection("TopItem:Month"));
    services.Configure<TopItemSettings>(TopItemSettings.Year,
                                        Configuration.GetSection("TopItem:Year"));

    services.AddRazorPages();
}

下列程式碼會顯示具名選項:

public class TestNOModel : PageModel
{
    private readonly TopItemSettings _monthTopItem;
    private readonly TopItemSettings _yearTopItem;

    public TestNOModel(IOptionsSnapshot<TopItemSettings> namedOptionsAccessor)
    {
        _monthTopItem = namedOptionsAccessor.Get(TopItemSettings.Month);
        _yearTopItem = namedOptionsAccessor.Get(TopItemSettings.Year);
    }

    public ContentResult OnGet()
    {
        return Content($"Month:Name {_monthTopItem.Name} \n" +
                       $"Month:Model {_monthTopItem.Model} \n\n" +
                       $"Year:Name {_yearTopItem.Name} \n" +
                       $"Year:Model {_yearTopItem.Model} \n"   );
    }
}

所有選項都是具名執行個體。 IConfigureOptions<TOptions> 執行個體會視為以 Options.DefaultName 執行個體為目標,也就是 string.EmptyIConfigureNamedOptions<TOptions> 也會實作 IConfigureOptions<TOptions>IOptionsFactory<TOptions> 的預設實作有邏輯可以適當地使用每個項目。 null 具名選項用來以所有具名執行個體為目標,而不是特定的具名執行個體。 ConfigureAllPostConfigureAll 都使用了這個慣例。

OptionsBuilder API

OptionsBuilder<TOptions> 會用於設定 TOptions 執行個體。 因為 OptionsBuilder 僅為初始 AddOptions<TOptions>(string optionsName) 呼叫的單一參數,而不是出現在所有後續呼叫的參數,所以其可簡化建立具名選項的程序。 選項驗證及接受服務依存性的 ConfigureOptions 多載,只可透過 OptionsBuilder 使用。

選項驗證一節所使用的是 OptionsBuilder

如需新增自訂存放庫 的資訊,請參閱 使用 AddOptions 來設定自訂存放庫。

使用 DI 服務來設定選項

服務可以透過相依性插入存取,並且以兩種方式設定選項:

我們建議您將設定委派傳遞到 Configure,因為建立服務更複雜。 建立一個類型,相當於在呼叫 Configure 時架構所進行的動作。 呼叫 Configure 會註冊暫時性泛型 IConfigureNamedOptions<TOptions>,其具有會接受所指定泛型服務類型的建構函式。

選項驗證

選項驗證可驗證選項值。

請考量下列 appsettings.json 檔案:

{
  "MyConfig": {
    "Key1": "My Key One",
    "Key2": 10,
    "Key3": 32
  }
}

下列類別會繫結至 "MyConfig" 設定區段並套用數個 DataAnnotations 規則:

public class MyConfigOptions
{
    public const string MyConfig = "MyConfig";

    [RegularExpression(@"^[a-zA-Z''-'\s]{1,40}$")]
    public string Key1 { get; set; }
    [Range(0, 1000,
        ErrorMessage = "Value for {0} must be between {1} and {2}.")]
    public int Key2 { get; set; }
    public int Key3 { get; set; }
}

下列程式碼範例:

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

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddOptions<MyConfigOptions>()
            .Bind(Configuration.GetSection(MyConfigOptions.MyConfig))
            .ValidateDataAnnotations();

        services.AddControllersWithViews();
    }

ValidateDataAnnotations 擴充方法定義在 Microsoft.Extensions.Options.DataAnnotations NuGet 套件中。 對於使用 Microsoft.NET.Sdk.Web SDK 的 Web 應用程式,此套件會從共用架構隱含參考。

下列程式碼會顯示設定值或驗證錯誤:

public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;
    private readonly IOptions<MyConfigOptions> _config;

    public HomeController(IOptions<MyConfigOptions> config,
                          ILogger<HomeController> logger)
    {
        _config = config;
        _logger = logger;

        try
        {
            var configValue = _config.Value;

        }
        catch (OptionsValidationException ex)
        {
            foreach (var failure in ex.Failures)
            {
                _logger.LogError(failure);
            }
        }
    }

    public ContentResult Index()
    {
        string msg;
        try
        {
             msg = $"Key1: {_config.Value.Key1} \n" +
                   $"Key2: {_config.Value.Key2} \n" +
                   $"Key3: {_config.Value.Key3}";
        }
        catch (OptionsValidationException optValEx)
        {
            return Content(optValEx.Message);
        }
        return Content(msg);
    }

下列程式碼會使用委派來套用更複雜的驗證規則:

public void ConfigureServices(IServiceCollection services)
{
    services.AddOptions<MyConfigOptions>()
        .Bind(Configuration.GetSection(MyConfigOptions.MyConfig))
        .ValidateDataAnnotations()
        .Validate(config =>
        {
            if (config.Key2 != 0)
            {
                return config.Key3 > config.Key2;
            }

            return true;
        }, "Key3 must be > than Key2.");   // Failure message.

    services.AddControllersWithViews();
}

用於複雜驗證的 IValidateOptions

下列類別會實作 IValidateOptions<TOptions>

public class MyConfigValidation : IValidateOptions<MyConfigOptions>
{
    public MyConfigOptions _config { get; private set; }

    public  MyConfigValidation(IConfiguration config)
    {
        _config = config.GetSection(MyConfigOptions.MyConfig)
            .Get<MyConfigOptions>();
    }

    public ValidateOptionsResult Validate(string name, MyConfigOptions options)
    {
        string vor=null;
        var rx = new Regex(@"^[a-zA-Z''-'\s]{1,40}$");
        var match = rx.Match(options.Key1);

        if (string.IsNullOrEmpty(match.Value))
        {
            vor = $"{options.Key1} doesn't match RegEx \n";
        }

        if ( options.Key2 < 0 || options.Key2 > 1000)
        {
            vor = $"{options.Key2} doesn't match Range 0 - 1000 \n";
        }

        if (_config.Key2 != default)
        {
            if(_config.Key3 <= _config.Key2)
            {
                vor +=  "Key3 must be > than Key2.";
            }
        }

        if (vor != null)
        {
            return ValidateOptionsResult.Fail(vor);
        }

        return ValidateOptionsResult.Success;
    }
}

IValidateOptions 可讓您將驗證碼移出 StartUp 並移至一個類別。

使用上述程式碼時,會使用下列程式碼在 Startup.ConfigureServices 中啟用驗證:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<MyConfigOptions>(Configuration.GetSection(
                                        MyConfigOptions.MyConfig));
    services.TryAddEnumerable(ServiceDescriptor.Singleton<IValidateOptions
                              <MyConfigOptions>, MyConfigValidation>());
    services.AddControllersWithViews();
}

選項設定後作業

使用 IPostConfigureOptions<TOptions> 來設定設定後作業。 設定後作業會在所有 IConfigureOptions<TOptions> 設定發生後執行:

services.PostConfigure<MyOptions>(myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

PostConfigure 可用來後置設定具名選項:

services.PostConfigure<MyOptions>("named_options_1", myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

使用 PostConfigureAll 後置設定所有設定執行個體:

services.PostConfigureAll<MyOptions>(myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

在啟動期間存取選項

IOptions<TOptions>IOptionsMonitor<TOptions> 可用於 Startup.Configure,因為服務是在 Configure 方法執行之前建置。

public void Configure(IApplicationBuilder app, 
    IOptionsMonitor<MyOptions> optionsAccessor)
{
    var option1 = optionsAccessor.CurrentValue.Option1;
}

請勿在 Startup.ConfigureServices 中使用 IOptions<TOptions>IOptionsMonitor<TOptions>。 可能會因為服務註冊的順序而有不一致的選項狀態存在。

Options.ConfigurationExtensions NuGet 套件

Microsoft.Extensions.Options.ConfigurationExtensions 套件會在 ASP.NET Core 應用程式中隱含參考。