Маршрутизація в ASP.NET Core: Основи
Маршрутизація в ASP.NET Core: Основи
Маршрутизація (Routing) — це фундаментальний механізм будь-якого веб-фреймворку. Вона відповідає за аналіз вхідного HTTP-запиту (наприклад, GET /users/1) та виклик відповідної ділянки коду (обробника) для формування відповіді.
У цьому розділі ми розберемо анатомію маршрутизації в ASP.NET Core Minimal APIs, навчимося створювати кінцеві точки (Endpoints) та працювати з параметрами маршрутів.
Що таке маршрутизація і навіщо вона потрібна?
Уявіть великий офісний центр. Коли ви заходите всередину, ви бачите турнікет і рецепцію зі списком компаній на різних поверхах. HTTP-запит — це ви. Маршрутизатор (Router) — це рецепція. Вона дивиться куди вам треба (/sales або /support) і направляє вас до потрібного кабінету (обробника).
Без маршрутизації нам довелося б вручну писати складний код з купою if/else, щоб аналізувати URL кожного запиту і вирішувати, що робити.
В ASP.NET Core маршрутизація складається з двох основних етапів (Middleware):
- EndpointRoutingMiddleware (
UseRouting()): Знаходить кінцеву точку, яка відповідає запиту. - EndpointMiddleware (
UseEndpoints()): Виконує код (делегат) знайденої кінцевої точки.
UseRouting та UseEndpoints вручну, оскільки клас WebApplication автоматично налаштовує їх для вас.Аналізатор кінцевих точок (Endpoint)
Кінцева точка (Endpoint) складається з трьох основних речей:
- HTTP-метод (GET, POST, PUT, DELETE тощо).
- Шаблон маршруту (наприклад,
/або/users/{id}). - Обробник (функція, яка виконається).
Створення базових кінцевих точок
Для визначення маршрутів ми використовуємо методи об'єкта app (інстанс WebApplication), такі як MapGet, MapPost, MapPut, MapDelete.
Розглянемо найпростіший приклад:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Головна сторінка");
app.MapGet("/about", () => "Сторінка про компанію");
app.Run();
Анатомія коду:
- Ми створили дві кінцеві точки:
- Перша реагує на HTTP GET запит за кореневою адресою
/і повертає рядок"Головна сторінка". - Друга реагує на HTTP GET запит за адресою
/aboutі повертає"Сторінка про компанію".
- Перша реагує на HTTP GET запит за кореневою адресою
- Обробником тут виступає звичайна лямбда-функція
() => ....
Повернення об'єктів (JSON Serializing)
Сучасні API рідко повертають просто текст. Зазвичай це структуровані дані у форматі JSON. ASP.NET Core Minimal APIs робить перетворення об'єкта в JSON автоматично!
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/user", () => new Person("John Doe", 30));
app.Run();
record class Person(string Name, int Age);
Анатомія коду:
- Ми створили
record class Person. - У маршруті
/userми просто повертаємоnew Person(...). - Магія під капотом: Фреймворк бачить, що ми повертаємо об'єкт (не рядок), автоматично серіалізує його в JSON та встановлює HTTP-заголовок
Content-Type: application/json.
Результат запиту:
{
"name": "John Doe",
"age": 30
}
Базові CRUD операції (REST)
Використовуючи різні HTTP-методи (MapGet, MapPost, MapPut, MapDelete), ми можемо побудувати повноцінне RESTful API:
var app = WebApplication.Create();
app.MapGet("/items", () => "Отримання списку");
app.MapPost("/items", (Item item) => Results.Created($"/items/{item.Id}", item));
app.MapPut("/items/{id}", (int id, Item item) => Results.NoContent());
app.MapDelete("/items/{id}", (int id) => Results.Ok());
app.Run();
public record Item(int Id, string Name);
Анатомія коду:
MapGet("/items"): Обробляє отримання всіх елементів.MapPost("/items"): Дані з тіла запиту (Request Body) автоматично десеріалізуються (JSON -> об'єкт) і передаються в параметрItem item.MapPutтаMapDelete: Містять у шляху параметр{id}, про який ми поговоримо детальніше нижче.Results.Created,Results.Ok,Results.NoContent: Це вбудовані хелпери для повернення правильних HTTP статус-кодів (201 Created, 200 OK, 204 No Content).
Параметри маршрутів (Route Parameters)
Часто шлях містить динамічні значення. Наприклад, ми хочемо отримати користувача з ID 123 за адресою /users/123. Для цього ми використовуємо параметри маршрутів.
Параметри позначаються фігурними дужками {назва_параметра} всередині шаблону маршруту.
Просте зв'язування параметра
app.MapGet("/users/{id}", (int id) =>
{
return $"ID користувача: {id}";
});
Анатомія коду:
- Шаблон
/users/{id}повідомляє роутеру, що після/users/йде динамічна частина. - Значення цієї частини витягується зі шляху та автоматично конвертується в тип параметра нашої функції —
int id. - Запит:
GET /users/42-> результат:"ID користувача: 42". - Що буде, якщо передати не число? Якщо ми зробимо запит
GET /users/hello, фреймворк не зможе конвертувати"hello"вintі автоматично поверне помилку400 Bad Request.
Декілька параметрів
Шлях може містити стільки параметрів, скільки потрібно, а як роздільник зазвичай використовують символ / (хоча можна й інші, наприклад -).
app.MapGet("/users/{userId}/books/{bookId}", (int userId, int bookId) =>
{
return $"Користувач {userId} запитує книгу {bookId}";
});
Запит: GET /users/14/books/89 -> результат: "Користувач 14 запитує книгу 89".
Необов'язкові (Опціональні) параметри та дефолтні значення
Іноді параметр може бути відсутнім у URL. У такому випадку ми додаємо ? після імені параметра в шаблоні, а параметр у делегаті робимо nullable або задаємо дефолтне значення.
// Опціональний параметр
app.MapGet("/products/{category?}", (string? category) =>
{
return $"Категорія: {category ?? "Усі товари"}";
});
// Параметр зі значенням за замовчуванням
app.MapGet("/articles/{page=1}", (int page) =>
{
return $"Сторінка номер: {page}";
});
Анатомія коду:
/products/{category?}: Запит/products/electronicsповерне"Категорія: electronics". Запит/products— поверне"Категорія: Усі товари". Зверніть увагу на Nullable типstring?./articles/{page=1}: Запит/articlesвстановить параметрpageу значення1.
Важливо: Опціональні параметри та параметри зі значеннями за замовчуванням мають бути останніми сегментами шаблону URL.
Як подивитися всі доступні маршрути?
Іноді під час розробки корисно побачити повний список зареєстрованих маршрутів. ASP.NET Core зберігає цю інформацію і дозволяє отримати до неї доступ через залежність IEnumerable<EndpointDataSource>.
app.MapGet("/debug/routes", (IEnumerable<EndpointDataSource> endpointSources) =>
{
var endpoints = endpointSources.SelectMany(source => source.Endpoints);
var routes = new List<string>();
foreach (var endpoint in endpoints)
{
if (endpoint is RouteEndpoint routeEndpoint)
{
routes.Add(routeEndpoint.RoutePattern.RawText);
}
}
return routes;
});
Анатомія коду:
- Ми інжектуємо (додаємо як параметр)
IEnumerable<EndpointDataSource>. Це внутрішня колекція фреймворку, яка тримає інформацію про всі роути. - Проходимось по всіх endpoint-ах і, якщо це
RouteEndpoint, дістаємо його "сирий текст" (RoutePattern.RawText). - Повертаємо список як JSON масив
string[].
Практичні завдання
GET /add/{a}/{b}.
Вона повинна приймати два цілих числа як параметри маршруту і повертати їх суму у вигляді рядка "Сума: X".
Перевірте, як відреагує API, якщо замість числа передати текст.POST /pets, яка приймає JSON-об'єкт Pet (Name, Species, Age).
Збережіть його у static List<Pet> в пам'яті. Поверніть створеного улюбленця разом зі статусом 201 Created та URL /pets/ім'я-тварини.
Створіть GET /pets, щоб повертати весь поточний список.Міні-сервіс управління товарами Реалізуйте:
- Отримання всього списку:
GET /products - Пошук за категорією з дефолтним значенням (в посилання):
GET /products/category/{category=all} - Інформація про один товар:
GET /products/{id} - Якщо товару за ID не існує — поверніть статус 404 (використовуючи
Results.NotFound()). - Зробіть спеціальний маршрут
GET /routes, який виведе всю зареєстровану "мапу гілок", як було показано вище.
Конвеєр запитів та Middleware
Вичерпний посібник з архітектури Pipeline в ASP.NET Core. Розбираємо Middleware (Use, Run, Map), HttpContext, Request та Response об'єкти.
Маршрутизація в ASP.NET Core: Розширені можливості
У попередньому матеріалі ми дізналися, як створювати базові маршрути та приймати параметри. Однак реальні проєкти вимагають значно більшого контролю над тим, які запити має обробляти конкретна кінцева точка.