Pola opsi ASP.NET Core

Catatan

Ini bukan versi terbaru dari artikel ini. Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Penting

Informasi ini berkaitan dengan produk pra-rilis yang mungkin dimodifikasi secara substansial sebelum dirilis secara komersial. Microsoft tidak memberikan jaminan, tersirat maupun tersurat, sehubungan dengan informasi yang diberikan di sini.

Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Oleh Rick Anderson.

Pola opsi menggunakan kelas untuk menyediakan akses yang sangat diketik ke grup pengaturan terkait. Saat pengaturan konfigurasi diisolasi oleh skenario ke dalam kelas terpisah, aplikasi mematuhi dua prinsip rekayasa perangkat lunak penting:

  • Enkapulasi:
    • Kelas yang bergantung pada pengaturan konfigurasi hanya bergantung pada pengaturan konfigurasi yang mereka gunakan.
  • Pemisahan Kekhawatiran:
    • Pengaturan untuk berbagai bagian aplikasi tidak bergantung atau digabungkan satu sama lain.

Opsi juga menyediakan mekanisme untuk memvalidasi data konfigurasi. Untuk informasi selengkapnya, lihat bagian Validasi opsi.

Artikel ini menyediakan informasi tentang pola opsi di ASP.NET Core. Untuk informasi tentang menggunakan pola opsi di aplikasi konsol, lihat Pola opsi di .NET.

Mengikat konfigurasi hierarkis

Cara yang lebih disukai untuk membaca nilai konfigurasi terkait adalah menggunakan pola opsi. Misalnya, untuk membaca nilai konfigurasi berikut:

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

Buat kelas PositionOptions berikut:

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

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

Kelas opsi:

  • Harus non-abstrak.
  • Memiliki properti baca-tulis publik dari jenis yang memiliki item yang sesuai dalam konfigurasi terikat.
  • Memiliki properti baca-tulis yang terikat dengan entri yang cocok dalam konfigurasi.
  • Tidak memiliki bidang yang terikat. Dalam kode sebelumnya, Position tidak terikat. Bidang Position digunakan sehingga string "Position" tidak perlu dikodekan secara permanen di aplikasi saat mengikat kelas ke penyedia konfigurasi.

Kode berikut:

  • Memanggil ConfigurationBinder.Bind untuk mengikat kelas PositionOptions ke bagian Position.
  • Menampilkan data konfigurasi 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}");
    }
}

Dalam kode sebelumnya, secara default, perubahan pada file konfigurasi JSON setelah aplikasi dimulai dibaca.

ConfigurationBinder.Get<T> mengikat dan mengembalikan jenis yang ditentukan. ConfigurationBinder.Get<T> mungkin lebih nyaman daripada menggunakan ConfigurationBinder.Bind. Contoh kode berikut menunjukkan cara menggunakan ConfigurationBinder.Get<T> dengan kelas 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}");
    }
}

Dalam kode sebelumnya, secara default, perubahan pada file konfigurasi JSON setelah aplikasi dimulai dibaca.

Ikatan juga memungkinkan kesimpulan kelas abstrak. Pertimbangkan kode berikut yang menggunakan kelas SomethingWithANameabstrak :

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

Kode berikut menampilkan NameTitleOptions nilai konfigurasi:

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

Panggilan ke Bind kurang ketat daripada panggilan ke Get<>:

  • Bind memungkinkan kesimpulan abstrak.
  • Get<> harus membuat instans itu sendiri.

Pola Opsi

Pendekatan alternatif saat menggunakan pola opsi adalah untuk mengikat bagian Position dan menambahkannya ke kontainer layanan injeksi dependensi. Dalam kode berikut, PositionOptions ditambahkan ke kontainer layanan dengan Configure dan terikat ke konfigurasi:

using ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

var app = builder.Build();

Dengan menggunakan kode sebelumnya, kode berikut membaca opsi posisi:

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

Dalam kode sebelumnya, perubahan pada file konfigurasi JSON setelah aplikasi dimulai tidak dibaca. Untuk membaca perubahan setelah aplikasi dimulai, gunakan IOptionsSnapshot.

Antarmuka opsi

IOptions<TOptions>:

IOptionsSnapshot<TOptions>:

IOptionsMonitor<TOptions>:

Skenario pasca-konfigurasi mengaktifkan pengaturan atau mengubah opsi setelah semua IConfigureOptions<TOptions> konfigurasi terjadi.

