Dataczwartek, 13 sierpnia 2026 Czas19:11:29
← Aplikacje webowe

Lekcja 19. Wdrażanie aplikacji ASP.NET Core — publikacja i środowiska

Trudny / egzaminacyjny

Po co się tego uczymy?

Gotowa aplikacja uruchamiana z Visual Studio (tryb Debug) to nie to samo, co aplikacja gotowa do udostępnienia prawdziwym użytkownikom w internecie. Ta lekcja pokazuje, jak przygotować aplikację ASP.NET Core do wdrożenia (publish), czym różnią się środowiska Development/Production, i jak skonfigurować bezpieczne przechowywanie sekretów (haseł do bazy danych) — odpowiednik lekcji o publikacji APK/AAB z działu MAUI, tylko dla aplikacji webowej.

Teoria

Wdrażanie PARY Angular + ASP.NET Core. Skoro Twoja aplikacja składa się z DWÓCH projektów (frontend Angular, backend ASP.NET Core), wdrożenie wymaga zbudowania OBU: ng build kompiluje i optymalizuje projekt Angular do statycznych plików HTML/CSS/JS (folder dist/), a dotnet publish kompiluje backend C#. W szablonie "ASP.NET Core with Angular" z lekcji 3, backend jest skonfigurowany tak, by przy publikacji AUTOMATYCZNIE zbudować i dołączyć pliki frontendu (przez app.MapFallbackToFile("/index.html"), poznane w lekcji 3) — efekt: JEDEN, gotowy do wdrożenia zestaw plików, choć nadal warto rozumieć, co dzieje się z każdą częścią osobno.

Środowiska (Environments). ASP.NET Core rozróżnia środowiska uruchomieniowe — najczęściej Development (praca lokalna, szczegółowe komunikaty błędów ułatwiające debugowanie) i Production (środowisko "na żywo", gdzie szczegóły błędów są UKRYTE przed użytkownikiem ze względów bezpieczeństwa). Zmienna środowiskowa ASPNETCORE_ENVIRONMENT decyduje, które środowisko jest aktywne. W kodzie: if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error"); } — pokazuje przyjazną stronę błędu ZAMIAST pełnego stosu wywołań (stack trace) na produkcji, gdzie ujawnienie szczegółów wewnętrznej struktury kodu byłoby ryzykiem bezpieczeństwa.

Publikacja aplikacji. Polecenie dotnet publish -c Release -o ./publikacja (albo odpowiadająca opcja "Publikuj" w Visual Studio) kompiluje aplikację w trybie Release (zoptymalizowanym, bez informacji debugowania) i umieszcza WSZYSTKIE potrzebne pliki (skompilowany kod, zależności, pliki statyczne z wwwroot) w jednym folderze wynikowym, gotowym do skopiowania na serwer.

Serwer Kestrel a odwrotne proxy (reverse proxy). Wbudowany serwer Kestrel (używany podczas developmentu) MOŻE obsługiwać ruch produkcyjny bezpośrednio, ale standardową praktyką jest postawienie PRZED nim dodatkowego serwera — Nginx (na Linux) albo IIS (na Windows) — działającego jako odwrotne proxy: przyjmuje ruch z internetu, obsługuje HTTPS/certyfikaty, i przekazuje żądania do Kestrela działającego "za kulisami". To dodaje warstwę bezpieczeństwa i elastyczności (np. można postawić WIELE aplikacji za jednym serwerem proxy, każdą pod innym adresem).

Bezpieczne przechowywanie sekretów. Connection string do bazy produkcyjnej, hasła, klucze API — te dane NIGDY nie powinny trafić do repozytorium Git razem z kodem źródłowym (dokładnie ta sama zasada co przy kluczu podpisującym APK w dziale MAUI). Podczas developmentu służy do tego User Secrets (dotnet user-secrets set "Klucz" "wartosc" — zapisuje dane POZA folderem projektu, na dysku dewelopera). Na serwerze produkcyjnym typowo używa się zmiennych środowiskowych systemu operacyjnego albo dedykowanych usług zarządzania sekretami (np. Azure Key Vault).

Docker jako alternatywa wdrożenia (narzędzie konteneryzacji, przydatne też poza samym .NET) — zamiast ręcznie kopiować pliki na serwer, aplikację ASP.NET Core można "zapakować" w kontener Docker (przez Dockerfile, jak we wstępnym przykładzie z lekcji o narzędziach) i uruchomić IDENTYCZNIE na dowolnym serwerze obsługującym Dockera — eliminuje to problem "u mnie działało" przy przenoszeniu aplikacji między środowiskami.

Schemat

Kod źródłowy (Visual Studio, tryb Debug)
        │
        │  dotnet publish -c Release -o ./publikacja
        ▼
Folder z gotową aplikacją (skompilowaną, zoptymalizowaną)
        │
        ├── kopiowanie na serwer ────────────┐
        │                                     │
        ▼                                     ▼
Serwer produkcyjny                    ALBO: Docker
ASPNETCORE_ENVIRONMENT=Production     docker build -t moja-app .
        │                              docker run -d -p 80:80 moja-app
        ▼
Kestrel (wbudowany serwer)
        │
        │  za odwrotnym proxy
        ▼
Nginx / IIS (obsługa HTTPS, certyfikaty)
        │
        ▼
Internet → użytkownicy

Sekrety (connection string, hasła):
Development: dotnet user-secrets set "..." "..."  (POZA repozytorium Git)
Production: zmienne środowiskowe serwera / Azure Key Vault

Przykład z życia

Uczniowski projekt zaliczeniowy z INF.04 na etapie prezentacji często wymaga pokazania DZIAŁAJĄCEJ aplikacji, a nie tylko kodu otwartego w Visual Studio — dokładnie tak samo jak w dziale MAUI, gdzie ostatnia lekcja uczyła budowania gotowego pliku APK, tutaj uczysz się przygotować gotową, wdrożoną wersję aplikacji webowej, dostępną pod realnym adresem.

Program.cs

// Program.cs - rozróżnienie środowisk
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

string connectionString = builder.Configuration.GetConnectionString("MojaBaza")
    ?? throw new InvalidOperationException("Brak connection string 'MojaBaza'.");
