Minimal API

Статичні Активи: MapStaticAssets (ASP.NET Core 9.0)

Впродовж багатьох років в ASP.NET Core стандартом для роздачі файлів був підхід із UseStaticFiles(), який ми розглянули в попередньому розділі. Він працює добре: браузер запитує файл з папки wwwroot, сервер його знаходить (якщо він є) і відправляє.

Статичні Активи: MapStaticAssets (ASP.NET Core 9.0)

Впродовж багатьох років в ASP.NET Core стандартом для роздачі файлів був підхід із UseStaticFiles(), який ми розглянули в попередньому розділі. Він працює добре: браузер запитує файл з папки wwwroot, сервер його знаходить (якщо він є) і відправляє.

Але у 2024 році разом із випуском ASP.NET Core 9.0 компанія Microsoft вирішила повністю переосмислити роботу зі статикою. Вони представили революційний метод — MapStaticAssets().

Чому знадобився новий підхід? Оптимізація! Сучасні веб-додатки повинні завантажуватися блискавично.

Проблеми UseStaticFiles

У класичного підходу є кілька суттєвих недоліків для високо навантажених додатків:

  1. Звернення до диска (Disk I/O): На кожен запит (навіть якщо файл кешується) Middleware UseStaticFiles намагається перевірити фізичну наявність файлу на жорсткому диску. Диск завжди повільніший за оперативну пам'ять.
  2. Кешування (Cache Busting): Якщо ви зміните app.js і викладете його на сервер, браузер користувача може не знати про це і використовувати стару закешовану версію з локального диска. Раніше для вирішення цього використовували хелпери (в Razor Pages), які додавали щось на кшталт ?v=xyz123 до URL, але в Minimal APIs з цим доводилось працювати вручну.
  3. Стиснення (Compression): Файли часто можна стиснути за алгоритмами GZIP або Brotli, щоб вони стали меншими і вантажилися швидше. Але стиснення "на льоту" (під час запиту) забирає ресурси процесора (CPU).

Оптимізація під час збірки (Build-Time Optimization)

MapStaticAssets() кардинально відрізняється своїм підходом. Більшу частину роботи він робить не тоді, коли користувач запитує сторінку (Run-time), а тоді, коли ви натискаєте кнопку БІЛД (Build-time) у вашому IDE!

Під час створення білду або публікації проєкту (.NET SDK), фреймворк автоматично:

  1. Знаходить всі файли в wwwroot.
  2. Рахує криптографічний хеш для кожного файлу (унікальний код на основі його вмісту).
  3. Стискає текстові файли (JS, CSS, HTML, SVG) одразу у два попередньо збережені формати: .gz (GZIP) та .br (Brotli).

Після цього він генерує оптимізовану "карту файлів" прямо в кінцеву збірку вашого коду (Manifest).

Використання MapStaticAssets()

У коді все виглядає навіть простіше, ніж зі старою системою.

Program.cs
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// Використовуємо новий революційний endpoint-роутинг для файлів (ASP.NET Core 9.0+)
app.MapStaticAssets();

app.MapGet("/", () => "Hello from API");

app.Run();

Анатомія коду:

  • Ми викликаємо MapStaticAssets() на екземплярі app.
  • Зверніть увагу: ми використовуємо Map... (як для звичайних роутів MapGet), а не Use.... Це означає, що статичні активи тепер тісно інтегровані в єдину систему Endpoint Routing.
  • Сервер завантажує свою "карту файлів" в оперативну пам'ять при старті. Коли приходить запит на /js/app.js, серверу не потрібно йти на жорсткий диск, щоб перевірити, чи там є файл! Він дивиться в свою таблицю в пам'яті (пам'ять у 100 000 разів швидша за SSD), одразу знаходить файл і знає, що до нього існують готові стиснуті версії.

Вбудований Fingerprinting (Відбитки)

Оскільки під час збірки для кожного файлу генерується унікальний хеш, Microsoft додала механізм, який називається "Кешування з відбитками пальців" (Fingerprinting).

Коли файл публікується, його унікальний хеш можна включити безпосередньо у посилання на нього. Наприклад, замість /css/main.css, браузер буде підвантажувати щось на зразок /css/main.{hash}.css.

Що це дає?

  1. Ми можемо наказати браузеру: "Кешуй цей файл НАЗАВЖДИ (на рік)!". Це робить завантаження наступних сторінок миттєвим.
  2. Але що, якщо нам треба оновити дизайн? Ми просто змінюємо main.css і компілюємо проєкт.
  3. Хеш змінюється. Новий URL стає /css/main.{newhash}.css. Сервер висилає нову сторінку. Браузер бачить, що URL файлу змінився, і завантажить його наново, не використовуючи старий кеш!

Якщо ви хочете згенерувати посилання на такий ресурс з коду або з інших endpoint-ів, зазвичай використовують LinkGenerator або вбудовані хелпери в Razor/Blazor. Цей процес виходить за рамки базових Minimal APIs, але важливо розуміти, що файли тепер захищені від "застрягання в старому кеші браузера".

Вбудоване стиснення (Brotli & Gzip)

Завдяки тому, що SDK вже створив попередньо стиснуті (pre-compressed) версії файлів під час збірки, MapStaticAssets не витрачає ресурси процесора сервера на архівацію.

  1. Браузер надсилає запит і каже: Accept-Encoding: gzip, deflate, br. (Це означає: "я вмію розпаковувати Brotli та GZIP").
  2. Сервер бачить br (Brotli — найкраще стиснення), перевіряє свою таблицю в пам'яті: "Ага, в мене вже є готовий файл розміром не 150 КБ, а 30 КБ в пам'яті". І віддає браузеру ці 30 КБ.
  3. Профіт: зниження мережевого трафіку до 80% і зменшення навантаження на процесори серверів.

Коли потрібно повертатися до UseStaticFiles?

MapStaticAssets — це майбутнє. Якщо ви стартуєте новий проєкт на .NET 9 — завжди використовуйте його. Однак, є кілька специфічних сценаріїв, де вам все ж доведеться використовувати стару систему:

  1. Динамічні файли (User Uploads): MapStaticAssets знає лише про ті файли, які були в wwwroot на момент компіляції додатка. Якщо ваш додаток дозволяє користувачеві завантажити фотографію аватарки (яка фізично зберігається на диску), MapStaticAssets її ніколи не знайде і віддасть 404 помилку. Для роздачі "живих" файлів, завантажених під час роботи, вам доведеться додати app.UseStaticFiles() (зазвичай з вказуванням іншої папки для даних користувачів).
  2. Перегляд директорій (Directory Browsing): Наразі MapStaticAssets не підтримує красивий перегляд списку файлів у системі.
Program.cs
var app = WebApplication.Create();

// Для наших системних стилів та JS, згенерованих під час білду (Швидко!)
app.MapStaticAssets();

// Для аватарів користувачів, завантажених під час роботи (Динамічно!)
app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider(Path.Combine(builder.Environment.ContentRootPath, "UserUploads")),
    RequestPath = "/avatars"
});

app.Run();

Практичні завдання

Теорія Оптимізації Проаналізуйте матеріал та самостійно перелічіть (у форматі списку або подумки) 3 ключові відмінності між UseStaticFiles() та MapStaticAssets(). Сфокусуйте вашу увагу на тому, коли (в який момент часу) сервер дізнається інформацію про вміст файлу в першому і другому випадках.
Copyright © 2026