Стан сесії: Sessions
Стан сесії: Sessions
У попередньому розділі ми розглянули Cookies (зберігання на комп'ютері клієнта). Але цей підхід має свої обмеження:
- Обмеження об'єму: Ви не можете зберегти більше 4 КБ даних у куках. Великий кошик покупок інтернет-магазину туди просто не поміститься.
- Мережевий трафік: Якщо ви збережете в куках 3 КБ, браузер буде "тягати" ці додаткові 3 КБ туди-сюди з кожним запитом (навіть запитом за аватаркою користувача), що уповільнює мережу.
- Безпека: Зберігати ціни товарів або знижки на стороні клієнта занадто небезпечно, адже дані легко скомпрометувати.
Тут на допомогу приходять Сесії (Sessions) — стан, який зберігається на стороні сервера.
Як працює сесія?
- Сервер виділяє "скриньку пам'яті" для конкретного користувача і дає їй унікальний номер (уявімо, #A105).
- Сервер зберігає самі дані (кошик покупок з товарами) у цій "скриньці" на своєму жорсткому диску або в оперативній пам'яті (чи в Redis).
- Сервер відправляє клієнту лише одну крихітну Cookie-файл, яка містить тільки номер скриньки:
SessionId=A105. - Коли клієнт робить наступний запит, він каже: "Я клієнт №A105". Сервер дістає з пам'яті дані зі скриньки A105.
Таким чином, сервер пам'ятає складні дані, а клієнт носить із собою лише "ключик" від них.
Налаштування Middleware для сесій
На відміну від Items або звичайних Cookies, підтримка сесій не увімкнена за замовчуванням. Вона вимагає трохи оперативної пам'яті, тому Microsoft зробила її опціональною.
Щоб сесія працювала, нам потрібні дві речі:
- Вказати, де саме на сервері фізично зберігатимуться "скриньки" (ми використаємо найпростіший варіант — оперативну пам'ять сервера: Memory Cache).
- Увімкнути сервіси управління сесіями та сам Middleware в конвеєр.
var builder = WebApplication.CreateBuilder(args);
// Крок 1. Підключаємо сховище (обов'язкова умова для 세сії)
builder.Services.AddDistributedMemoryCache();
// Крок 2. Налаштовуємо параметри самої сесії
builder.Services.AddSession(options =>
{
// Через скільки часу БЕЗДІЯЛЬНОСТІ користувача сесія знищиться (дефолт: 20 хв)
options.IdleTimeout = TimeSpan.FromMinutes(30);
// Налаштування для того самого "ключика", який полетить клієнту
options.Cookie.HttpOnly = true; // Захист від XSS
options.Cookie.IsEssential = true; // Означає: "Ця кука є критичною і не потребує Cookie Banner за GDPR"
});
var app = builder.Build();
// Крок 3. Додаємо Middleware в конвеєр запитів (ПЕРЕД вашими маршрутами!)
app.UseSession();
// ... тут ваші MapGet / MapPost ...
app.Run();
Робота з інтерфейсом ISession
Дані сесії живуть в об'єкті HttpContext.Session. Інтерфейс має базові методи для читання та запису примітивів:
SetInt32(),GetInt32()SetString(),GetString()Set(),Get()(для масивівbyte[])
Давайте створимо роут лічильника відвідувань:
app.MapGet("/visits", (HttpContext context) =>
{
// 1. Намагаємося дістати значення з ключа "VisitsCount"
int visits = context.Session.GetInt32("VisitsCount") ?? 0;
// 2. Збільшуємо на 1
visits++;
// 3. Зберігаємо назад у сесію
context.Session.SetInt32("VisitsCount", visits);
return $"Це ваш візит №: {visits} (протягом цієї сесії)";
});
Спробуйте зробити цей запит у браузері кілька разів, а потім відкрити режим "Інкогніто", в якому куки не зберігаються, і зробити запит там. Рахунок почнеться з 1, адже це вже нова сесія.
Серіалізація складних об'єктів у сесію
У вас немає методу SetObject(). А що, якщо ми хочемо берегти список товарів (клас CartItem)?
Оскільки сесія може зберігатися не тільки в пам'яті, але й в Redis (базі даних для кешу), дані мають бути перетворені у формат, який можна зберегти на диск або передати по мережі — зазвичай це JSON рядок.
Для зручності C#-розробники завжди пишуть Extension-методи (методи розширення).
using System.Text.Json;
public static class SessionExtensions
{
// Отримуємо об'єкт (Десеріалізація String -> Object)
public static T? GetFromJson<T>(this ISession session, string key)
{
var json = session.GetString(key);
return json == null ? default : JsonSerializer.Deserialize<T>(json);
}
// Зберігаємо об'єкт (Серіалізація Object -> String)
public static void SetAsJson<T>(this ISession session, string key, T value)
{
var json = JsonSerializer.Serialize(value);
session.SetString(key, json);
}
}
Тепер ми можемо використовувати їх в наших Endpoint'ах так, ніби вони були там "з коробки":
public record CartItem(int ProductId, int Quantity);
app.MapPost("/cart/add", (HttpContext context, int productId) =>
{
// Дістаємо існуючий список з сесії (або створюємо новий порожній, якщо нічого ще немає)
var cart = context.Session.GetFromJson<List<CartItem>>("MyCart") ?? new List<CartItem>();
cart.Add(new CartItem(productId, 1));
// Зберігаємо оновлений список у сесію
context.Session.SetAsJson("MyCart", cart);
return Results.Ok($"Додано! Всього товарів: {cart.Count}");
});
У нашому прикладі
AddDistributedMemoryCacheозначає, що всі сесії живуть в оперативній пам'яті одного конкретного сервера (на якому запущено вашу програму). Якщо ваш магазин стане популярним і ви запустите 3 екземпляри (копії) сервера через Load Balancer, пам'ять у кожного буде своя. Користувач покладе товар в кошик на Сервері 1, а наступний запит балансувальник може надіслати йому на Сервер 2, де його кошик порожній! Щоб вирішити цю проблему "втрати сесії",AddDistributedMemoryCacheпросто замінюють наAddStackExchangeRedisCache(...)з підключенням до зовнішньої In-Memory бази даних Redis, звідки всі 3 сервери братимуть спільну інформацію про сесії. А решта коду (Endpoint,SetAsJson) залишиться без жодних змін!
Практичні завдання
AddDistributedMemoryCache і зберігаєте в сесії важливі дані користувача.
Раптом у дата-центрі на 5 секунд зникає живлення і ваш додаток перезавантажується.
Що станеться з даними у сесії поточних користувачів і що побачать клієнти при наступному запиті? Чому так відбудеться?Кошик інтернет-магазину Реалізуйте логіку попереднього прикладу повністю.
- Не забудьте підключити сервіси
AddDistributedMemoryCache()таAddSession(), і використатиapp.UseSession(). - Скопіюйте розширення
SessionExtensions.cs. - Створіть 2 Endpoint'и:
POST /cart?productId=5(який додає товар або збільшує йогоQuantity, якщо такий вже є в спискуcart). - Створіть другий Endpoint
GET /cartякий буде повертати користувачу його поточний JSON список товарів з кошика, або порожній масив[], якщо він нічого не додавав.
Управління станом: HttpContext.Items та Cookies
Веб працює поверх протоколу HTTP, який за своєю природою не має стану (Stateless). Це означає, що сервер не пам'ятає вас між першим і другим запитом. Для сервера кожен запит — ніби від абсолютно нового користувача.
Структура проєкту: від хаосу до архітектури
Еволюція організації Minimal API проєкту: від одного файлу до Extension Methods, Route Groups, Feature Folders та Vertical Slice Architecture.