ASP.NET Core Web API. Типы значений, возвращаемые контроллерами

Контроллеры ASP.NET Core Web API могут возвращать данные различных типов от простых, например, чисел и до сложных объектов. В этой части рассмотрим основные типы значений, возвращаемые контроллерами.

Определенные типы

В самом простом случае, действие контроллера может возвращать примитивные или составные типы данных. Пример передачи таких данных продемонстрирован непосредственно в контроллере шаблонного приложения ASP.NET Core Web API

[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
    return Enumerable.Range(1, 5).Select(index => new WeatherForecast
    {
        Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
        TemperatureC = Random.Shared.Next(-20, 55),
        Summary = Summaries[Random.Shared.Next(Summaries.Length)]
    })
    .ToArray();
}

Действие контроллера Get() возвращает конкретный тип данных — коллекцию элементов IEnumerable<WeatherForecast>. При этом ASP.NET Core, по умолчанию, будет пытаться сериализовать результат в JSON. С одной стороны, возврат действием контроллера объектов конкретного типа — это естественная работа методов в C#. Однако, такой подход к организации действий контроллеров сопряжен с одним серьезным недостатком — используя такой подход мы ограничены исключительно одним типом данных. То есть сейчас метод Get() контроллера может возвращать только коллекцию. При этом, часто требуется, чтобы действие контроллера возвращало различные типы данных, например, при успешном выполнении запроса — коллекцию объектов, а при неудачном — объект, содержащий описание ошибки.

Тип ActionResult и ActionResult<T>

Типы ActionResult и ActionResult<T> – это специальные типы данных, определяющие тип возвращаемого действием контроллера результата. При этом суть использования ActionResult<T> заключается в том, чтобы позволить действию контроллера вернуть ИЛИ объект типа T ИЛИ объект типа ActionResult.

Тип ActionResult – это абстрактный класс, однако, нам крайне редко придется создавать свои классы, унаследованные от ActionResult. В ASP.NET Core уже определен не только ряд наследников этого класса, но и методов, позволяющих формировать ответы клиенту в виде объектов типа ActionResult. В таблице ниже представлены классы-наследники ActionResult и методы класса ControllerBase, формирующие результат указанных классов.

Класс Метод ControllerBase Описание
AcceptedResult Accepted() Отправляет клиенту код статуса 202 Accepted
BadRequestResult  BadRequest() Отправляет клиенту код статуса 400 Bad Request
ChallengeResult Challenge() Используется для проверки аутентификации пользователя

Возможные коды статуса, отправляемые методом: 401 Unauthorized. 403 Forbidden

ConflictResult  Conflict() Отправляет ответ с кодом статуса 409 Conflict
ContentResult  Content() Отправляет клиенту ответ в виде строки
CreatedResult  Created() Отправляет ответ с кодом статуса 201 Created
FileContentResult

FileStreamResult

VirtualFileResult

File() Отправляет клиенту указанный файл
ForbidResult  Forbid() По умолчанию, клиенту возвращается код статуса 403 Forbidden
JsonResult  Json() Отправляет клиенту объект в формате JSON
LocalRedirectResult  LocalRedirect() Перенаправляет пользователя на локальный URL и возвращает код статуса 302 Found
  LocalRedirectPermanent() Перенаправляет пользователя на локальный URL и возвращает код статуса 301 Moved Permanently
  LocalRedirectPermanent

PreserveMethod()

Перенаправляет пользователя на локальный URL и возвращает код статуса 308 Permanent Redirect
  LocalRedirect

PreserveMethod()

Перенаправляет пользователя на локальный URL и возвращает код статуса 307 Temporary Redirect
NoContentResult  NoContent() Отправляет ответ с кодом статуса 204 No Content
NotFoundResult  NotFound() Отправляет ответ с кодом статуса 404 Not Found
OkResult  Ok() Отправляет ответ с кодом статуса 200 OK
PhysicalFileResult PhysicalFile() Отправляет клиенту в ответ файл, находящийся по заданному пути
ObjectResult Problem() Применяет формат ProblemDetails для отправки ответа.
RedirectResult  Redirect() Осуществляет перенаправление клиента на заданный URL и возвращает код статуса 302 Found
  RedirectPermanent() Осуществляет перенаправление клиента на заданный URL и возвращает код статуса 301 Moved Permanently
  RedirectPermanent

PreserveMethod()

Осуществляет перенаправление клиента на заданный URL и возвращает код статуса 308 Permanent Redirect
  RedirectPreserveMethod() Осуществляет перенаправление клиента на заданный URL и возвращает код статуса 307 Temporary Redirect
SignInResult SignIn() Используется при проверке подлинности клиента
SignOutResult SignOut() Используется при выходе клиента из системы
StatusCodeResult  StatusCode() Отправляет клиенту заданный код статуса
UnauthorizedResult  Unauthorized() Отправляет ответ с кодом статуса 401 Unauthorized
UnauthorizedObjectResult  Unauthorized(Object)
UnprocessableEntityResult  UnprocessableEntity() Отправляет ответ с кодом статуса 422 Unprocessable Entity
ActionResult  ValidationProblem() Отправляет клиенту код статуса 400 Bad Request

Например, перепишем метод Get() контроллера WeatherForecastController следующим образом:

[HttpGet("{id}")]
public ActionResult<WeatherForecast> Get(int id)
{
    var weather = Enumerable.Range(1, 5).Select(index => new WeatherForecast
    {
        Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
        TemperatureC = Random.Shared.Next(-20, 55),
        Summary = Summaries[Random.Shared.Next(Summaries.Length)]
    })
    .ToArray();

    if ((id < 0) || (id > 5))
        return BadRequest();
    else 
        return Ok(weather[id]);
}

