Przejdź do treści stopki
Iron Academy Logo
Naucz się C#
Naucz się C#

Inne Kategorie

Wzorzec opcji w C#

[[academy-video-youtube({"vid": "ko1Ie9gDydY", "start_time": "0", "title": "The Options Pattern in C#", "creator": "Tim Corey", "length": "10m 15s"})]]

Konfiguracja w aplikacjach .NET zwykle znajduje się w appsettings.json, zmiennych środowiskowych lub tajnych danych użytkownika. Wydobywanie tych danych konfiguracyjnych z pliku i umieszczanie ich w twoich klasach w czysty, testowalny sposób to coś, co rozwiązuje wzorzec opcji. Zamiast ręcznie czytać klucze JSON lub przekazywać surowe ciągi znaków, wiążesz sekcję konfiguracji z silnie typowaną klasą C# i pozwalasz na wstrzykiwanie zależności, aby dostarczyć ją wszędzie tam, gdzie jest potrzebna.

W swoim wideo "The Options Pattern in C#", Tim Corey wprowadza trzy warianty wzorca opcji (IOptions, IOptionsSnapshot i IOptionsMonitor), demonstruje każdy z nich w aplikacji serwerowej Blazor i pokazuje, jak dodać walidację, aby aplikacja szybko się zamknęła, gdy konfiguracja jest niekompletna lub niepoprawna. Jeśli tworzysz jakikolwiek projekt .NET, który czyta pliki konfiguracyjne i używa wstrzykiwania zależności, ten wzorzec jest podstawowy.

Przygotowanie: Model POCO i appsettings.json

[0:34 - 1:46] Tim zaczyna od aplikacji webowej Blazor (renderowanej po stronie serwera, bez interaktywności po stronie klienta), która już ma dwie części na miejscu. Pierwszym jest sekcja w appsettings.json z trzema parami klucz-wartość w sekcji CloudInfo:

// appsettings.json
{
  "CloudInfo": {
    "Storage": "https://storage.example.com",
    "Website": "https://www.example.com",
    "API": "https://api.example.com"
  }
}
// appsettings.json
{
  "CloudInfo": {
    "Storage": "https://storage.example.com",
    "Website": "https://www.example.com",
    "API": "https://api.example.com"
  }
}

Druga część to zwykła klasa C# (POCO), której właściwości odzwierciedlają klucze JSON:

public class CloudInfoOptions
{
    public string Storage { get; set; }
    public string Website { get; set; }
    public string API { get; set; }
}
public class CloudInfoOptions
{
    public string Storage { get; set; }
    public string Website { get; set; }
    public string API { get; set; }
}

Tim zauważa, że źródłem konfiguracji nie musi być konkretnie appsettings.json. Może to być appsettings.Development.json, secrets.json lub dowolna kombinacja. Wzorzec opcji czyta z dowolnych dostawców konfiguracji zarejestrowanych w aplikacji, a nazwy właściwości na POCO pasują do kluczy JSON z konwencji.

Rejestracja Opcji w Program.cs

[2:25 - 3:15] Podłączenie konfiguracji do iniekcji zależności wymaga dwóch wywołań metod w Program.cs:

// Register CloudInfoOptions bound to the "CloudInfo" section
builder.Services.AddOptions<CloudInfoOptions>()
    .BindConfiguration("CloudInfo");
// Register CloudInfoOptions bound to the "CloudInfo" section
builder.Services.AddOptions<CloudInfoOptions>()
    .BindConfiguration("CloudInfo");

AddOptions<t>() rejestruje typ w DI. BindConfiguration("CloudInfo") informuje framework, którą część konfiguracji mapować. String "CloudInfo" odpowiada kluczowi JSON w appsettings.json. Jeśli nazwa sekcji i nazwa klasy nie pasują, argument ciągu znaków jest tym, co framework używa do znalezienia odpowiednich danych.

IOptions: Podejście Singleton

[3:15 - 5:18] Po rejestracji, Tim wprowadza konfigurację na stronę Blazor używając IOptions<CloudInfoOptions>:

@inject IOptions<CloudInfoOptions> CloudConfig

@code {
    protected override void OnInitialized()
    {
        var options = CloudConfig.Value;
        // options.Storage, options.Website, options.API are available
    }
}
@inject IOptions<CloudInfoOptions> CloudConfig

@code {
    protected override void OnInitialized()
    {
        var options = CloudConfig.Value;
        // options.Storage, options.Website, options.API are available
    }
}

Kluczowy szczegół to, że IOptions<t> jest zarejestrowany jako singleton. Wartości konfiguracji są odczytywane raz, gdy aplikacja się uruchamia, i są przechowywane w pamięci podręcznej na czas trwający cały proces. Jeśli zmienisz appsettings.json podczas działania aplikacji, IOptions nie odzwierciedli tych zmian.

Dla większości aplikacji to właściwy wybór. Konfiguracja rzadko zmienia się w czasie wykonywania, a czas życia singletona oznacza brak dodatkowego obciążenia na żądanie.

IOptionsSnapshot: Odświeżanie Zasięgu

[5:18 - 6:55] Tim zamienia IOptions na IOptionsSnapshot, aby zademonstrować wariant o zasięgu:

@inject IOptionsSnapshot<CloudInfoOptions> CloudConfig
@inject IOptionsSnapshot<CloudInfoOptions> CloudConfig

IOptionsSnapshot<t> czyta konfigurację świeżo dla każdego żądania (czas życia o zasięgu). Jeśli zmodyfikujesz appsettings.json podczas działania aplikacji, kolejne żądanie internetowe przechwyci nowe wartości. Poprzednie żądanie zachowuje własną migawkę, więc nie ma niezgodności w trakcie żądania.

To jest przydatne w aplikacjach, gdzie zmiany konfiguracji muszą być wdrożone bez ponownego uruchamiania, jak przełączanie flag funkcjonalnych, aktualizowanie punktów końcowych API, czy rotacja ciągów połączeń do przechowywania. Kompromisem jest niewielki koszt na żądanie związany z ponownym odczytem konfiguracji, który jest pomijalny dla większości obciążeń.

IOptionsMonitor: Powiadomienia o Zmianach na Żywo

[6:55 - 8:34] Trzeci wariant, IOptionsMonitor<t>, idzie dalej niż przeładowanie przyrostowe. Aktywnie monitoruje zmiany w konfiguracji i może uruchamiać callbacki, gdy wartości się zmienią:

@inject IOptionsMonitor<CloudInfoOptions> CloudConfig

@code {
    protected override void OnInitialized()
    {
        // Current values
        var current = CloudConfig.CurrentValue;

        // Register a callback for live changes
        CloudConfig.OnChange(updatedOptions =>
        {
            // React to configuration changes in real time
        });
    }
}
@inject IOptionsMonitor<CloudInfoOptions> CloudConfig

@code {
    protected override void OnInitialized()
    {
        // Current values
        var current = CloudConfig.CurrentValue;

        // Register a callback for live changes
        CloudConfig.OnChange(updatedOptions =>
        {
            // React to configuration changes in real time
        });
    }
}

W przeciwieństwie do IOptionsSnapshot, który odświeża tylko między żądaniami, IOptionsMonitor może wykrywać zmiany podczas długotrwałej operacji. Tim wskazuje, że ma to największe znaczenie dla usług działających w tle, hubów SignalR lub dowolnego komponentu o dłuższym czasie życia niż pojedyncze żądanie HTTP.

Dodawanie Walidacji

[8:34 - 10:05] The final piece Tim covers is validation. Wzorzec opcji obsługuje zasady walidacji inline, które działają podczas pierwszego dostępu do konfiguracji:

builder.Services.AddOptions<CloudInfoOptions>()
    .BindConfiguration("CloudInfo")
    .Validate(opts =>
        !string.IsNullOrEmpty(opts.Storage) &&
        !string.IsNullOrEmpty(opts.Website) &&
        !string.IsNullOrEmpty(opts.API));
builder.Services.AddOptions<CloudInfoOptions>()
    .BindConfiguration("CloudInfo")
    .Validate(opts =>
        !string.IsNullOrEmpty(opts.Storage) &&
        !string.IsNullOrEmpty(opts.Website) &&
        !string.IsNullOrEmpty(opts.API));

Tim demonstruje przypadek niepowodzenia, usuwając wartość Storage z appsettings.json i uruchamiając aplikację. Wynik to natychmiastowy nieobsługiwany wyjątek: "CloudInfoOptions nie przeszło walidacji." Aplikacja odmawia rozpoczęcia z niekompletną konfiguracją, co jest dokładnie zachowaniem, jakiego oczekujesz. Odkrycie brakującej wartości przy starcie jest znacznie lepsze niż uderzenie w referencję z zerowym w produkcji o 2 w nocy.

W przypadku bardziej złożonych scenariuszy walidacji (kontrole między właściwościami, reguły warunkowe), można łańcuchowo połączyć wiele wywołań .Validate() lub wdrożyć IValidateOptions<t> jako dedykowaną klasę walidacji.

Podsumowanie: Trzy Interfejsy, Jeden Wzorzec

[10:05 - 10:10] Wzorzec opcji zapewnia czyste oddzielenie między magazynowaniem konfiguracji a jej konsumowaniem. IOptions obejmuje większość przypadków użycia, gdzie konfiguracja jest statyczna. IOptionsSnapshot obsługuje aplikacje, które muszą odbierać zmiany między żądaniami. IOptionsMonitor obsługuje komponenty działające długoterminowo, które potrzebują świadomości w czasie rzeczywistym na temat aktualizacji konfiguracji. A walidacja zapewnia, że twoja aplikacja wygasa głośno, gdy brakuje wymaganych wartości.

Wnioski

[10:10 - 10:15] W skrócie: zarejestruj swoją konfigurację za pomocą AddOptions<t>().BindConfiguration("SectionName") w Program.cs, wstrzyknij jeden z trzech interfejsów (IOptions, IOptionsSnapshot lub IOptionsMonitor) w zależności od wymagań dotyczących przeładowania i dodaj .Validate(), aby wychwytywać brakujące wartości przy uruchomieniu, a nie podczas działania.

Wzorzec działa w każdym typie projektu .NET: Blazor, ASP.NET Core, aplikacje konsolowe, usługi pracownicze. Rejestracja jest taka sama wszędzie.

Przykład tip: Jeśli nie jesteś pewien, który interfejs użyć, zacznij od IOptions<t>. Jest najprostszy, nie ma żadnego obciążenia na żądanie i obejmuje zdecydowaną większość przypadków, w których konfiguracja jest ustawiona przy starcie i nie zmienia się. Przejdź do IOptionsSnapshot lub IOptionsMonitor tylko wtedy, gdy masz konkretną potrzebę dla przeładowania w czasie działania.

Obejrzyj pełne wideo na jego kanale YouTube i zyskaj więcej wglądu w wzorce konfiguracyjne C#.

Hero Worlddot related to Wzorzec opcji w C#
Hero Affiliate related to Wzorzec opcji w C#

Zarabiaj więcej, dzieląc się tym, co kochasz

Tworzysz treści dla deweloperów pracujących z .NET, C#, Java, Python, czy Node.js? Zamień swoją wiedzę specjalistyczną na dodatkowy dochód!

Zespół wsparcia Iron

Jesteśmy online 24 godziny, 5 dni w tygodniu.
Czat
E-mail
Zadzwoń do mnie