Konfiguracja w ASP.NET Core — appsettings, Options i sekrety

Twoje API potrzebuje ustawień: connection string do bazy, klucz do bramki płatności, adres zewnętrznej usługi, limit rozmiaru pliku. Trzy pytania decydują, czy zrobisz to dobrze: gdzie te wartości trzymać, jak je czytać w kodzie i — najważniejsze — jak nie wrzucić hasła do bazy na publiczne repozytorium GitHub. ASP.NET Core ma na to spójny system konfiguracji; ten post pokazuje, jak z niego korzystać, nie popełniając klasycznych błędów bezpieczeństwa.
Warstwy konfiguracji — i kto kogo nadpisuje
Konfiguracja w ASP.NET Core to nie jeden plik, lecz warstwy ładowane po kolei, gdzie późniejsza nadpisuje wcześniejszą. Domyślna kolejność:
| Kolejność | Źródło | Typowe zastosowanie |
|---|---|---|
| 1 | appsettings.json | Wartości bazowe, wspólne dla wszystkich |
| 2 | appsettings.{Environment}.json | Nadpisania per środowisko (Development/Production) |
| 3 | User Secrets | Sekrety w czasie developmentu (poza projektem) |
| 4 | Zmienne środowiskowe | Sekrety i nadpisania na serwerze |
| 5 | Argumenty wiersza poleceń | Doraźne nadpisanie przy starcie |
Ten porządek to projekt, nie przypadek: bazowe appsettings.json commitujesz do repo, a wrażliwe i środowiskowe wartości wstrzykujesz wyżej w łańcuchu (zmienne środowiskowe na produkcji), gdzie nadpiszą to, co w pliku. Ten sam mechanizm warstw widziałeś już przy middleware — kolejność decyduje o wyniku.
// appsettings.json — bazowo
{ "Platnosci": { "Waluta": "PLN", "TimeoutSekundy": 30 } }// appsettings.Development.json — nadpisanie w dev
{ "Platnosci": { "TimeoutSekundy": 5 } }W środowisku Development Waluta = PLN (z bazy), a TimeoutSekundy = 5 (nadpisane).
Odczyt — od IConfiguration do wzorca Options
Najprościej sięgnąć po IConfiguration, ale to podejście „stringly-typed” — literówka w kluczu daje null, nie błąd:
var waluta = builder.Configuration["Platnosci:Waluta"]; // "PLN" albo null
var timeout = builder.Configuration.GetValue<int>("Platnosci:TimeoutSekundy");Lepszy jest wzorzec Options: mapujesz sekcję konfiguracji na klasę C# i wstrzykujesz ją tam, gdzie jej potrzebujesz.
public class PlatnosciOptions
{
public string Waluta { get; set; } = "PLN";
public int TimeoutSekundy { get; set; }
}
// rejestracja — wiążemy sekcję "Platnosci" z klasą
builder.Services.Configure<PlatnosciOptions>(
builder.Configuration.GetSection("Platnosci"));Od teraz ustawienia wstrzykujesz przez DI jako silnie typowany obiekt — z autouzupełnianiem i kontrolą typów zamiast magicznych stringów:
public class BramkaPlatnosci(IOptions<PlatnosciOptions> options)
{
private readonly PlatnosciOptions _cfg = options.Value;
public void Zaplac() => Console.WriteLine(
$"Płatność w {_cfg.Waluta}, timeout {_cfg.TimeoutSekundy}s");
}Konfiguracja przez
IConfiguration["..."]to stringi, które kompilator ignoruje. Wzorzec Options zamienia je w klasę — literówka staje się błędem kompilacji, a nie cichymnullna produkcji.
Sekrety — nigdy w appsettings.json commitowanym do gita
Najważniejsza reguła całego tematu: appsettings.json trafia do repozytorium, więc nie może zawierać sekretów. Hasło do bazy, klucz API czy token wrzucone do tego pliku wyciekają do historii gita w chwili pierwszego commita — i zostają tam, nawet gdy je później usuniesz.
W developmencie służą do tego User Secrets — plik trzymany poza katalogiem projektu (w profilu użytkownika), którego git nigdy nie widzi:
dotnet user-secrets init
dotnet user-secrets set "Platnosci:KluczApi" "sk_test_abc123"Odczytujesz je dokładnie tak samo jak resztę konfiguracji — warstwa User Secrets wstrzykuje się automatycznie w środowisku Development.
Na produkcji sekrety podajesz przez zmienne środowiskowe (albo magazyn typu Azure Key Vault). Zagnieżdżenie zapisujesz podwójnym podkreśleniem:
Platnosci__KluczApi=sk_live_xyz789Ta sama nazwa klucza, inne źródło — kod się nie zmienia. To właśnie po to istnieją warstwy.
Środowiska — jeden build, różne zachowania
Aktywne środowisko ustala zmienna ASPNETCORE_ENVIRONMENT (Development, Staging, Production). Odczytasz je z app.Environment i uzależnisz od niego zachowanie:
if (app.Environment.IsDevelopment())
app.MapOpenApi(); // dokumentacja API tylko lokalnieDzięki temu ten sam artefakt wdrażasz na każde środowisko — różnicę robi konfiguracja, nie osobny build.
Pułapki konfiguracji
Pułapka #1 — sekret w repo. Connection string z hasłem w appsettings.json to najczęstszy wyciek juniora. Raz zacommitowany, żyje w historii gita na zawsze. Sekrety → User Secrets (dev) i zmienne środowiskowe (prod), nigdy do pliku pod kontrolą wersji.
Pułapka #2 — IOptions nie widzi zmian. IOptions<T> jest singletonem — wartość wczytuje raz przy starcie. Jeśli chcesz, by zmiana w appsettings.json była widoczna bez restartu, użyj IOptionsSnapshot<T> (przeładowywany per żądanie) albo IOptionsMonitor<T>. Mylenie ich to godziny debugowania „czemu nowa wartość się nie łapie”.
Pułapka #3 — brakująca sekcja daje ciche zera. GetSection("Platnosci") na nieistniejącej sekcji nie rzuca wyjątku — zwraca obiekt z domyślnymi wartościami (0, null). Wymuś kontrolę już przy starcie:
builder.Services.AddOptions<PlatnosciOptions>()
.Bind(builder.Configuration.GetSection("Platnosci"))
.Validate(o => o.TimeoutSekundy > 0, "TimeoutSekundy musi być dodatni")
.ValidateOnStart(); // błąd przy starcie, nie przy pierwszym żądaniuPułapka #4 — connection string wpisany w kodzie. UseSqlite("Data Source=...") z zaszytą wartością działa, dopóki nie musisz jej zmienić per środowisko. Czytaj ją z konfiguracji (GetConnectionString("Domyslny")), a wartość produkcyjną podawaj zmienną środowiskową.
Podsumowanie
Konfiguracja w ASP.NET Core to warstwy — od bazowego appsettings.json, przez plik per środowisko, po User Secrets i zmienne środowiskowe — gdzie każda wyższa nadpisuje niższą. W kodzie sięgasz po wzorzec Options (IOptions<T>) zamiast stringów z IConfiguration, żeby literówka była błędem kompilacji, nie cichym null. Żelazna zasada bezpieczeństwa: sekrety nigdy nie trafiają do plików pod kontrolą wersji — w developmencie idą do User Secrets, na produkcji do zmiennych środowiskowych. Reszta (środowiska, walidacja ValidateOnStart) to warianty tego samego, spójnego systemu.
Co dalej
Masz komplet fundamentów Etapu 12: HTTP, pierwsze API, routing, middleware i konfigurację. Teraz zobacz, jak spiąć to w utrzymywalną całość w Clean Architecture w .NET 10 — REST API — gdzie konfiguracja, dostęp do bazy i logika biznesowa trafiają do osobnych warstw za wyraźnymi granicami.
🚀 Co dalej?
Zobacz to w praktyce na wideo i pobierz darmową roadmapę, żeby ułożyć naukę w spójną ścieżkę do pierwszej pracy.
- 🗺️ Pobierz darmową roadmapę Junior .NET Developer — 12 kroków od podstaw C# do pierwszej pracy: dev-hobby.pl
- 🎬 Subskrybuj kanał YouTube — nowe filmy co tydzień.
Zamień wiedzę w umiejętności
Pobierz darmową Roadmapę .NET i ułóż takie tematy jak ten w spójną ścieżkę do pierwszej pracy.
Pobieram roadmapę →