Управління станом: HttpContext.Items та Cookies
Управління станом: HttpContext.Items та Cookies
Веб працює поверх протоколу HTTP, який за своєю природою не має стану (Stateless). Це означає, що сервер не пам'ятає вас між першим і другим запитом. Для сервера кожен запит — ніби від абсолютно нового користувача.
Але реальні додатки потребують "пам'яті". Кошик покупок, статус авторизації, вибрана мова — усе це Стан додатку (Application State).
В ASP.NET Core існують різні способи зберігати дані, залежно від того, як довго вони мають жити: від мілісекунд (протягом одного запиту) до місяців (на комп'ютері клієнта).
У цьому розділі ми розглянемо ультракороткострокове зберігання (HttpContext.Items) та довгострокове зберігання на клієнті (Cookies).
1. Короткостроковий стан: HttpContext.Items
Життєвий цикл колекції Items дорівнює життевому циклу одного HTTP-запиту.
Як тільки сервер відправив фінальну відповідь клієнту — колекція знищується назавжди.
Навіщо це потрібно?
Уявіть, що у вас є складний конвеєр (Pipeline) із кількох Middleware і одного Endpoint.
Перший Middleware перевіряє JWT токен і дістає з бази даних об'єкт User (що займає час). Endpoint також потребує цього об'єкта User, щоб зрозуміти, яку інформацію повертати.
Щоб не йти в базу даних вдруге, Middleware просто "кладе" об'єкт User у спільну кишеню поточного запиту — HttpContext.Items, а Endpoint його звідти "дістає".
Приклад використання
var app = WebApplication.Create();
// 1. Middleware "видобуває" дані
app.Use(async (context, next) =>
{
// Симуляція запиту до БД або якоїсь важкої логіки
var correlationId = Guid.NewGuid().ToString();
// Зберігаємо дані в Items. Ключ - це рядок (або Type), Значення - будь-який object.
context.Items["TrackingId"] = correlationId;
await next(); // Передаємо керування далі
});
// 2. Інший Middleware може це прочитати
app.Use(async (context, next) =>
{
if (context.Items.TryGetValue("TrackingId", out var trackingId))
{
Console.WriteLine($"Проходимо Middleware 2 з ID: {trackingId}");
}
await next();
});
// 3. Endpoint отримує дані і відправляє клієнту
app.MapGet("/my-id", (HttpContext context) =>
{
// Завжди перевіряємо, чи є дані (out var) або кастуємо (as string)
var myId = context.Items["TrackingId"] as string;
return $"Ваш унікальний ID для цього запиту: {myId}";
});
app.Run();
Анатомія коду:
- Колекція
Items— це стандартнийIDictionary<object, object?>. Оскільки значення є звичайнимobject, під час зчитування ми завжди маємо використовувати приведення типів (casting):as stringабо(string). - Цей словник не потокобезпечний (Not Thread-Safe) для запису з кількох паралельних потоків одного запиту, хоча такі ситуації трапляються рідко.
2. Довгостроковий стан на клієнті: Cookies (Куки)
Якщо Items вмирає через мілісекунди, то Cookies можуть жити місяцями. Це маленькі (до 4 КБ) фрагменти тексту, які сервер просить браузер зберегти у себе і потім відправляти назад з кожним наступним запитом.
Cookies ідеально підходять для налаштувань користувача (тема: світла/темна) або токенів сесії/аутентифікації.
Як працюють Cookies?
- Запит: Клієнт просить сторінку.
- Відповідь сервера: Сервер повертає сторінку + HTTP-заголовок
Set-Cookie: theme=dark. - Браузер: Зберігає у себе
theme=dark. - Наступний запит: Клієнт хоче зайти на іншу сторінку. Браузер сам додає HTTP-заголовок
Cookie: theme=dark. - Сервер: Читає його і малює темну сторінку.
Встановлення та читання Cookies
В ASP.NET Core ми працюємо з Cookies через об'єкти context.Response.Cookies (для запису) та context.Request.Cookies (для читання).
var app = WebApplication.Create();
// Зчитування
app.MapGet("/preferences", (HttpContext context) =>
{
// Намагаємося прочитати куку "theme". Якщо її немає, беремо "light"
string theme = context.Request.Cookies["theme"] ?? "light";
return $"Поточна тема: {theme}";
});
// Встановлення
app.MapGet("/preferences/{theme}", (HttpContext context, string theme) =>
{
// Створюємо налаштування для безпеки (важливо!)
var cookieOptions = new CookieOptions
{
Expires = DateTimeOffset.UtcNow.AddMonths(1), // Життєвий цикл 1 місяць
HttpOnly = true, // JavaScript на клієнті не зможе прочитати куку
Secure = true, // Кука передаватиметься лише через HTTPS
SameSite = SameSiteMode.Strict // Захист від CSRF атак
};
// Відправляємо браузеру команду зберегти куку
context.Response.Cookies.Append("theme", theme, cookieOptions);
return $"Тему успішно змінено на {theme}";
});
// Видалення
app.MapGet("/forget", (HttpContext context) =>
{
// Сервер не може фізично видалити файл на комп'ютері клієнта.
// Він просто відправляє куку з такою ж назвою і терміном дії "в минулому".
context.Response.Cookies.Delete("theme");
return "Тему скинуто до стандартної";
});
app.Run();
Анатомія коду:
У класі CookieOptions є критично важливі налаштування безпеки:
HttpOnly: Встановлюйте завждиtrue, якщо ця кука потрібна тільки серверу (наприклад, токен входу). Якщо хакери знайдуть XSS вразливість (зможуть виконати свій JS код на вашому сайті), вони не вкрадуть токен черезdocument.cookie.Secure: Гарантує, що браузер взагалі не відправить вашу куку, якщо користувач сидить у публічному Wi-Fi кафе і підключився до вас через незахищенийhttp://.SameSite: Забороняє браузеру надсилати куку, якщо запит до вашого сервера був ініційований з іншого домену (наприклад, хтось створив кнопку-пастку "Тицьни сюди" на своєму сайті). Це захист від Cross-Site Request Forgery.
Практичні завдання
DateTime.UtcNow у context.Items["StartTime"].
Створіть роут /time, який читає це значення з Items, вираховує скільки мілісекунд пройшло від StartTime до моменту генерації відповіді, і повертає цей час клієнту.GET /hello.
Коли користувач вперше заходить на нього, перевірте наявність куки HasVisited.
Якщо її немає: поверніть "Ласкаво просимо, новачок!" та встановіть куку HasVisited=true з терміном дії 24 години.
Якщо кука є: поверніть "З поверненням!".
(Для тестування використовуйте вкладку Application -> Cookies в DevTools F12 вашого браузера).Логування: Serilog та Middleware
Вбудована консоль ASP.NET Core відмінно підходить для розробки. Проте в Production вам потрібен надійний спосіб зберігати логи: записувати їх у текстові файли з ротацією (новий файл щодня), відправляти в базу даних або надсилати у хмарні сервіси на кшталт DataDog, SEQ чи Application Insights.
Стан сесії: Sessions
У попередньому розділі ми розглянули Cookies (зберігання на комп'ютері клієнта). Але цей підхід має свої обмеження: