HTTP-коды и результаты действий

IActionResult и коды ответов: как вернуть 200, 201, 404 и почему это важно.

Суть: HTTP-ответ — это не только тело (JSON), но и статус-код, который говорит клиенту, что произошло: успех (2xx), ошибка клиента (4xx), ошибка сервера (5xx). В ASP.NET Core за это отвечают типы результатов вроде Ok(), NotFound(), BadRequest().

Начинающие часто возвращают данные и забывают про код ответа. Но для API код — это контракт: фронтенд по нему понимает, показать ли данные, форму ошибки или редирект. Возвращать 200 при «не найдено» — типичная ошибка, ломающая клиентов.

Основные результаты

[HttpGet("{id:int}")]
public IActionResult Get(int id)
{
    var user = _repo.Find(id);
    if (user is null)
        return NotFound();              // 404

    return Ok(user);                    // 200 + JSON
}

[HttpPost]
public IActionResult Create(UserDto dto)
{
    if (!ModelState.IsValid)
        return BadRequest(ModelState);  // 400

    var created = _repo.Add(dto);
    return CreatedAtAction(nameof(Get), new { id = created.Id }, created); // 201
}

Карта кодов

КодМетодКогда
200 OKOk()Успешное чтение/обновление
201 CreatedCreatedAtAction()Создан новый ресурс
204 No ContentNoContent()Успех без тела (удаление)
400 Bad RequestBadRequest()Невалидные данные клиента
404 Not FoundNotFound()Ресурс не существует

Как работает под капотом

Когда вы возвращаете Ok(user), создаётся объект OkObjectResult со статусом 200 и телом user. Фреймворк выполняет этот результат: выставляет статус-код, сериализует тело в JSON, пишет заголовки. IActionResult — это абстракция «отложенного ответа»: метод описывает что вернуть, а фреймворк решает как. Типизированные результаты (ActionResult<User>) добавляют ещё и информацию о типе для документации Swagger.

Частые ошибки

  • Возвращать 200 при ошибке. Клиент не отличит успех от провала — всегда возвращайте корректный код.
  • Кидать исключение вместо 404. «Не найдено» — это нормальная ситуация, а не ошибка сервера (500).
  • Отдавать стектрейс в ответе. Внутренние ошибки идут в 500 без деталей наружу (детали — в логи).

Best practices

  • Используйте CreatedAtAction/CreatedAtRoute для 201 — они ещё и ставят заголовок Location на новый ресурс.
  • Для единообразных ошибок применяйте ProblemDetails (стандарт RFC 7807) — [ApiController] делает это автоматически.
  • Возвращайте ActionResult<T> вместо IActionResult, когда тип данных известен — лучше для документации и типобезопасности.

Семейства статус-кодов и что они сообщают клиенту

Коды ответов делятся на классы, и каждый класс несёт смысл. 2xx — успех (200 OK, 201 Created, 204 No Content). 3xx — перенаправление (301/302). 4xx — ошибка на стороне клиента: 400 (плохой запрос), 401 (не аутентифицирован), 403 (нет прав), 404 (не найдено), 409 (конфликт, например нарушение уникальности), 422 (данные не прошли семантическую проверку). 5xx — ошибка сервера (500, 503). Клиент по первой цифре уже понимает стратегию: ретраить, показать форму ошибки, отправить на логин.

Особенно важно различать 400, 401, 403 и 404, потому что их часто путают. 400 — «ты прислал ерунду». 401 — «представься». 403 — «ты известен, но сюда нельзя». 404 — «такого ресурса нет». Каждый из них ведёт клиента к разному действию, и подмена одного другим ломает поведение фронтенда и сторонних потребителей API.

ProblemDetails — стандартный формат ошибок

Чтобы ошибки были единообразными, существует стандарт RFC 7807 ProblemDetails: ответ об ошибке — это JSON с полями type, title, status, detail, instance. ASP.NET Core с атрибутом [ApiController] автоматически возвращает ошибки валидации и многие 4xx/5xx именно в этом формате. Клиентам удобно: они обрабатывают один предсказуемый формат, а не зоопарк самописных структур ошибок от каждого эндпоинта.

Возврат ActionResult<T> вместо IActionResult даёт ещё одно преимущество: компилятор и генератор документации знают тип успешного ответа. Вы можете вернуть и Ok(dto), и NotFound() из одного метода, при этом Swagger покажет схему T для 200. Это сочетание типобезопасности и самодокументируемости — признак зрелого API.

Итог: статус-код — часть контракта API. Возвращайте корректные коды через хелперы, не маскируйте ошибки под 200. Дальше переходим к проектированию полноценного REST Web API.

Проверьте себя
1. Что вернуть, если ресурс по id не найден?
AOk(null) со статусом 200
BNotFound() со статусом 404
CBadRequest() со статусом 400
DИсключение и статус 500
2. Какой код и хелпер используют при успешном создании ресурса?
A200 через Ok()
B204 через NoContent()
C201 через CreatedAtAction()
D404 через NotFound()