IOptionsFactory<TOptions> bertanggung jawab untuk membuat instans opsi baru. Ini memiliki satu Create metode. Implementasi default mengambil semua yang terdaftar IConfigureOptions<TOptions> dan IPostConfigureOptions<TOptions> dan menjalankan semua konfigurasi terlebih dahulu, diikuti oleh pasca-konfigurasi. Ini membedakan antara IConfigureNamedOptions<TOptions> dan IConfigureOptions<TOptions> dan hanya memanggil antarmuka yang sesuai.

IOptionsMonitorCache<TOptions> digunakan oleh IOptionsMonitor<TOptions> untuk menyimpan TOptions instans. IOptionsMonitorCache<TOptions> Instans opsi yang tidak valid di monitor sehingga nilai dikomputasi ulang (TryRemove). Nilai dapat diperkenalkan secara manual dengan TryAdd. Metode Clear ini digunakan ketika semua instans bernama harus dibuat ulang sesuai permintaan.

Menggunakan IOptionsSnapshot untuk membaca data yang diperbarui

Menggunakan IOptionsSnapshot<TOptions>:

  • Opsi dihitung sekali per permintaan saat diakses dan di-cache selama masa pakai permintaan.
  • Dapat dikenakan penalti performa yang signifikan karena merupakan layanan Cakupan dan dikomputasi ulang per permintaan. Untuk informasi selengkapnya, lihat masalah GitHub ini dan Meningkatkan performa pengikatan konfigurasi.
  • Perubahan pada konfigurasi dibaca setelah aplikasi dimulai saat menggunakan penyedia konfigurasi yang mendukung pembacaan nilai konfigurasi yang diperbarui.

Perbedaan antara IOptionsMonitor dan IOptionsSnapshot adalah bahwa:

  • IOptionsMonitor adalah layanan Singleton yang mengambil nilai opsi saat ini kapan saja, yang sangat berguna dalam dependensi singleton.
  • IOptionsSnapshot adalah layanan Cakupan dan menyediakan rekam jepret opsi pada saat IOptionsSnapshot<T> objek dibuat. Rekam jepret opsi dirancang untuk digunakan dengan dependensi sementara dan terlingkup.

Kode berikut menggunakan 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}");
    }
}

Kode berikut mendaftarkan instans konfigurasi yang MyOptions mengikat terhadap:

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

var app = builder.Build();

Dalam kode sebelumnya, perubahan pada JSfile konfigurasi ON setelah aplikasi dimulai dibaca.

IOptionsMonitor

Kode berikut mendaftarkan instans konfigurasi yang MyOptions mengikat.

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

var app = builder.Build();

Contoh berikut menggunakan 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}");
    }
}

Dalam kode sebelumnya, secara default, perubahan pada file konfigurasi JSON setelah aplikasi dimulai dibaca.

Dukungan opsi bernama menggunakan IConfigureNamedOptions

Opsi bernama:

  • Berguna saat beberapa bagian konfigurasi mengikat properti yang sama.
  • Peka huruf besar/kecil.

Pertimbangkan file appsettings.json berikut:

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

Daripada membuat dua kelas untuk mengikat TopItem:Month dan TopItem:Year, kelas berikut digunakan untuk setiap bagian:

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

Kode berikut mengonfigurasi opsi bernama:

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

Kode berikut menampilkan opsi bernama:

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

Semua opsi diberi nama instans. IConfigureOptions<TOptions> instans diperlakukan sebagai menargetkan Options.DefaultName instans, yaitu string.Empty. IConfigureNamedOptions<TOptions> juga mengimplementasikan IConfigureOptions<TOptions>. Implementasi default dari IOptionsFactory<TOptions> memiliki logika untuk menggunakan masing-masing dengan tepat. null Opsi bernama digunakan untuk menargetkan semua instans bernama alih-alih instans bernama tertentu. ConfigureAll dan PostConfigureAll gunakan konvensi ini.

OptionsBuilder API

OptionsBuilder<TOptions> digunakan untuk mengonfigurasi TOptions instans. OptionsBuilder menyederhanakan pembuatan opsi bernama karena hanya satu parameter ke panggilan awal AddOptions<TOptions>(string optionsName) alih-alih muncul di semua panggilan berikutnya. Validasi opsi dan ConfigureOptions kelebihan beban yang menerima dependensi layanan hanya tersedia melalui OptionsBuilder.

