2. REST API и CRUD¶
REST API представляет ресурсы через URL, а действия выражает HTTP-методами.
В этом проекте главным ресурсом является событие активности. Событие можно создать, посмотреть в списке, отфильтровать и удалить. Для студента важно понять не только набор методов, но и логику общения клиента с сервером:
- Клиент отправляет HTTP-запрос.
- ASP.NET Core выбирает контроллер по маршруту.
- Контроллер получает параметры из URL, query string или тела запроса.
- Контроллер вызывает сервис приложения.
- Сервис выполняет правила и обращается к репозиторию.
- Контроллер возвращает HTTP-статус и данные.
То есть REST API — это не “код контроллера ради контроллера”, а внешний вход в вашу систему. Через него с приложением могут работать веб-интерфейс, мобильное приложение, интеграционный сервис или Swagger во время разработки.
| Метод | URL | Назначение |
|---|---|---|
GET |
/api/events |
получить список |
GET |
/api/events/{id} |
получить одну запись |
POST |
/api/events |
создать запись |
PUT |
/api/events/{id} |
заменить запись |
PATCH |
/api/events/{id} |
частично изменить запись |
DELETE |
/api/events/{id} |
удалить запись |
Как читать таблицу REST-методов¶
URL отвечает на вопрос с каким объектом работаем, а HTTP-метод отвечает на вопрос что делаем. Поэтому не стоит делать адреса вида /api/createEvent или /api/deleteEvent. В REST-стиле правильнее использовать один ресурс /api/events, а действие передавать методом POST, GET или DELETE.
Например:
POST /api/events— создать новое событие;GET /api/events— получить список событий;DELETE /api/events/15— удалить событие с идентификатором15.
Минимальный CRUD-контроллер¶
Ниже показан контроллер, который содержит три операции. Его задача — принять HTTP-запрос и передать работу сервису IActivityEventService. Именно поэтому внутри контроллера нет SQL-запросов, расчётов аналитики и прямой работы с DbContext.
Обратите внимание на атрибуты:
[ApiController]включает удобное поведение Web API: автоматическую обработку модели запроса и ошибок валидации;[Route("api/events")]задаёт общий путь для всех методов контроллера;[HttpGet],[HttpPost],[HttpDelete]связывают методы C# с HTTP-методами;[FromQuery]говорит, что фильтр берётся из параметров URL, например?page=1&pageSize=10.
[ApiController]
[Route("api/events")]
public class EventsController : ControllerBase
{
private readonly IActivityEventService _service;
public EventsController(IActivityEventService service)
{
_service = service;
}
[HttpGet]
public async Task<ActionResult<IReadOnlyList<ActivityEventDto>>> Get(
[FromQuery] ActivityEventFilter filter,
CancellationToken ct)
{
return Ok(await _service.GetAsync(filter, ct));
}
[HttpPost]
public async Task<ActionResult<ActivityEventDto>> Create(
CreateActivityEventRequest request,
CancellationToken ct)
{
var userId = int.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
var result = await _service.CreateAsync(request, userId, ct);
return Created($"/api/events/{result.Id}", result);
}
[HttpDelete("{id:int}")]
public async Task<IActionResult> Delete(int id, CancellationToken ct)
{
var deleted = await _service.DeleteAsync(id, ct);
return deleted ? NoContent() : NotFound();
}
}
Что происходит в методе Create:
- Из JWT-токена берётся идентификатор текущего пользователя.
- Тело JSON-запроса автоматически преобразуется в
CreateActivityEventRequest. - Сервис создаёт доменную сущность и сохраняет её.
- API возвращает
201 Created, потому что на сервере появился новый ресурс.
Если вернуть просто 200 OK, код тоже может работать, но 201 Created точнее описывает результат операции.
Фильтр списка¶
Фильтр нужен, чтобы не загружать все события сразу. В производственных системах таблица событий быстро растёт: за день может появиться тысяча или миллион записей. Поэтому API должно уметь выбирать только нужную часть данных.
Поля фильтра читаются из query string:
Такой запрос означает: “верни первую страницу событий типа Login с уровнем Info, по 20 записей на страницу”.
namespace ActivityMonitoring.Application.Events;
public class ActivityEventFilter
{
public int? UserId { get; set; }
public string? Type { get; set; }
public string? Severity { get; set; }
public DateTime? From { get; set; }
public DateTime? To { get; set; }
public int Page { get; set; } = 1;
public int PageSize { get; set; } = 20;
}
Поля Page и PageSize защищают сервер от ситуации, когда клиент случайно запросит все записи сразу. В репозитории дополнительно стоит ограничить PageSize, например максимумом 100.
Валидация простыми проверками¶
Валидация отвечает на вопрос: “можно ли вообще принимать такие данные в систему?”. Если тип события пустой, оно не имеет смысла. Если описание слишком длинное, пользователь может случайно отправить мусор или слишком большой текст.
if (string.IsNullOrWhiteSpace(request.Type))
{
return BadRequest("Тип события обязателен.");
}
if (request.Description.Length > 500)
{
return BadRequest("Описание события не должно превышать 500 символов.");
}
В реальном проекте лучше перенести валидацию в сервис или использовать FluentValidation, но для практики достаточно понятных проверок на уровне сервиса.
Типичные ошибки¶
| Ошибка | Почему возникает | Как исправить |
|---|---|---|
404 Not Found |
Неверный URL или маршрут контроллера | Проверить [Route] и адрес запроса |
415 Unsupported Media Type |
Не указан Content-Type: application/json |
Добавить заголовок к POST-запросу |
400 Bad Request |
JSON не совпадает с DTO | Проверить имена полей и типы данных |
500 Internal Server Error |
Ошибка внутри сервиса или БД | Смотреть консольные логи ASP.NET Core |
| Пустой список | Фильтр слишком строгий или нет данных | Создать тестовые события генератором |
Что студент должен понять¶
После этого раздела студент должен уметь объяснить, почему EventsController не является “главной программой”, а только входной точкой. Главная логика проекта находится в сервисах, а контроллер связывает внешний HTTP-мир с внутренней архитектурой приложения.