Použití IConfiguration a IOptions patternu

Jan Holan       11.04.2020       .NET Tips, .NET Core, ASP.NET Core       7620 zobrazení

Pokusím se zde na malých ukázkách kódu postupně ukázat použití IConfiguration a IOptions v .NET Core.

Budu předpokládat standardní konfiguraci .NET Core aplikaci pomoci souboru appsettings.json načtenou například pomoci výchozího Host.CreateDefaultBuilder.

Mějme v něm přidanou tuto jednoduchou konfigurační sekci:

  "AppSettings": {
    "Settings1": "Value1",
    "Settings2": "Value2"
  },

Začneme základním načtení konfigurace z IConfiguration objektu.

IConfiguration

K hodnotám uvedené konfigurační sekce můžeme přistupovat napřímo pomoci objektu typu IConfiguration takto (kde objekt configuration získáme například pomoci DI přes konstruktor):

string settings1 = configuration["AppSettings:Settings1"]

Lepší způsob ale je si na konfigurační sekci zavést vlastní typový objekt:

[System.Diagnostics.DebuggerDisplay("\\{ Settings1 = {Settings1}, Settings2 = {Settings2} \\}")]
public sealed class AppSettingsOptions
{
    public string Settings1 { get; set; }
    public string Settings2 { get; set; }
}

a configuraci pak můžeme do něj načítat takto:

var appSettings = configuration.GetSection("AppSettings").Get<AppSettingsOptions>();
nebo do již existující instance takto:
var appSettings = new AppSettingsOptions();
configuration.GetSection("AppSettings").Bind(appSettings);

Pokud konfigurační soubor appsettings.json změníme, bude (díky reloadOnChange: true uvedeného při standardní konfiguraci) znovu načten. Nové volání načtení konfigurace pak bude změny obsahovat.

IOptions

Options pattern použijeme tak, že si nejprve (typicky ve startupu aplikace v metodě ConfigureServices) zaregistrujeme náš objekt AppSettingsOptions do containeru ServiceCollection. Provedeme to pomoci extenze Configure<T>(IConfiguration config) s předáním konfigurační sekce:

services.Configure<AppSettingsOptions>(this.configuration.GetSection("AppSettings"));

Při používání si pak pomoci DI v konstruktoru získáme naplněnou instanci objektu AppSettingsOptions pomoci IOptions interfaců. Máme tyto možnosti:

public class Test
{
    private AppSettingsOptions AppSettingsOptions;

    public Test(IOptions<AppSettingsOptions> appSettingsOptionsAccessor)
    {
        this.AppSettingsOptions = appSettingsOptionsAccessor.Value;
    }

    public Test(IOptionsSnapshot<AppSettingsOptions> appSettingsOptionsAccessor)
    {
        this.AppSettingsOptions = appSettingsOptionsAccessor.Value;
    }

    public Test(IOptionsMonitor<AppSettingsOptions> appSettingsOptionsAccessor)
    {
        this.AppSettingsOptions = appSettingsOptionsAccessor.CurrentValue;

        var disposable = appSettingsOptionsAccessor.OnChange(options =>
        {
            this.AppSettingsOptions = options;
        });
    }
}

Při použití IOptions<T> bude přes vlastnost Value objekt typu AppSettingsOptions načten a nakešován jako Singleton. Při změně konfigurace v souboru appsettings.json nebudou nové hodnoty do této instance načteny.

Při použití IOptionsSnapshot<T> má získaný objekt typu AppSettingsOptions lifetime Scoped, tedy typicky request. Při novém requestu bude obsahovat změny konfigurace provedené v appsettings.json. Objekt se totiž vytváří při každém requestu znovu (a je po jeho dobu existence nakešován), to ale nemusí být někdy vhodné (například pokud máme v objektu nějakou složitější logiku).
IOptionsSnapshot ještě navíc podporují tzv. named options, když potřebujete mít více instancí konfigurace (registrují se uvedením jména při services.Configure<T>(string name, IConfiguration config).

Třetí možností je použití IOptionsMonitor<T>. Obsahuje vlastnost CurrentValue pro načtení objektu AppSettingsOptions, který bude Singleton (jako v případě IOptions<T>.Value). Navíc ale umí reagovat na změny konfigurace, tj. po změně bude CurrentValue vracet novou instanci se změněnými hodnotami. Přitom se ale nová instance vytváří pouze když je provedena změna (na rozdíl od IOptionsSnapshot<T>). Pokud sami potřebujeme reagovat na změnu konfigurace, máme možnost tak provést pomoci akce v OnChange viz. uvedený příklad. (OnChange vrací IDisposable, kde volání Dispose() ukončí přijímaní dalších změn a uvolní ChangeToken.). Named options jsou zde podporovány také.

Pokud jste to do teď neřešili a máte v kódu pouze IOptions, za mě doporučuji změnit a používat IOptionsMonitor.

IOptions with a delegate registration

Někdy může být potřeba options objekt z konfigurace vytvářet nějakou složitější logikou, bohužel jsem nenašel způsob jak do Configure předat nějakou vlastní faktory akci. Můžeme ale využít registraci do ServiceCollection pomoci delegátu, který vám předá vytvořenou instanci options objektu a umožní ho pak naplnit. Registraci provedeme extenzí Configure<T>(Action<TOptions> configureOptions), v kódu to může vypadat nějak takto:

var section = configuration.GetSection("AppOptions");
services.Configure<AppOptions>(options =>
{
    section.Bind<AppOptions>(options);

    //Fill (change) Setting2 with custom logic
    options.Setting2 = GetSetting2(section);
});

//Register ChangeToken to respond to configuration changes (normaly registered in services.Configure(config) extension)
services.AddSingleton((IOptionsChangeTokenSource<IPAddressRulesOptions>)new ConfigurationChangeTokenSource<AppOptions>(section));

V příkladu naplním instanci našeho objektu AppSettingsOptions pomoci Bind, a pak změním vlastnosti Settings2 vlastní logikou (kterou zde představuje volání GetSettings2).

Je zde takový side effect, že takto zaregistrovaný options objekt nereaguje přes IOptionsMotitor<AppSettingsOptions> na změny konfigurace. To spravíme tím, že ještě zaregistrujeme IOptionsChangeTokenSource<T> resp. ConfigurationChangeTokenSource<T> který zajistí reagování na změny konfigurace. Normálně je tato registrace součástí extenze s parametrem config Configure(config), ale zde ve variantě Configure(configureOptions) chybí.

Použité reference:
https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-3.1
https://andrewlock.net/creating-singleton-named-options-with-ioptionsmonitor/
https://docs.microsoft.com/en-us/aspnet/core/fundamentals/change-tokens?view=aspnetcore-3.1

 

hodnocení článku

0       Hodnotit mohou jen registrované uživatelé.

 

Nový příspěvek

 

Příspěvky zaslané pod tento článek se neobjeví hned, ale až po schválení administrátorem.

Použití TypeConverter v ConfigurationBinder

Ještě doplním, že při načítání objektu z konfigurace pomoci Get nebo Bind se může u třídy použít TypeConverter, který ConfigurationBinder pak použije pro vytvoření instance.

Například pro načtení objektu ze string pomoci nějaké metody Parse, to vypadá takto:

[TypeConverter(typeof(AppSettingsOptionsConverter))]
public sealed class AppSettingsOptions

public class AppSettingsOptionsConverter : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
    {
        object result;
        if (value is string stringValue && !string.IsNullOrEmpty(stringValue))
        {
            result = AppSettingsOptions.Parse(stringValue);
        }
        else
        {
            result = null;
        }

        return result ?? base.ConvertFrom(context, culture, value);
    }
}
nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět
                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

Příspěvky zaslané pod tento článek se neobjeví hned, ale až po schválení administrátorem.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:
Heslo:

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback