Веб працює поверх протоколу HTTP, який за своєю природою не має стану (Stateless). Це означає, що сервер не пам'ятає вас між першим і другим запитом. Для сервера кожен запит — ніби від абсолютно нового користувача.
Але реальні додатки потребують "пам'яті". Кошик покупок, статус авторизації, вибрана мова — усе це Стан додатку (Application State).
В ASP.NET Core існують різні способи зберігати дані, залежно від того, як довго вони мають жити: від мілісекунд (протягом одного запиту) до місяців (на комп'ютері клієнта).
У цьому розділі ми розглянемо ультракороткострокове зберігання (HttpContext.Items) та довгострокове зберігання на клієнті (Cookies).
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).Якщо Items вмирає через мілісекунди, то Cookies можуть жити місяцями. Це маленькі (до 4 КБ) фрагменти тексту, які сервер просить браузер зберегти у себе і потім відправляти назад з кожним наступним запитом.
Cookies ідеально підходять для налаштувань користувача (тема: світла/темна) або токенів сесії/аутентифікації.
Set-Cookie: theme=dark.theme=dark.Cookie: theme=dark.В 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 (зберігання на комп'ютері клієнта). Але цей підхід має свої обмеження: