Pola opsi ASP.NET Core

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:

  • Enkapkulasi:
    • 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 disukai untuk membaca nilai konfigurasi terkait adalah menggunakan pola opsi. Misalnya, untuk membaca nilai konfigurasi berikut:

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

Buat kelas berikut PositionOptions :

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 dengan konstruktor tanpa parameter publik.
  • Semua properti baca-tulis publik dari jenis 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 PositionOptions kelas ke bagian Position .
  • Position Menampilkan data konfigurasi.
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 JSfile konfigurasi ON setelah aplikasi dimulai dibaca.

ConfigurationBinder.Get<T> mengikat dan mengembalikan jenis yang ditentukan. ConfigurationBinder.Get<T> mungkin lebih nyaman daripada menggunakan ConfigurationBinder.Bind. Kode berikut menunjukkan cara menggunakan ConfigurationBinder.Get<T> dengan PositionOptions kelas :

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 JSfile konfigurasi ON setelah aplikasi dimulai dibaca.

Pendekatan alternatif saat menggunakan pola opsi adalah 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 JSfile konfigurasi ON setelah aplikasi dimulai tidak dibaca. Untuk membaca perubahan setelah aplikasi dimulai, gunakan IOptionsSnapshot.

Antarmuka opsi

IOptions<TOptions>:

  • Tidak mendukung:
    • Membaca data konfigurasi setelah aplikasi dimulai.
    • Opsi bernama
  • Terdaftar sebagai Singleton dan dapat disuntikkan ke dalam masa pakai apa pun.

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 cache TOptions instans. Instans IOptionsMonitorCache<TOptions> 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 ini adalah layanan Tercakup 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 Terlingkup dan menyediakan rekam jepret opsi pada saat IOptionsSnapshot<T> objek dibangun. Opsi rekam jepret 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 JSfile konfigurasi ON 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 berikut appsettings.json :

{
  "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 penargetan 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 saat 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 berikut appsettings.json :

{
  "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 dari Program.cs dan ke dalam 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 pasca-konfigurasi semua instans 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 diketik dengan kuat 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.

Menampilkan atau mengunduh kode sampel (cara mengunduh)

Mengikat konfigurasi hierarkis

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

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

Buat kelas berikut PositionOptions :

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

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

Kelas opsi:

  • Harus non-abstrak dengan konstruktor tanpa parameter publik.
  • Semua properti baca-tulis publik jenis terikat.
  • Bidang tidak terikat. Dalam kode sebelumnya, Position tidak terikat. Properti 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 PositionOptions kelas ke bagian Position .
  • Position Menampilkan data konfigurasi.
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 JSfile konfigurasi ON setelah aplikasi dimulai dibaca.

ConfigurationBinder.Get<T> mengikat dan mengembalikan jenis yang ditentukan. ConfigurationBinder.Get<T> mungkin lebih nyaman daripada menggunakan ConfigurationBinder.Bind. Kode berikut menunjukkan cara menggunakan ConfigurationBinder.Get<T> dengan PositionOptions kelas :

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 JSfile konfigurasi ON setelah aplikasi dimulai dibaca.

Pendekatan alternatif saat menggunakan pola opsi adalah 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 JSfile konfigurasi ON 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. Instans IOptionsMonitorCache<TOptions> 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 Terlingkup dan menyediakan rekam jepret opsi pada saat IOptionsSnapshot<T> objek dibangun. Opsi rekam jepret 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 JSfile konfigurasi ON 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 berikut appsettings.json :

{
  "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 penargetan 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 saat mengonfigurasi opsi dengan dua cara:

  • Teruskan delegasi konfigurasi ke Configure di 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 berikut appsettings.json :

{
  "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 dari StartUp dan ke dalam 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 opsi bernama pasca-konfigurasi:

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

Gunakan PostConfigureAll untuk pasca-konfigurasi semua instans 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 secara implisit dirujuk dalam aplikasi ASP.NET Core.