Содержание
Контроллеры 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
|
File() |
Отправляет клиенту указанный файл |
ForbidResult |
Forbid() |
По умолчанию, клиенту возвращается код статуса 403 Forbidden |
JsonResult |
Json() |
Отправляет клиенту объект в формате JSON |
LocalRedirectResult |
LocalRedirect() |
Перенаправляет пользователя на локальный URL и возвращает код статуса 302 Found |
LocalRedirectPermanent() |
Перенаправляет пользователя на локальный URL и возвращает код статуса 301 Moved Permanently | |
LocalRedirectPermanent
|
Перенаправляет пользователя на локальный URL и возвращает код статуса 308 Permanent Redirect | |
LocalRedirect
|
Перенаправляет пользователя на локальный 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
|
Осуществляет перенаправление клиента на заданный 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>
.