Earn More by Sharing What You Love
Do you create content for developers working with .NET, C#, Java, Python, or Node.js? Turn your expertise into extra income!

Tim Corey
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.
[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 Property Storage As String
Public Property Website As String
Public Property API As String
End ClassTim 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.
[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(Of 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.
[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 CloudConfig As IOptions(Of CloudInfoOptions)
Protected Overrides Sub OnInitialized()
Dim options = CloudConfig.Value
' options.Storage, options.Website, options.API are available
End SubKluczowy 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.
[5:18 - 6:55] Tim zamienia IOptions na IOptionsSnapshot, aby zademonstrować wariant o zasięgu:
@inject IOptionsSnapshot<CloudInfoOptions> CloudConfig@inject IOptionsSnapshot(Of CloudInfoOptions) CloudConfigIOptionsSnapshot<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ń.
[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 CloudConfig As IOptionsMonitor(Of CloudInfoOptions)
Protected Overrides Sub OnInitialized()
' Current values
Dim current = CloudConfig.CurrentValue
' Register a callback for live changes
CloudConfig.OnChange(
Sub(updatedOptions)
' React to configuration changes in real time
End Sub)
End SubW 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.
[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(Of CloudInfoOptions)() _
.BindConfiguration("CloudInfo") _
.Validate(Function(opts) _
Not String.IsNullOrEmpty(opts.Storage) AndAlso _
Not String.IsNullOrEmpty(opts.Website) AndAlso _
Not 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.
[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.
[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#.
Do you create content for developers working with .NET, C#, Java, Python, or Node.js? Turn your expertise into extra income!
Join our newsletter, you’ll get exclusive access on article updates. We value your privacy