OptionsBuilder digunakan di bagian Validasi opsi.

Lihat Menggunakan AddOptions untuk mengonfigurasi repositori kustom untuk informasi yang menambahkan repositori kustom.

Menggunakan layanan DI untuk mengonfigurasi opsi

Layanan dapat diakses dari injeksi dependensi sambil mengonfigurasi opsi dengan dua cara:

  • Teruskan delegasi konfigurasi ke Configure pada OptionsBuilder<TOptions>. OptionsBuilder<TOptions> menyediakan kelebihan beban Configure yang memungkinkan penggunaan hingga lima layanan untuk mengonfigurasi opsi:

    builder.Services.AddOptions<MyOptions>("optionalName")
        .Configure<Service1, Service2, Service3, Service4, Service5>(
            (o, s, s2, s3, s4, s5) => 
                o.Property = DoSomethingWith(s, s2, s3, s4, s5));
    
  • Buat jenis yang mengimplementasikan IConfigureOptions<TOptions> atau IConfigureNamedOptions<TOptions> dan mendaftarkan jenis sebagai layanan.

Sebaiknya teruskan delegasi konfigurasi ke Configure, karena membuat layanan lebih kompleks. Membuat jenis setara dengan apa yang dilakukan kerangka kerja saat memanggil Configure. Configure Panggilan mendaftarkan generik IConfigureNamedOptions<TOptions>sementara , yang memiliki konstruktor yang menerima jenis layanan generik yang ditentukan.

Validasi opsi

Validasi opsi memungkinkan nilai opsi divalidasi.

Pertimbangkan file appsettings.json berikut:

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

Kelas berikut digunakan untuk mengikat ke bagian "MyConfig" konfigurasi dan menerapkan beberapa DataAnnotations aturan:

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

Kode berikut:

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

Metode ValidateDataAnnotations ekstensi ditentukan dalam paket NuGet Microsoft.Extensions.Options.DataAnnotations . Untuk aplikasi web yang menggunakan Microsoft.NET.Sdk.Web SDK, paket ini dirujuk secara implisit dari kerangka kerja bersama.

Kode berikut menampilkan nilai konfigurasi atau kesalahan validasi:

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

Kode berikut menerapkan aturan validasi yang lebih kompleks menggunakan delegasi:

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> dan IValidatableObject

Kelas berikut mengimplementasikan 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 memungkinkan pemindahan kode validasi keluar dari Program.cs dan ke kelas.

Menggunakan kode sebelumnya, validasi diaktifkan dengan Program.cs kode berikut:

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

Validasi opsi juga mendukung IValidatableObject. Untuk melakukan validasi tingkat kelas kelas dalam kelas itu sendiri:

ValidateOnStart

Validasi opsi berjalan saat pertama kali IOptions<TOptions>implementasi , , IOptionsSnapshot<TOptions>atau IOptionsMonitor<TOptions> dibuat. Untuk menjalankan validasi opsi dengan bersemangat, saat aplikasi dimulai, panggil ValidateOnStart di Program.cs:

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

Opsi pasca-konfigurasi

Atur pasca-konfigurasi dengan IPostConfigureOptions<TOptions>. Pasca-konfigurasi berjalan setelah semua IConfigureOptions<TOptions> konfigurasi terjadi:

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 tersedia untuk pasca-konfigurasi opsi bernama:

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

Gunakan PostConfigureAll untuk mengonfigurasi semua instans konfigurasi pasca-konfigurasi:

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

Opsi akses di Program.cs

Untuk mengakses IOptions<TOptions> atau IOptionsMonitor<TOptions> di Program.cs, panggil GetRequiredService di WebApplication.Services:

var app = builder.Build();

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

Sumber Daya Tambahan:

Oleh Kirk Larkin dan Rick Anderson.

Pola opsi menggunakan kelas untuk menyediakan akses yang sangat diketik ke grup pengaturan terkait. Saat pengaturan konfigurasi diisolasi oleh skenario ke dalam kelas terpisah, aplikasi mematuhi dua prinsip rekayasa perangkat lunak penting:

  • Enkapulasi:
    • Kelas yang bergantung pada pengaturan konfigurasi hanya bergantung pada pengaturan konfigurasi yang mereka gunakan.
  • Pemisahan Kekhawatiran:
    • Pengaturan untuk berbagai bagian aplikasi tidak bergantung atau digabungkan satu sama lain.

Opsi juga menyediakan mekanisme untuk memvalidasi data konfigurasi. Untuk informasi selengkapnya, lihat bagian Validasi opsi.

Artikel ini menyediakan informasi tentang pola opsi di ASP.NET Core. Untuk informasi tentang menggunakan pola opsi di aplikasi konsol, lihat Pola opsi di .NET.

Mengikat konfigurasi hierarkis

Cara yang lebih disukai untuk membaca nilai konfigurasi terkait adalah menggunakan pola opsi. Misalnya, untuk membaca nilai konfigurasi berikut:

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

Buat kelas PositionOptions berikut:

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

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

Kelas opsi:

  • Harus nonabstrak dengan konstruktor tanpa parameter publik.
  • Semua properti baca-tulis publik dari jenis ini terikat.
  • Bidang tidak terikat. Dalam kode sebelumnya, Position tidak terikat. Bidang Position digunakan sehingga string "Position" tidak perlu dikodekan secara permanen di aplikasi saat mengikat kelas ke penyedia konfigurasi.

Kode berikut:

  • Memanggil ConfigurationBinder.Bind untuk mengikat kelas PositionOptions ke bagian Position.
  • Menampilkan data konfigurasi 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}");
    }
}

Dalam kode sebelumnya, secara default, perubahan pada file konfigurasi JSON setelah aplikasi dimulai dibaca.

ConfigurationBinder.Get<T> mengikat dan mengembalikan jenis yang ditentukan. ConfigurationBinder.Get<T> mungkin lebih nyaman daripada menggunakan ConfigurationBinder.Bind. Contoh kode berikut menunjukkan cara menggunakan ConfigurationBinder.Get<T> dengan kelas 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}");
    }
}

Dalam kode sebelumnya, secara default, perubahan pada file konfigurasi JSON setelah aplikasi dimulai dibaca.

Pendekatan alternatif saat menggunakan pola opsi adalah untuk mengikat bagian Position dan menambahkannya ke kontainer layanan injeksi dependensi. Dalam kode berikut, PositionOptions ditambahkan ke kontainer layanan dengan Configure dan terikat ke konfigurasi:

using ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

var app = builder.Build();

Dengan menggunakan kode sebelumnya, kode berikut membaca opsi posisi:

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

Dalam kode sebelumnya, perubahan pada file konfigurasi JSON setelah aplikasi dimulai tidak dibaca. Untuk membaca perubahan setelah aplikasi dimulai, gunakan IOptionsSnapshot.

Antarmuka opsi

IOptions<TOptions>:

IOptionsSnapshot<TOptions>:

IOptionsMonitor<TOptions>:

Skenario pasca-konfigurasi mengaktifkan pengaturan atau mengubah opsi setelah semua IConfigureOptions<TOptions> konfigurasi terjadi.

IOptionsFactory<TOptions> bertanggung jawab untuk membuat instans opsi baru. Ini memiliki satu Create metode. Implementasi default mengambil semua yang terdaftar IConfigureOptions<TOptions> dan IPostConfigureOptions<TOptions> dan menjalankan semua konfigurasi terlebih dahulu, diikuti oleh pasca-konfigurasi. Ini membedakan antara IConfigureNamedOptions<TOptions> dan IConfigureOptions<TOptions> dan hanya memanggil antarmuka yang sesuai.

IOptionsMonitorCache<TOptions> digunakan oleh IOptionsMonitor<TOptions> untuk menyimpan TOptions instans. IOptionsMonitorCache<TOptions> Instans opsi yang tidak valid di monitor sehingga nilai dikomputasi ulang (TryRemove). Nilai dapat diperkenalkan secara manual dengan TryAdd. Metode Clear ini digunakan ketika semua instans bernama harus dibuat ulang sesuai permintaan.

Menggunakan IOptionsSnapshot untuk membaca data yang diperbarui

Menggunakan IOptionsSnapshot<TOptions>:

  • Opsi dihitung sekali per permintaan saat diakses dan di-cache selama masa pakai permintaan.
  • Dapat dikenakan penalti performa yang signifikan karena merupakan layanan Cakupan dan dikomputasi ulang per permintaan. Untuk informasi selengkapnya, lihat masalah GitHub ini dan Meningkatkan performa pengikatan konfigurasi.
  • Perubahan pada konfigurasi dibaca setelah aplikasi dimulai saat menggunakan penyedia konfigurasi yang mendukung pembacaan nilai konfigurasi yang diperbarui.

Perbedaan antara IOptionsMonitor dan IOptionsSnapshot adalah bahwa:

  • IOptionsMonitor adalah layanan Singleton yang mengambil nilai opsi saat ini kapan saja, yang sangat berguna dalam dependensi singleton.
  • IOptionsSnapshot adalah layanan Cakupan dan menyediakan rekam jepret opsi pada saat IOptionsSnapshot<T> objek dibuat. Rekam jepret opsi dirancang untuk digunakan dengan dependensi sementara dan terlingkup.

Kode berikut menggunakan 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}");
    }
}

Kode berikut mendaftarkan instans konfigurasi yang MyOptions mengikat terhadap:

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

var app = builder.Build();

Dalam kode sebelumnya, perubahan pada JSfile konfigurasi ON setelah aplikasi dimulai dibaca.

IOptionsMonitor

Kode berikut mendaftarkan instans konfigurasi yang MyOptions mengikat.

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

var app = builder.Build();

Contoh berikut menggunakan 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}");
    }
}

Dalam kode sebelumnya, secara default, perubahan pada file konfigurasi JSON setelah aplikasi dimulai dibaca.

Dukungan opsi bernama menggunakan IConfigureNamedOptions

Opsi bernama:

  • Berguna saat beberapa bagian konfigurasi mengikat properti yang sama.
  • Peka huruf besar/kecil.

Pertimbangkan file appsettings.json berikut:

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

Daripada membuat dua kelas untuk mengikat TopItem:Month dan TopItem:Year, kelas berikut digunakan untuk setiap bagian:

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

Kode berikut mengonfigurasi opsi bernama:

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

Kode berikut menampilkan opsi bernama:

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

Semua opsi diberi nama instans. IConfigureOptions<TOptions> instans diperlakukan sebagai menargetkan Options.DefaultName instans, yaitu string.Empty. IConfigureNamedOptions<TOptions> juga mengimplementasikan IConfigureOptions<TOptions>. Implementasi default dari IOptionsFactory<TOptions> memiliki logika untuk menggunakan masing-masing dengan tepat. null Opsi bernama digunakan untuk menargetkan semua instans bernama alih-alih instans bernama tertentu. ConfigureAll dan PostConfigureAll gunakan konvensi ini.

OptionsBuilder API

OptionsBuilder<TOptions> digunakan untuk mengonfigurasi TOptions instans. OptionsBuilder menyederhanakan pembuatan opsi bernama karena hanya satu parameter ke panggilan awal AddOptions<TOptions>(string optionsName) alih-alih muncul di semua panggilan berikutnya. Validasi opsi dan ConfigureOptions kelebihan beban yang menerima dependensi layanan hanya tersedia melalui OptionsBuilder.

OptionsBuilder digunakan di bagian Validasi opsi.

Lihat Menggunakan AddOptions untuk mengonfigurasi repositori kustom untuk informasi yang menambahkan repositori kustom.

Menggunakan layanan DI untuk mengonfigurasi opsi

Layanan dapat diakses dari injeksi dependensi sambil mengonfigurasi opsi dengan dua cara:

  • Teruskan delegasi konfigurasi ke Configure pada OptionsBuilder<TOptions>. OptionsBuilder<TOptions> menyediakan kelebihan beban Configure yang memungkinkan penggunaan hingga lima layanan untuk mengonfigurasi opsi:

    builder.Services.AddOptions<MyOptions>("optionalName")
        .Configure<Service1, Service2, Service3, Service4, Service5>(
            (o, s, s2, s3, s4, s5) => 
                o.Property = DoSomethingWith(s, s2, s3, s4, s5));
    
  • Buat jenis yang mengimplementasikan IConfigureOptions<TOptions> atau IConfigureNamedOptions<TOptions> dan mendaftarkan jenis sebagai layanan.

Sebaiknya teruskan delegasi konfigurasi ke Configure, karena membuat layanan lebih kompleks. Membuat jenis setara dengan apa yang dilakukan kerangka kerja saat memanggil Configure. Configure Panggilan mendaftarkan generik IConfigureNamedOptions<TOptions>sementara , yang memiliki konstruktor yang menerima jenis layanan generik yang ditentukan.

Validasi opsi

Validasi opsi memungkinkan nilai opsi divalidasi.

Pertimbangkan file appsettings.json berikut:

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

Kelas berikut digunakan untuk mengikat ke bagian "MyConfig" konfigurasi dan menerapkan beberapa DataAnnotations aturan:

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

Kode berikut:

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

Metode ValidateDataAnnotations ekstensi ditentukan dalam paket NuGet Microsoft.Extensions.Options.DataAnnotations . Untuk aplikasi web yang menggunakan Microsoft.NET.Sdk.Web SDK, paket ini dirujuk secara implisit dari kerangka kerja bersama.

Kode berikut menampilkan nilai konfigurasi atau kesalahan validasi:

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

Kode berikut menerapkan aturan validasi yang lebih kompleks menggunakan delegasi:

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> dan IValidatableObject

Kelas berikut mengimplementasikan 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 memungkinkan pemindahan kode validasi keluar dari Program.cs dan ke kelas.

Menggunakan kode sebelumnya, validasi diaktifkan dengan Program.cs kode berikut:

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

Validasi opsi juga mendukung IValidatableObject. Untuk melakukan validasi tingkat kelas kelas dalam kelas itu sendiri:

ValidateOnStart

Validasi opsi berjalan saat pertama kali IOptions<TOptions>implementasi , , IOptionsSnapshot<TOptions>atau IOptionsMonitor<TOptions> dibuat. Untuk menjalankan validasi opsi dengan bersemangat, saat aplikasi dimulai, panggil ValidateOnStart di Program.cs:

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

Opsi pasca-konfigurasi

Atur pasca-konfigurasi dengan IPostConfigureOptions<TOptions>. Pasca-konfigurasi berjalan setelah semua IConfigureOptions<TOptions> konfigurasi terjadi:

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 tersedia untuk pasca-konfigurasi opsi bernama:

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

Gunakan PostConfigureAll untuk mengonfigurasi semua instans konfigurasi pasca-konfigurasi:

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

Opsi akses di Program.cs

Untuk mengakses IOptions<TOptions> atau IOptionsMonitor<TOptions> di Program.cs, panggil GetRequiredService di WebApplication.Services:

var app = builder.Build();

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

Sumber Daya Tambahan:

Oleh Kirk Larkin dan Rick Anderson.

Pola opsi menggunakan kelas untuk menyediakan akses yang sangat diketik ke grup pengaturan terkait. Saat pengaturan konfigurasi diisolasi oleh skenario ke dalam kelas terpisah, aplikasi mematuhi dua prinsip rekayasa perangkat lunak penting:

  • Enkapulasi:
    • Kelas yang bergantung pada pengaturan konfigurasi hanya bergantung pada pengaturan konfigurasi yang mereka gunakan.
  • Pemisahan Kekhawatiran:
    • Pengaturan untuk berbagai bagian aplikasi tidak bergantung atau digabungkan satu sama lain.

Opsi juga menyediakan mekanisme untuk memvalidasi data konfigurasi. Untuk informasi selengkapnya, lihat bagian Validasi opsi.

Topik ini menyediakan informasi tentang pola opsi di ASP.NET Core. Untuk informasi tentang menggunakan pola opsi di aplikasi konsol, lihat Pola opsi di .NET.

Melihat atau mengunduh kode sampel (cara mengunduh)

Mengikat konfigurasi hierarkis

Cara yang lebih disukai untuk membaca nilai konfigurasi terkait adalah menggunakan pola opsi. Misalnya, untuk membaca nilai konfigurasi berikut:

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

Buat kelas PositionOptions berikut:

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

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

Kelas opsi:

  • Harus nonabstrak dengan konstruktor tanpa parameter publik.
  • Semua properti baca-tulis publik dari jenis ini terikat.
  • Bidang tidak terikat. Dalam kode sebelumnya, Position tidak terikat. Bidang Position digunakan sehingga string "Position" tidak perlu dikodekan secara permanen di aplikasi saat mengikat kelas ke penyedia konfigurasi.

Kode berikut:

  • Memanggil ConfigurationBinder.Bind untuk mengikat kelas PositionOptions ke bagian Position.
  • Menampilkan data konfigurasi 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}");
    }
}

Dalam kode sebelumnya, secara default, perubahan pada file konfigurasi JSON setelah aplikasi dimulai dibaca.

ConfigurationBinder.Get<T> mengikat dan mengembalikan jenis yang ditentukan. ConfigurationBinder.Get<T> mungkin lebih nyaman daripada menggunakan ConfigurationBinder.Bind. Contoh kode berikut menunjukkan cara menggunakan ConfigurationBinder.Get<T> dengan kelas 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}");
    }
}

Dalam kode sebelumnya, secara default, perubahan pada file konfigurasi JSON setelah aplikasi dimulai dibaca.

Pendekatan alternatif saat menggunakan pola opsi adalah untuk mengikat bagian Position dan menambahkannya ke kontainer layanan injeksi dependensi. Dalam kode berikut, PositionOptions ditambahkan ke kontainer layanan dengan Configure dan terikat ke konfigurasi:

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

Dengan menggunakan kode sebelumnya, kode berikut membaca opsi posisi:

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

Dalam kode sebelumnya, perubahan pada file konfigurasi JSON setelah aplikasi dimulai tidak dibaca. Untuk membaca perubahan setelah aplikasi dimulai, gunakan IOptionsSnapshot.

Antarmuka opsi

IOptions<TOptions>:

IOptionsSnapshot<TOptions>:

IOptionsMonitor<TOptions>:

Skenario pasca-konfigurasi mengaktifkan pengaturan atau mengubah opsi setelah semua IConfigureOptions<TOptions> konfigurasi terjadi.

IOptionsFactory<TOptions> bertanggung jawab untuk membuat instans opsi baru. Ini memiliki satu Create metode. Implementasi default mengambil semua yang terdaftar IConfigureOptions<TOptions> dan IPostConfigureOptions<TOptions> dan menjalankan semua konfigurasi terlebih dahulu, diikuti oleh pasca-konfigurasi. Ini membedakan antara IConfigureNamedOptions<TOptions> dan IConfigureOptions<TOptions> dan hanya memanggil antarmuka yang sesuai.

IOptionsMonitorCache<TOptions> digunakan oleh IOptionsMonitor<TOptions> untuk menyimpan TOptions instans. IOptionsMonitorCache<TOptions> Instans opsi yang tidak valid di monitor sehingga nilai dikomputasi ulang (TryRemove). Nilai dapat diperkenalkan secara manual dengan TryAdd. Metode Clear ini digunakan ketika semua instans bernama harus dibuat ulang sesuai permintaan.

Menggunakan IOptionsSnapshot untuk membaca data yang diperbarui

Menggunakan IOptionsSnapshot<TOptions>opsi , dihitung sekali per permintaan saat diakses dan di-cache selama masa pakai permintaan. Perubahan pada konfigurasi dibaca setelah aplikasi dimulai saat menggunakan penyedia konfigurasi yang mendukung pembacaan nilai konfigurasi yang diperbarui.

Perbedaan antara IOptionsMonitor dan IOptionsSnapshot adalah bahwa:

  • IOptionsMonitor adalah layanan Singleton yang mengambil nilai opsi saat ini kapan saja, yang sangat berguna dalam dependensi singleton.
  • IOptionsSnapshot adalah layanan Cakupan dan menyediakan rekam jepret opsi pada saat IOptionsSnapshot<T> objek dibuat. Rekam jepret opsi dirancang untuk digunakan dengan dependensi sementara dan terlingkup.

Kode berikut menggunakan 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}");
    }
}

Kode berikut mendaftarkan instans konfigurasi yang MyOptions mengikat terhadap:

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

    services.AddRazorPages();
}

Dalam kode sebelumnya, perubahan pada JSfile konfigurasi ON setelah aplikasi dimulai dibaca.

IOptionsMonitor

Kode berikut mendaftarkan instans konfigurasi yang MyOptions mengikat.

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

    services.AddRazorPages();
}

Contoh berikut menggunakan 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}");
    }
}

Dalam kode sebelumnya, secara default, perubahan pada file konfigurasi JSON setelah aplikasi dimulai dibaca.

Dukungan opsi bernama menggunakan IConfigureNamedOptions

Opsi bernama:

  • Berguna saat beberapa bagian konfigurasi mengikat properti yang sama.
  • Peka huruf besar/kecil.

Pertimbangkan file appsettings.json berikut:

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

Daripada membuat dua kelas untuk mengikat TopItem:Month dan TopItem:Year, kelas berikut digunakan untuk setiap bagian:

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

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

Kode berikut mengonfigurasi opsi bernama:

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

Kode berikut menampilkan opsi bernama:

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

Semua opsi diberi nama instans. IConfigureOptions<TOptions> instans diperlakukan sebagai menargetkan Options.DefaultName instans, yaitu string.Empty. IConfigureNamedOptions<TOptions> juga mengimplementasikan IConfigureOptions<TOptions>. Implementasi default dari IOptionsFactory<TOptions> memiliki logika untuk menggunakan masing-masing dengan tepat. null Opsi bernama digunakan untuk menargetkan semua instans bernama alih-alih instans bernama tertentu. ConfigureAll dan PostConfigureAll gunakan konvensi ini.

OptionsBuilder API

OptionsBuilder<TOptions> digunakan untuk mengonfigurasi TOptions instans. OptionsBuilder menyederhanakan pembuatan opsi bernama karena hanya satu parameter ke panggilan awal AddOptions<TOptions>(string optionsName) alih-alih muncul di semua panggilan berikutnya. Validasi opsi dan ConfigureOptions kelebihan beban yang menerima dependensi layanan hanya tersedia melalui OptionsBuilder.

OptionsBuilder digunakan di bagian Validasi opsi.

Lihat Menggunakan AddOptions untuk mengonfigurasi repositori kustom untuk informasi yang menambahkan repositori kustom.

Menggunakan layanan DI untuk mengonfigurasi opsi

Layanan dapat diakses dari injeksi dependensi sambil mengonfigurasi opsi dengan dua cara:

  • Teruskan delegasi konfigurasi ke Configure pada OptionsBuilder<TOptions>. OptionsBuilder<TOptions> menyediakan kelebihan beban Configure yang memungkinkan penggunaan hingga lima layanan untuk mengonfigurasi opsi:

    services.AddOptions<MyOptions>("optionalName")
        .Configure<Service1, Service2, Service3, Service4, Service5>(
            (o, s, s2, s3, s4, s5) => 
                o.Property = DoSomethingWith(s, s2, s3, s4, s5));
    
  • Buat jenis yang mengimplementasikan IConfigureOptions<TOptions> atau IConfigureNamedOptions<TOptions> dan mendaftarkan jenis sebagai layanan.

Sebaiknya teruskan delegasi konfigurasi ke Configure, karena membuat layanan lebih kompleks. Membuat jenis setara dengan apa yang dilakukan kerangka kerja saat memanggil Configure. Configure Panggilan mendaftarkan generik IConfigureNamedOptions<TOptions>sementara , yang memiliki konstruktor yang menerima jenis layanan generik yang ditentukan.

Validasi opsi

Validasi opsi memungkinkan nilai opsi divalidasi.

Pertimbangkan file appsettings.json berikut:

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

Kelas berikut mengikat ke bagian "MyConfig" konfigurasi dan menerapkan beberapa DataAnnotations aturan:

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

Kode berikut:

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

Metode ValidateDataAnnotations ekstensi ditentukan dalam paket NuGet Microsoft.Extensions.Options.DataAnnotations . Untuk aplikasi web yang menggunakan Microsoft.NET.Sdk.Web SDK, paket ini dirujuk secara implisit dari kerangka kerja bersama.

Kode berikut menampilkan nilai konfigurasi atau kesalahan validasi:

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

Kode berikut menerapkan aturan validasi yang lebih kompleks menggunakan delegasi:

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 untuk validasi kompleks

Kelas berikut mengimplementasikan 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 memungkinkan pemindahan kode validasi keluar dari StartUp dan ke kelas.

Menggunakan kode sebelumnya, validasi diaktifkan dengan Startup.ConfigureServices kode berikut:

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

Opsi pasca-konfigurasi

Atur pasca-konfigurasi dengan IPostConfigureOptions<TOptions>. Pasca-konfigurasi berjalan setelah semua IConfigureOptions<TOptions> konfigurasi terjadi:

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

PostConfigure tersedia untuk pasca-konfigurasi opsi bernama:

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

Gunakan PostConfigureAll untuk mengonfigurasi semua instans konfigurasi pasca-konfigurasi:

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

Mengakses opsi selama startup

IOptions<TOptions> dan IOptionsMonitor<TOptions> dapat digunakan dalam Startup.Configure, karena layanan dibangun sebelum Configure metode dijalankan.

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

Jangan gunakan IOptions<TOptions> atau IOptionsMonitor<TOptions> di Startup.ConfigureServices. Status opsi yang tidak konsisten mungkin ada karena pemesanan pendaftaran layanan.

Paket NuGet Options.ConfigurationExtensions

Paket Microsoft.Extensions.Options.ConfigurationExtensions dirujuk secara implisit di aplikasi ASP.NET Core.