Lekcja 13. Sesje i ciasteczka w ASP.NET Core
ŚredniPo co się tego uczymy?
HTTP jest z natury "bezstanowy" — serwer normalnie NIE pamięta niczego między jednym żądaniem a drugim, każde żądanie traktowane jest jak od zupełnie nowego, nieznajomego użytkownika. Ale przecież aplikacje muszą pamiętać, że ktoś jest zalogowany, co ma w koszyku zakupowym, jakie wybrał ustawienia — dokładnie ten sam problem, który w dziale PHP rozwiązywały sesje i ciasteczka. Ta lekcja pokazuje, jak identyczny mechanizm działa w ASP.NET Core.
Teoria
Ciasteczka (cookies) to małe fragmenty danych zapisywane PRZEZ SERWER w przeglądarce użytkownika — przeglądarka automatycznie odsyła je z powrotem do serwera przy KAŻDYM kolejnym żądaniu do tej samej domeny. To fundamentalny mechanizm, dzięki któremu serwer w ogóle może "rozpoznać" powracającego użytkownika. W ASP.NET Core: Response.Cookies.Append("klucz", "wartosc", opcje) zapisuje ciasteczko, Request.Cookies["klucz"] odczytuje jego wartość (zwraca null, jeśli nie istnieje), Response.Cookies.Delete("klucz") usuwa.
Sesje (session) to mechanizm WYŻSZEGO poziomu, zbudowany NA BAZIE ciasteczek: serwer generuje unikalny identyfikator sesji, zapisuje go w JEDNYM ciasteczku (.AspNetCore.Session), a WSZYSTKIE faktyczne dane (mogące być znacznie większe i bardziej złożone niż w zwykłym ciasteczku) trzyma po stronie SERWERA, powiązane z tym identyfikatorem. Przeglądarka "nie widzi" samych danych sesji — widzi tylko krótki identyfikator, który za każdym razem odsyła z powrotem.
Konfiguracja sesji wymaga dwóch kroków w Program.cs: rejestracji usługi (builder.Services.AddDistributedMemoryCache() + builder.Services.AddSession()) i włączenia middleware (app.UseSession(), wywołane PRZED app.MapRazorPages()/app.MapControllers() — kolejność ma znaczenie!). Dostęp do sesji w kodzie strony/kontrolera: HttpContext.Session.SetString("klucz", wartosc) / GetString("klucz") (dla liczb: SetInt32/GetInt32) — sesja natywnie obsługuje TYLKO stringi i liczby całkowite; dla bardziej złożonych obiektów trzeba je zserializować (np. do JSON) przed zapisem.
| Mechanizm | Gdzie przechowywane dane | Typowe zastosowanie |
|---|---|---|
| Ciasteczko | w przeglądarce użytkownika | małe, długotrwałe dane: "zapamiętaj mnie", preferowany język, zgoda na cookies |
| Sesja | na serwerze (ciasteczko trzyma tylko identyfikator) | dane tymczasowe, ważne TYLKO podczas jednej wizyty: zawartość koszyka, stan logowania |
Czas życia. Ciasteczko może mieć ustawioną datę wygaśnięcia (CookieOptions { Expires = DateTimeOffset.Now.AddDays(30) }) — przetrwa nawet zamknięcie przeglądarki. Sesja domyślnie wygasa po określonym czasie BEZCZYNNOŚCI (options.IdleTimeout = TimeSpan.FromMinutes(20)) — jeśli użytkownik nic nie robi przez ten czas, dane sesji są tracone.
RODO i zgoda na cookies. W prawdziwych, publicznych aplikacjach webowych w Unii Europejskiej użycie ciasteczek (poza absolutnie niezbędnymi do działania strony) wymaga jawnej zgody użytkownika — stąd powszechne banery "Ta strona używa plików cookie" na niemal każdej stronie internetowej.
Schemat
Pierwsza wizyta użytkownika
│
▼
Serwer tworzy sesję, generuje ID (np. "a1b2c3...")
│
▼
Response: Set-Cookie: .AspNetCore.Session=a1b2c3...
│
▼
Przeglądarka zapisuje ciasteczko
│
│ (kolejne żądanie, np. "Dodaj do koszyka")
▼
Request: Cookie: .AspNetCore.Session=a1b2c3...
│
▼
Serwer odczytuje ID sesji z ciasteczka
│
▼
HttpContext.Session → dane TEJ konkretnej sesji (przechowywane na serwerze)
│
▼
HttpContext.Session.SetString("koszyk", "...") - zapis
HttpContext.Session.GetString("koszyk") - odczyt przy kolejnym żądaniu
Przykład z życia
Koszyk zakupowy w sklepie internetowym pamięta dodane produkty przez cały czas przeglądania sklepu (dzięki sesji), a osobne ciasteczko może zapamiętać, że użytkownik już RAZ zaakceptował politykę prywatności, żeby baner z pytaniem o zgodę nie pojawiał się przy KAŻDEJ kolejnej wizycie przez najbliższe 30 dni.
Pages/Koszyk.cshtml
<!-- Pages/Koszyk.cshtml -->
@page
@model KoszykModel
<h1>Twój koszyk</h1>
@if (Model.LiczbaProduktow == 0)
{
<p>Koszyk jest pusty.</p>
}
else
{
<p>Liczba produktów w koszyku: @Model.LiczbaProduktow</p>
}
<form method="post">
<button type="submit">Dodaj przykładowy produkt do koszyka</button>
</form>
Pages/Koszyk.cshtml.cs + Program.cs
// Pages/Koszyk.cshtml.cs
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace MojaStronaApp.Pages;
public class KoszykModel : PageModel
{
private const string KluczSesji = "liczba_produktow_koszyk";
public int LiczbaProduktow { get; set; }
public void OnGet()
{
LiczbaProduktow = HttpContext.Session.GetInt32(KluczSesji) ?? ;
}
public void OnPost()
{
int aktualna = HttpContext.Session.GetInt32(KluczSesji) ?? ;
aktualna++;
HttpContext.Session.SetInt32(KluczSesji, aktualna);
LiczbaProduktow = aktualna;
}
}
// ---------------------------------------------------------------------
// Program.cs - konfiguracja sesji
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddDistributedMemoryCache();
builder.Services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(20);
options.Cookie.HttpOnly = true;
});
var app = builder.Build();
app.UseStaticFiles();
app.UseRouting();
app.UseSession(); // WAŻNE: przed MapRazorPages, po UseRouting
app.MapRazorPages();
app.Run();
Komentarz i wyjaśnienie kodu
HttpContext.Session.GetInt32(KluczSesji) ?? 0 — GetInt32 zwraca int? (nullable), bo klucz może jeszcze NIE istnieć w sesji (pierwsza wizyta) — operator ?? zapewnia wtedy sensowną wartość domyślną (0) zamiast null.
W Program.cs, options.Cookie.HttpOnly = true to WAŻNE ustawienie bezpieczeństwa — sprawia, że ciasteczko sesji jest NIEDOSTĘPNE dla JavaScriptu działającego na stronie (np. przez document.cookie), co utrudnia kradzież identyfikatora sesji przez złośliwy skrypt (atak XSS). Kolejność app.UseRouting() → app.UseSession() → app.MapRazorPages() w potoku przetwarzania żądań jest ISTOTNA — UseSession() musi wykonać się PRZED jakimkolwiek kodem próbującym z sesji skorzystać.
Ćwiczenie samodzielne
Zbuduj powyższy przykład, uruchom aplikację i sprawdź w przeglądarce: kliknij kilka razy przycisk dodawania do koszyka, odśwież stronę (liczba powinna pozostać), a następnie otwórz stronę w oknie PRYWATNYM/incognito — sprawdź, że tam licznik zaczyna od zera (bo to zupełnie NOWA, niezależna sesja).
Zadania do pracy własnej
Dodaj przycisk "Wyczyść koszyk" wywołujący
HttpContext.Session.Remove(KluczSesji), zerujący licznik produktów w koszyku.Zamiast prostego licznika, przechowuj w sesji LISTĘ nazw dodanych produktów — ponieważ sesja natywnie obsługuje tylko stringi/liczby, zserializuj listę do JSON przed zapisem (
System.Text.Json.JsonSerializer.Serialize) i zdeserializuj przy odczycie, dokładnie jak w lekcji o plikach JSON z działu MAUI.Zbuduj mechanizm "Zapamiętaj moje imię" — formularz z jednym polem tekstowym, gdzie po wysłaniu imię zapisuje się w CIASTECZKU (nie sesji) z terminem ważności 30 dni, a strona główna wyświetla "Witaj ponownie, [imię]!" przy KOLEJNYCH wizytach, nawet po zamknięciu i ponownym otwarciu przeglądarki (co odróżnia to od sesji, która wygasłaby po pewnym czasie bezczynności).
Typowe błędy
Wywołanie app.UseSession() PO app.MapRazorPages()/app.MapControllers() w Program.cs — powoduje, że sesja nie jest jeszcze dostępna, gdy strona próbuje z niej skorzystać, i próba odczytu/zapisu zakończy się błędem albo brakiem działania.
Próba zapisania w sesji złożonego obiektu C# bezpośrednio (np. HttpContext.Session.Set("produkt", produkt) bez wcześniejszej serializacji) — natywne API sesji obsługuje tylko string, int i tablice bajtów; dla obiektów złożonych trzeba samodzielnie zserializować (JSON) i zapisać jako string.
Przechowywanie w sesji DANYCH WRAŻLIWYCH bez zabezpieczeń (np. hasła w czystym tekście) — nawet jeśli dane sesji są po stronie serwera (bezpieczniejsze niż ciasteczka), to wciąż nie jest miejsce na przechowywanie haseł czy innych krytycznych sekretów bez dodatkowego szyfrowania.
Nawiązanie do egzaminu zawodowego
To bezpośrednia realizacja INF.04.7.3 "mechanizmy sesji i ciasteczek", identyczna koncepcyjnie z analogicznym tematem w dziale PHP (INF.03.5.4.7), tylko realizowana natywnym mechanizmem ASP.NET Core zamiast $_SESSION/setcookie(). Kolejna lekcja rozszerza to o pełny mechanizm logowania i autoryzacji, który w praktyce opiera się właśnie na sesjach/ciasteczkach do śledzenia stanu zalogowania użytkownika.