Теперь наш метод возвращает только одно значение из массива. При этом, тип возвращаемого результата определен как ActionResult<WeatherForecast>, что позволяет нам сделать так, чтобы действие контроллера могло вернуть два различных типа данных:

if ((id < 0) || (id > 5))
    return BadRequest();
else 
    return weather[id];

первый результат — это наследник ActionResult, который вернется пользователю, если он передаст неверное значение параметра id, второй — объект типа WeatherForecast. Добавим в http-файл проекта следующие запросы:

@WebApplication4_HostAddress = http://localhost:5193

GET {{WebApplication4_HostAddress}}/weatherforecast/1
Accept: application/json

###
GET {{WebApplication4_HostAddress}}/weatherforecast/7
Accept: application/json

Запустим приложение и проверим выполнение обоих запросов. Первый запрос:

Второй запрос:

Здесь мы воспользовались универсальным типом ActionResult<T>, поэтому смогли во втором случае вернуть пользователю конкретный тип:

return weather[id];

Что касается использования неуниверсального ActionResult в качестве типа возвращаемого значения, то в этом случае действие контроллера должно возвращать только ActionResult. То есть, если бы мы хотели использовать этот тип в действии контроллера, нам мы бы переписали его следующим образом:

[HttpGet("{id}")]
public ActionResult Get(int id)
{
    var weather = Enumerable.Range(1, 5).Select(index => new WeatherForecast
    {
        Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
        TemperatureC = Random.Shared.Next(-20, 55),
        Summary = Summaries[Random.Shared.Next(Summaries.Length)]
    })
    .ToArray();

    if ((id < 0) || (id > 5))
        return BadRequest();
    else 
        return Ok(weather[id]);
}

Здесь нам пришлось изменить возвращаемый результат с

return weather[id]

на

return Ok(weather[id]);

Описание действия метода Ok() можно посмотреть в таблице выше. Для пользователя API результат выполнения действия никак не изменится – он также получит в ответе или код статуса 204, или массив объектов в формате JSON с кодом статуса HTTP 200 Ok.

Типы IResult и Results<T1, T2, Tn>

В качестве типа результат действия контроллера также может выступать любой объект, реализующий интерфейс IResult. Все объекты, реализующие этот интерфейс располагаются в пространстве имен Microsoft.AspNetCore.Http.HttpResults, а для их создания используются методы статического класса TypedResults (все методы этого класса можно посмотреть здесь). Например, перепишем действие контроллера следующим образом:

[HttpGet("{id}")]
public Results<BadRequest, Ok<WeatherForecast>> Get(int id)
{
    var weather = Enumerable.Range(1, 5).Select(index => new WeatherForecast
    {
        Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
        TemperatureC = Random.Shared.Next(-20, 55),
        Summary = Summaries[Random.Shared.Next(Summaries.Length)]
    })
    .ToArray();

    if ((id < 0) || (id > 5))
        return TypedResults.BadRequest();
    else 
        return TypedResults.Ok(weather[id]);
}

Здесь мы использовали в качестве возвращаемого результата универсальный тип Results<T1, T2...Tn>. Для использования этого варианта ответа необходимо использовать статический класс TypedResult.

if ((id < 0) || (id > 5))
    return TypedResults.BadRequest();
else 
    return TypedResults.Ok(weather[id]);

Этот тип данных стоит использовать для контроллеров, когда нам необходимо использовать и minimal API и API на основе контроллеров, что бывает редко. Поэтому при выборе между Results и ActionResult лучше отдать предпочтение второму типу данных.

Интерфейс IActionResult

Интерфейс IActionResult реализуется в классе ActionResult и также представляет тип результата, возвращаемого действием контроллера. Тип возвращаемого значения IActionResult можно использовать, если в действии допускаются несколько типов возвращаемого значения ActionResult. Сейчас наше действие Get() как раз возвращает несколько типов возвращаемого значения. Поэтому мы можем применить в качестве типа результата, возвращаемого действием интерфейс

[HttpGet("{id}")]
public IActionResult Get(int id)
{
    var weather = Enumerable.Range(1, 5).Select(index => new WeatherForecast
    {
        Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
        TemperatureC = Random.Shared.Next(-20, 55),
        Summary = Summaries[Random.Shared.Next(Summaries.Length)]
    })
    .ToArray();

    if ((id < 0) || (id > 5))
        return BadRequest();
    else 
        return Ok(weather[id]);
}

Опять же, для клиента API такие изменения никак не отразятся на его работе.

Что касается вопроса того, какой тип возвращаемого результата использовать в приложении, то можно дать следующую рекомендацию – следует стараться избегать, указания в типах результатов для действий определенных типов данных (string, int, конкретный класс и так далее). Да, ASP.NET Core не запрещает возвращать, например, строки или числа, но при выполнении действия контроллера возможно возникновение какой-либо непредвиденной ситуации, например пользователь передаст неверный параметр запроса – в этом случае сервер должен будет вернуть код состояния 4хх и описание возникшей проблемы и использование в результате определенного типа не позволит этого сделать. Поэтому при разработке действий в контроллерах следует отдавать предпочтение использованию типов возвращаемых результатов ActionResult<T>, ActionResult или же IActionResult.

Итого

Контроллеры в ASP.NET Core Web API могт возвращать значения различных типов — от примитивных типов и конкретных объектов до объектов, реализующих специальные интерфейсы типа IResult и IActionResult. Использование того или иного типа данных зависит, в первую очередь, от задач, решаемых API. В большинстве случаев мы будем использовать в работе IActionResult или его типизированный вариант ActionResult<T>.

Подписаться
Уведомить о
guest
8 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии