У попередньому розділі ми розглянули Cookies (зберігання на комп'ютері клієнта). Але цей підхід має свої обмеження:
Тут на допомогу приходять Сесії (Sessions) — стан, який зберігається на стороні сервера.
SessionId=A105.Таким чином, сервер пам'ятає складні дані, а клієнт носить із собою лише "ключик" від них.
На відміну від Items або звичайних Cookies, підтримка сесій не увімкнена за замовчуванням. Вона вимагає трохи оперативної пам'яті, тому Microsoft зробила її опціональною.
Щоб сесія працювала, нам потрібні дві речі:
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.POST /cart?productId=5 (який додає товар або збільшує його Quantity, якщо такий вже є в списку cart).GET /cart який буде повертати користувачу його поточний JSON список товарів з кошика, або порожній масив [], якщо він нічого не додавав.Управління станом: HttpContext.Items та Cookies
Веб працює поверх протоколу HTTP, який за своєю природою не має стану (Stateless). Це означає, що сервер не пам'ятає вас між першим і другим запитом. Для сервера кожен запит — ніби від абсолютно нового користувача.
Структура проєкту: від хаосу до архітектури
Еволюція організації Minimal API проєкту: від одного файлу до Extension Methods, Route Groups, Feature Folders та Vertical Slice Architecture.