builder.Services.AddDbContext<MojaBazaContext>(options => options.UseSqlite(connectionString));

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage(); // szczegółowe błędy - TYLKO lokalnie
}
else
{
    app.UseExceptionHandler("/Error"); // przyjazna strona błędu na produkcji
    app.UseHsts();                     // wymuszenie HTTPS w przeglądarce
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.MapRazorPages();

app.Run();

Terminal — User Secrets, publikacja, zmienne środowiskowe, Docker

# Terminal - publikacja aplikacji

# 1) Zapisanie sekretu TYLKO lokalnie (podczas developmentu, POZA repozytorium)
dotnet user-secrets init
dotnet user-secrets set "ConnectionStrings:MojaBaza" "Data Source=lokalna.db"

# 2) Publikacja w trybie Release
dotnet publish -c Release -o ./publikacja

# 3) Ustawienie zmiennej środowiskowej na serwerze produkcyjnym (Linux)
export ASPNETCORE_ENVIRONMENT=Production
export ConnectionStrings__MojaBaza="Data Source=/dane/produkcja.db"

# 4) Uruchomienie opublikowanej aplikacji
cd ./publikacja
dotnet MojaStronaApp.dll

# ---------------------------------------------------------------------

# Alternatywa: budowanie i uruchomienie w Dockerze
# Dockerfile:
# FROM mcr.microsoft.com/dotnet/aspnet:10.0
# WORKDIR /app
# COPY ./publikacja .
# EXPOSE 80
# ENTRYPOINT ["dotnet", "MojaStronaApp.dll"]

docker build -t moja-strona-app .
docker run -d -p 80:80 -e ASPNETCORE_ENVIRONMENT=Production moja-strona-app

# ---------------------------------------------------------------------

# Budowanie frontendu Angular (w folderze projektu klienckiego)
cd nazwaprojektu.client
ng build --configuration production
# Wynik: folder dist/ z zoptymalizowanymi, statycznymi plikami HTML/CSS/JS

# W szablonie "ASP.NET Core with Angular" polecenie "dotnet publish" w projekcie
# Server AUTOMATYCZNIE wywołuje "ng build" i kopiuje wynik do publikowanej wersji backendu -
# nie trzeba tego zwykle robić ręcznie, ale warto wiedzieć, że to się dzieje w tle.

Komentarz i wyjaśnienie kodu

Zauważ różnicę w Program.cs: connectionString = ... ?? throw new InvalidOperationException(...) — w środowisku PRODUKCYJNYM lepiej, żeby aplikacja w ogóle NIE WYSTARTOWAŁA, jeśli brakuje krytycznej konfiguracji (connection string), niż żeby wystartowała i zawiodła w nieprzewidywalny sposób przy pierwszej próbie dostępu do bazy.

Notacja ConnectionStrings__MojaBaza (z PODWÓJNYM podkreślnikiem) w zmiennej środowiskowej to sposób, w jaki ASP.NET Core mapuje zmienne środowiskowe na ZAGNIEŻDŻONĄ strukturę konfiguracji z appsettings.json (ConnectionStrings:MojaBaza) — dwukropek w JSON odpowiada podwójnemu podkreślnikowi w nazwie zmiennej środowiskowej, bo system operacyjny nie pozwala na dwukropki w nazwach zmiennych.

app.UseHsts() (HTTP Strict Transport Security) informuje przeglądarkę, żeby przy KOLEJNYCH wizytach automatycznie używała WYŁĄCZNIE HTTPS dla tej domeny, nawet jeśli użytkownik wpisze adres z http:// — dodatkowa warstwa ochrony przed atakami podsłuchującymi ruch sieciowy.

Ćwiczenie samodzielne

Opublikuj dowolny projekt z tego działu poleceniem dotnet publish -c Release -o ./publikacja, przejrzyj zawartość wygenerowanego folderu, i uruchom aplikację BEZPOŚREDNIO z tego folderu (dotnet NazwaProjektu.dll, bez otwierania Visual Studio) — sprawdź w przeglądarce, że działa identycznie jak podczas developmentu.

Zadania do pracy własnej

  1. Skonfiguruj dotnet user-secrets dla swojego projektu z bazą danych (przenieś connection string z appsettings.json do sekretów) i sprawdź, że aplikacja nadal poprawnie się łączy z bazą.

  2. Dodaj do Program.cs rozróżnienie środowisk jak w przykładzie (UseDeveloperExceptionPage vs UseExceptionHandler) i celowo wywołaj błąd w kodzie (np. dzielenie przez zero) — porównaj, jak wygląda strona błędu w trybie Development a jak wyglądałaby (przygotuj prostą stronę /Error) w trybie Production.

  3. Napisz Dockerfile dla swojego projektu (analogicznie do przykładu), zbuduj obraz i uruchom kontener lokalnie z odpowiednio ustawioną zmienną środowiskową ASPNETCORE_ENVIRONMENT=Production, sprawdzając w przeglądarce, że opublikowana w kontenerze aplikacja działa identycznie jak wersja uruchomiona bezpośrednio.

Typowe błędy

Zostawienie ASPNETCORE_ENVIRONMENT=Development na serwerze produkcyjnym — ujawnia szczegółowe komunikaty błędów (w tym fragmenty kodu i struktury bazy danych) prawdziwym użytkownikom, co jest poważnym ryzykiem bezpieczeństwa.

Umieszczanie hasła do bazy produkcyjnej bezpośrednio w appsettings.json commitowanym do Gita — dokładnie ten sam błąd co zgubienie/upublicznienie klucza podpisującego APK w dziale MAUI; zawsze używać User Secrets (development) i zmiennych środowiskowych/managera sekretów (produkcja).

Publikowanie w trybie Debug zamiast Release — kod skompilowany w trybie Debug jest wolniejszy i zawiera niepotrzebne na produkcji informacje diagnostyczne.

Nawiązanie do egzaminu zawodowego

To realizacja praktycznego aspektu wdrażania aplikacji, uzupełniającego INF.04.7 o element cyklu życia oprogramowania wspólny dla wszystkich rodzajów aplikacji (podobnie jak lekcja o publikacji APK/AAB w dziale MAUI) — a jednocześnie nawiązanie do INF.04.7.1 (Docker jako narzędzie procesu wdrażania, wymienione wprost w podstawie programowej jako narzędzie procesu wdrażania). Ostatnia lekcja tego działu łączy wszystko w finalny projekt i zadanie kontrolne.