Типы возвращаемых действий контроллера в веб-API ASP.NET Core

Примечание.

Это не последняя версия этой статьи. В текущем выпуске см . версию .NET 8 этой статьи.

Внимание

Эта информация относится к предварительному выпуску продукта, который может быть существенно изменен до его коммерческого выпуска. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.

В текущем выпуске см . версию .NET 8 этой статьи.

Просмотреть или скачать образец кода (описание загрузки)

ASP.NET Core предоставляет следующие параметры для возвращаемых типов действий контроллера веб-API:

В этой статье объясняется, когда наиболее подходящим для использования каждого возвращаемого типа.

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

Наиболее простое действие возвращает примитивный или сложный тип данных, например string пользовательский объект. Рассмотрим следующее действие, которое возвращает коллекцию пользовательских объектов Product:

[HttpGet]
public Task<List<Product>> Get() =>
    _productContext.Products.OrderBy(p => p.Name).ToListAsync();

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

Если возможны несколько типов возвращаемых данных, обычно можно смешать ActionResult возвращаемый тип с примитивным или сложным типом возвращаемого значения. Для удовлетворения этого типа действия необходимы либо IActionResult, либо ActionResult<T>. В этой статье приведено несколько примеров нескольких типов возвращаемых данных.

Возврат IEnumerable<T> или IAsyncEnumerable<T>

Ознакомьтесь с рекомендациями по возврату IEnumerable<T> или IAsyncEnumerable<T> производительности.

ASP.NET Core буферизирует результат действий, возвращаемых IEnumerable<T> перед записью в ответ. Рассмотрите возможность объявления возвращаемого типа подписи действия, чтобы IAsyncEnumerable<T> гарантировать асинхронную итерацию. В конечном счете режим итерации основан на возвращаемом базовом конкретном типе, а выбранный формататор влияет на обработку результата:

  • При использовании System.Text.Json средства форматирования MVC использует поддержку, System.Text.Json добавленную для потоковой передачи результата.
  • При использовании Newtonsoft.Json или с XML-based форматировщиками результат буферичен.

Рассмотрим следующее действие, которое возвращает записи о продуктах со сниженной ценой как IEnumerable<Product>:

[HttpGet("syncsale")]
public IEnumerable<Product> GetOnSaleProducts()
{
    var products = _productContext.Products.OrderBy(p => p.Name).ToList();

    foreach (var product in products)
    {
        if (product.IsOnSale)
        {
            yield return product;
        }
    }
}

Эквивалентом IAsyncEnumerable<Product> для предшествующего действия является:

[HttpGet("asyncsale")]
public async IAsyncEnumerable<Product> GetOnSaleProductsAsync()
{
    var products = _productContext.Products.OrderBy(p => p.Name).AsAsyncEnumerable();

    await foreach (var product in products)
    {
        if (product.IsOnSale)
        {
            yield return product;
        }
    }
}

Тип IActionResult

Тип возвращаемого значения IActionResult можно использовать, если в действии допускаются несколько типов возвращаемого значения ActionResult. Типы ActionResult представляют различные коды состояния HTTP. Любой неабстрактный класс, унаследованный от квалификаторов ActionResult в виде допустимого типа возвращаемого значения. Некоторые распространенные типы возвращаемых значений в этой категории: BadRequestResult (400), NotFoundResult (404) и OkObjectResult (200). Кроме того, удобные методы в классе ControllerBase можно использовать для получения типов ActionResult из действия. Например, return BadRequest(); — это сокращенная форма return new BadRequestResult();.

Так как в типе действия есть несколько типов возвращаемого значения и путей, необходимо использовать атрибут [ProducesResponseType]. Этот атрибут создает описательные сведения об ответе для страниц справки по веб-API, создаваемых с помощью таких инструментов, как Swagger. [ProducesResponseType] указывает известные типы и коды состояния HTTP, возвращаемые действием.

Синхронное действие

Рассмотрим следующее синхронное действие, в котором возможны два типа возвращаемых значений:

[HttpGet("{id}")]
[ProducesResponseType<Product>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult GetById_IActionResult(int id)
{
    var product = _productContext.Products.Find(id);
    return product == null ? NotFound() : Ok(product);
}

В предшествующем действии:

  • возвращается код состояния 404, если продукт, представленный id, не существует в базовом хранилище данных; вызывается удобный метод NotFound в качестве сокращения return new NotFoundResult();.
  • Код состояния 200 возвращается с объектом Product, если продукт не существует. вызывается удобный метод Ok в качестве сокращения return new OkObjectResult(product);.

Асинхронное действие

Рассмотрим следующее асинхронное действие, в котором возможны два типа возвращаемых значений:

[HttpPost()]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> CreateAsync_IActionResult(Product product)
{
    if (product.Description.Contains("XYZ Widget"))
    {
        return BadRequest();
    }

    _productContext.Products.Add(product);
    await _productContext.SaveChangesAsync();

    return CreatedAtAction(nameof(CreateAsync_IActionResult), new { id = product.Id }, product);
}

В предшествующем действии:

  • Код состояния 400 возвращается, если описание продукта содержит строку XYZ Widget. вызывается удобный метод BadRequest в качестве сокращения return new BadRequestResult();.

  • Код состояния 201 генерируется удобным методом CreatedAtAction при создании продукта. Следующий код является альтернативой вызову CreatedAtAction:

    return new CreatedAtActionResult(nameof(CreateAsync), 
                                    "Products", 
                                    new { id = product.Id }, 
                                    product);
    

    В приведенном выше пути Product кода объект предоставляется в тексте ответа. Также предоставляется заголовок ответа Location с URL-адресом только что созданного продукта.

Например, следующая модель указывает на то, что запросы должны включать свойства Name и Description. Если Name и Description не были указаны в запросе, происходит сбой проверки модели.

public class Product
{
    public int Id { get; set; }

    [Required]
    public string Name { get; set; } = string.Empty;

    [Required]
    public string Description { get; set; } = string.Empty;

    public bool IsOnSale { get; set; }
}

[ApiController] Если атрибут применен, ошибки проверки модели приводят к коду состояния 400. Дополнительные сведения см. в разделе Автоматические отклики HTTP 400.

ActionResult и IActionResult

В следующем разделе сравнивается ActionResult с IActionResult

Тип ActionResult<T>

ASP.NET Core включает тип возвращаемого значения ActionResult<T> для действий контроллера веб-API. Он позволяет возвращать тип, производный от ActionResult определенного типа или возвращающий определенный тип. ActionResult<T> имеет следующие преимущества по сравнению с типом IActionResult:

  • Свойство Type атрибута [ProducesResponseType] можно исключить. Например, [ProducesResponseType(200, Type = typeof(Product))] упрощается до [ProducesResponseType(200)]. Ожидаемый тип возвращаемого значения действия выводится из объекта TActionResult<T>in.
  • Операторы неявного приведения поддерживают преобразование T и ActionResult в ActionResult<T>. T преобразуется в ObjectResult, то есть return new ObjectResult(T); упрощается до return T;.

C# не поддерживает операторы неявных приведений в интерфейсах. Следовательно, для преобразования в конкретный тип необходимо использовать ActionResult<T>. Например, использование IEnumerable не работает в следующем примере:

[HttpGet]
public ActionResult<IEnumerable<Product>> Get() =>
    _repository.GetProducts();

Один из способов исправить приведенный выше код — возвратить _repository.GetProducts().ToList();.

Большинство действий имеют тип возвращаемого значения. При выполнении действия могут возникнуть непредвиденные условия, и в этом случае определенный тип не возвращается. Например, входной параметр действия может не пройти проверку модели. В этом случае обычно возвращается подходящий тип ActionResult вместо конкретного типа.

Синхронное действие

Рассмотрим синхронное действие, в котором возможны два типа возвращаемых значений:

[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<Product> GetById_ActionResultOfT(int id)
{
    var product = _productContext.Products.Find(id);
    return product == null ? NotFound() : product;
}

В предшествующем действии:

  • возвращается код состояния 404, если продукт не существует в базе данных;
  • возвращается код состояния 200 с соответствующим объектом Product, если продукт существует.

Асинхронное действие

Рассмотрим асинхронное действие, в котором возможны два типа возвращаемых значений:

[HttpPost()]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<Product>> CreateAsync_ActionResultOfT(Product product)
{
    if (product.Description.Contains("XYZ Widget"))
    {
        return BadRequest();
    }

    _productContext.Products.Add(product);
    await _productContext.SaveChangesAsync();

    return CreatedAtAction(nameof(CreateAsync_ActionResultOfT), new { id = product.Id }, product);
}

В предшествующем действии:

  • Код состояния 400 (BadRequest) возвращается средой выполнения ASP.NET Core, когда:
    • Атрибут [ApiController] был применен, и проверка модели не пройдена.
    • Описание продукта содержит XYZ Widget.
  • Код состояния 201 генерируется методом CreatedAtAction при создании продукта. В этом пути к коду объект Product предоставляется в тексте ответа. Также предоставляется заголовок ответа Location с URL-адресом только что созданного продукта.

Тип HttpResults

Помимо встроенных типов результатов MVC (IActionResultи ActionResult<T>), ASP.NET Core включает типы HttpResults, которые можно использовать как в минимальных API, так и в веб-API.

Отличается от типов результатов, относящихся к MVC, :HttpResults

  • Реализация результатов, обрабатываемая вызовом IResult.ExecuteAsync.

  • Не использует настроенные форматировщики. Не используется настроенные средства форматирования:

    • Некоторые функции, такие как Content negotiation недоступны.
    • Созданное решение Content-Type определяется реализацией HttpResults .

Это HttpResults может быть полезно при совместном использовании кода между минимальными API и веб-API.

Тип IResult

Пространство Microsoft.AspNetCore.Http.HttpResults имен содержит классы, реализующие IResult интерфейс. Интерфейс IResult определяет контракт, представляющий результат конечной точки HTTP. Статический класс Results используется для создания разных объектов IResult, которые представляют разные типы ответов.

Встроенная таблица результатов показывает распространенные вспомогательные средства результатов.

Рассмотрим следующий код:

[HttpGet("{id}")]
[ProducesResponseType<Product>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IResult GetById(int id)
{
    var product = _productContext.Products.Find(id);
    return product == null ? Results.NotFound() : Results.Ok(product);
}

В предшествующем действии:

  • возвращается код состояния 404, если продукт не существует в базе данных;
  • Код состояния 200 возвращается с соответствующим Product объектом, когда продукт существует, сформированный results.Ok <T>().

Рассмотрим следующий код:

[HttpPost]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType<Product>(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IResult> CreateAsync(Product product)
{
    if (product.Description.Contains("XYZ Widget"))
    {
        return Results.BadRequest();
    }

    _productContext.Products.Add(product);
    await _productContext.SaveChangesAsync();

    var location = Url.Action(nameof(CreateAsync), new { id = product.Id }) ?? $"/{product.Id}";
    return Results.Created(location, product);
}

В предшествующем действии:

  • Код состояния 400 возвращается, когда:
    • Атрибут [ApiController] был применен, и проверка модели не пройдена.
    • Описание продукта содержит XYZ Widget.
  • Код состояния 201 генерируется методом Results.Create при создании продукта. В этом пути к коду объект Product предоставляется в тексте ответа. Также предоставляется заголовок ответа Location с URL-адресом только что созданного продукта.

Результаты<TResult1, тип TResultN>

Статический класс TypedResults возвращает конкретную IResult реализацию, которая позволяет использовать IResult в качестве возвращаемого типа. Использование конкретной IResult реализации обеспечивает следующее преимущество по сравнению с типом IResult:

  • [ProducesResponseType] Все атрибуты можно исключить, так как HttpResult реализация автоматически вносит свой вклад в метаданные конечной точки.

Если требуется несколько IResult типов возврата, возврат Results<TResult1, TResultN> предпочтителен по сравнению с возвратом IResult. Results<TResult1, TResultN> Возврат предпочтителен, так как универсальные типы объединения автоматически сохраняют метаданные конечной точки.

Results<TResult1, TResultN> Типы объединения реализуют неявные операторы приведения, чтобы компилятор может автоматически преобразовать типы, указанные в универсальных аргументах, в экземпляр типа объединения. Это дает дополнительное преимущество предоставления проверка времени компиляции, что обработчик маршрутов фактически возвращает результаты, объявленные им. Попытка вернуть тип, который не объявлен как один из универсальных аргументов, приводит к Results<> ошибке компиляции.

Рассмотрим следующий код:

[HttpGet("{id}")]
public Results<NotFound, Ok<Product>> GetById(int id)
{
    var product = _productContext.Products.Find(id);
    return product == null ? TypedResults.NotFound() : TypedResults.Ok(product);
}

В предшествующем действии:

  • возвращается код состояния 404, если продукт не существует в базе данных;
  • Код состояния 200 возвращается с соответствующим Product объектом при наличии продукта, созданного типом TypedResults.Ok<T>.
[HttpPost]
public async Task<Results<BadRequest, Created<Product>>> CreateAsync(Product product)
{
    if (product.Description.Contains("XYZ Widget"))
    {
        return TypedResults.BadRequest();
    }

    _productContext.Products.Add(product);
    await _productContext.SaveChangesAsync();

    var location = Url.Action(nameof(CreateAsync), new { id = product.Id }) ?? $"/{product.Id}";
    return TypedResults.Created(location, product);
}

В предшествующем действии:

  • Код состояния 400 возвращается, когда:
    • Атрибут [ApiController] был применен, а проверка модели завершается ошибкой.
    • Описание продукта содержит XYZ Widget.
  • Код состояния 201 генерируется методом TypedResults.Created при создании продукта. В этом пути к коду объект Product предоставляется в тексте ответа. Также предоставляется заголовок ответа Location с URL-адресом только что созданного продукта.

Дополнительные ресурсы

Просмотреть или скачать образец кода (описание загрузки)

ASP.NET Core предоставляет следующие параметры для возвращаемых типов действий контроллера веб-API:

В этой статье объясняется, когда наиболее подходящим для использования каждого возвращаемого типа.

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

Наиболее простое действие возвращает примитивный или сложный тип данных, например string пользовательский объект. Рассмотрим следующее действие, которое возвращает коллекцию пользовательских объектов Product:

[HttpGet]
public Task<List<Product>> Get() =>
    _productContext.Products.OrderBy(p => p.Name).ToListAsync();

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

Если возможны несколько типов возвращаемых данных, обычно можно смешать ActionResult возвращаемый тип с примитивным или сложным типом возвращаемого значения. Для удовлетворения этого типа действия необходимы либо IActionResult, либо ActionResult<T>. В этой статье приведено несколько примеров нескольких типов возвращаемых данных.

Возврат IEnumerable<T> или IAsyncEnumerable<T>

Ознакомьтесь с рекомендациями по возврату IEnumerable<T> или IAsyncEnumerable<T> производительности.

ASP.NET Core буферизирует результат действий, возвращаемых IEnumerable<T> перед записью в ответ. Рассмотрите возможность объявления возвращаемого типа подписи действия, чтобы IAsyncEnumerable<T> гарантировать асинхронную итерацию. В конечном счете режим итерации основан на возвращаемом базовом конкретном типе, а выбранный формататор влияет на обработку результата:

  • При использовании System.Text.Json средства форматирования MVC использует поддержку, System.Text.Json добавленную для потоковой передачи результата.
  • При использовании Newtonsoft.Json или с XML-based форматировщиками результат буферичен.

Рассмотрим следующее действие, которое возвращает записи о продуктах со сниженной ценой как IEnumerable<Product>:

[HttpGet("syncsale")]
public IEnumerable<Product> GetOnSaleProducts()
{
    var products = _productContext.Products.OrderBy(p => p.Name).ToList();

    foreach (var product in products)
    {
        if (product.IsOnSale)
        {
            yield return product;
        }
    }
}

Эквивалентом IAsyncEnumerable<Product> для предшествующего действия является:

[HttpGet("asyncsale")]
public async IAsyncEnumerable<Product> GetOnSaleProductsAsync()
{
    var products = _productContext.Products.OrderBy(p => p.Name).AsAsyncEnumerable();

    await foreach (var product in products)
    {
        if (product.IsOnSale)
        {
            yield return product;
        }
    }
}

Тип IActionResult

Тип возвращаемого значения IActionResult можно использовать, если в действии допускаются несколько типов возвращаемого значения ActionResult. Типы ActionResult представляют различные коды состояния HTTP. Любой неабстрактный класс, унаследованный от квалификаторов ActionResult в виде допустимого типа возвращаемого значения. Некоторые распространенные типы возвращаемых значений в этой категории: BadRequestResult (400), NotFoundResult (404) и OkObjectResult (200). Кроме того, удобные методы в классе ControllerBase можно использовать для получения типов ActionResult из действия. Например, return BadRequest(); — это сокращенная форма return new BadRequestResult();.

Так как в типе действия есть несколько типов возвращаемого значения и путей, необходимо использовать атрибут [ProducesResponseType]. Этот атрибут создает описательные сведения об ответе для страниц справки по веб-API, создаваемых с помощью таких инструментов, как Swagger. [ProducesResponseType] указывает известные типы и коды состояния HTTP, возвращаемые действием.

Синхронное действие

Рассмотрим следующее синхронное действие, в котором возможны два типа возвращаемых значений:

[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Product))]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult GetById_IActionResult(int id)
{
    var product = _productContext.Products.Find(id);
    return product == null ? NotFound() : Ok(product);
}

В предшествующем действии:

  • возвращается код состояния 404, если продукт, представленный id, не существует в базовом хранилище данных; вызывается удобный метод NotFound в качестве сокращения return new NotFoundResult();.
  • Код состояния 200 возвращается с объектом Product, если продукт не существует. вызывается удобный метод Ok в качестве сокращения return new OkObjectResult(product);.

Асинхронное действие

Рассмотрим следующее асинхронное действие, в котором возможны два типа возвращаемых значений:

[HttpPost()]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> CreateAsync_IActionResult(Product product)
{
    if (product.Description.Contains("XYZ Widget"))
    {
        return BadRequest();
    }

    _productContext.Products.Add(product);
    await _productContext.SaveChangesAsync();

    return CreatedAtAction(nameof(GetById_IActionResult), new { id = product.Id }, product);
}

В предшествующем действии:

  • Код состояния 400 возвращается, если описание продукта содержит строку XYZ Widget. вызывается удобный метод BadRequest в качестве сокращения return new BadRequestResult();.

  • Код состояния 201 генерируется удобным методом CreatedAtAction при создании продукта. Следующий код является альтернативой вызову CreatedAtAction:

    return new CreatedAtActionResult(nameof(GetById), 
                                    "Products", 
                                    new { id = product.Id }, 
                                    product);
    

    В приведенном выше пути Product кода объект предоставляется в тексте ответа. Также предоставляется заголовок ответа Location с URL-адресом только что созданного продукта.

Например, следующая модель указывает на то, что запросы должны включать свойства Name и Description. Если Name и Description не были указаны в запросе, происходит сбой проверки модели.

public class Product
{
    public int Id { get; set; }

    [Required]
    public string Name { get; set; } = string.Empty;

    [Required]
    public string Description { get; set; } = string.Empty;

    public bool IsOnSale { get; set; }
}

[ApiController] Если атрибут применен, ошибки проверки модели приводят к коду состояния 400. Дополнительные сведения см. в разделе Автоматические отклики HTTP 400.

ActionResult и IActionResult

В следующем разделе сравнивается ActionResult с IActionResult

Тип ActionResult<T>

ASP.NET Core включает тип возвращаемого значения ActionResult<T> для действий контроллера веб-API. Он позволяет возвращать тип, производный от ActionResult определенного типа или возвращающий определенный тип. ActionResult<T> имеет следующие преимущества по сравнению с типом IActionResult:

  • Свойство Type атрибута [ProducesResponseType] можно исключить. Например, [ProducesResponseType(200, Type = typeof(Product))] упрощается до [ProducesResponseType(200)]. Ожидаемый тип возвращаемого значения действия выводится из объекта TActionResult<T>in.
  • Операторы неявного приведения поддерживают преобразование T и ActionResult в ActionResult<T>. T преобразуется в ObjectResult, то есть return new ObjectResult(T); упрощается до return T;.

C# не поддерживает операторы неявных приведений в интерфейсах. Следовательно, для преобразования в конкретный тип необходимо использовать ActionResult<T>. Например, использование IEnumerable не работает в следующем примере:

[HttpGet]
public ActionResult<IEnumerable<Product>> Get() =>
    _repository.GetProducts();

Один из способов исправить приведенный выше код — возвратить _repository.GetProducts().ToList();.

Большинство действий имеют тип возвращаемого значения. При выполнении действия могут возникнуть непредвиденные условия, и в этом случае определенный тип не возвращается. Например, входной параметр действия может не пройти проверку модели. В этом случае обычно возвращается подходящий тип ActionResult вместо конкретного типа.

Синхронное действие

Рассмотрим синхронное действие, в котором возможны два типа возвращаемых значений:

[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<Product> GetById_ActionResultOfT(int id)
{
    var product = _productContext.Products.Find(id);
    return product == null ? NotFound() : product;
}

В предшествующем действии:

  • возвращается код состояния 404, если продукт не существует в базе данных;
  • возвращается код состояния 200 с соответствующим объектом Product, если продукт существует.

Асинхронное действие

Рассмотрим асинхронное действие, в котором возможны два типа возвращаемых значений:

[HttpPost()]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<Product>> CreateAsync_ActionResultOfT(Product product)
{
    if (product.Description.Contains("XYZ Widget"))
    {
        return BadRequest();
    }

    _productContext.Products.Add(product);
    await _productContext.SaveChangesAsync();

    return CreatedAtAction(nameof(GetById_ActionResultOfT), new { id = product.Id }, product);
}

В предшествующем действии:

  • Код состояния 400 (BadRequest) возвращается средой выполнения ASP.NET Core, когда:
    • Атрибут [ApiController] был применен, и проверка модели не пройдена.
    • Описание продукта содержит XYZ Widget.
  • Код состояния 201 генерируется методом CreatedAtAction при создании продукта. В этом пути к коду объект Product предоставляется в тексте ответа. Также предоставляется заголовок ответа Location с URL-адресом только что созданного продукта.

Тип HttpResults

Помимо встроенных типов результатов MVC (IActionResultи ActionResult<T>), ASP.NET Core включает типы HttpResults, которые можно использовать как в минимальных API, так и в веб-API.

Отличается от типов результатов, относящихся к MVC, :HttpResults

  • Реализация результатов, обрабатываемая вызовом IResult.ExecuteAsync.

  • Не использует настроенные форматировщики. Не используется настроенные средства форматирования:

    • Некоторые функции, такие как Content negotiation недоступны.
    • Созданное решение Content-Type определяется реализацией HttpResults .

Это HttpResults может быть полезно при совместном использовании кода между минимальными API и веб-API.

Тип IResult

Пространство Microsoft.AspNetCore.Http.HttpResults имен содержит классы, реализующие IResult интерфейс. Интерфейс IResult определяет контракт, представляющий результат конечной точки HTTP. Статический класс Results используется для создания разных объектов IResult, которые представляют разные типы ответов.

Встроенная таблица результатов показывает распространенные вспомогательные средства результатов.

Рассмотрим следующий код:

[HttpGet("{id}")]
[ProducesResponseType(typeof(Product), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IResult GetById(int id)
{
    var product = _productContext.Products.Find(id);
    return product == null ? Results.NotFound() : Results.Ok(product);
}

В предшествующем действии:

  • возвращается код состояния 404, если продукт не существует в базе данных;
  • Код состояния 200 возвращается с соответствующим Product объектом, когда продукт существует, сформированный results.Ok <T>().

Рассмотрим следующий код:

[HttpPost]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(typeof(Product), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IResult> CreateAsync(Product product)
{
    if (product.Description.Contains("XYZ Widget"))
    {
        return Results.BadRequest();
    }

    _productContext.Products.Add(product);
    await _productContext.SaveChangesAsync();

    var location = Url.Action(nameof(GetById), new { id = product.Id }) ?? $"/{product.Id}";
    return Results.Created(location, product);
}

В предшествующем действии:

  • Код состояния 400 возвращается, когда:
    • Атрибут [ApiController] был применен, и проверка модели не пройдена.
    • Описание продукта содержит XYZ Widget.
  • Код состояния 201 генерируется методом Results.Create при создании продукта. В этом пути к коду объект Product предоставляется в тексте ответа. Также предоставляется заголовок ответа Location с URL-адресом только что созданного продукта.

Результаты<TResult1, тип TResultN>

Статический класс TypedResults возвращает конкретную IResult реализацию, которая позволяет использовать IResult в качестве возвращаемого типа. Использование конкретной IResult реализации обеспечивает следующее преимущество по сравнению с типом IResult:

  • [ProducesResponseType] Все атрибуты можно исключить, так как HttpResult реализация автоматически вносит свой вклад в метаданные конечной точки.

Если требуется несколько IResult типов возврата, возврат Results<TResult1, TResultN> предпочтителен по сравнению с возвратом IResult. Results<TResult1, TResultN> Возврат предпочтителен, так как универсальные типы объединения автоматически сохраняют метаданные конечной точки.

Results<TResult1, TResultN> Типы объединения реализуют неявные операторы приведения, чтобы компилятор может автоматически преобразовать типы, указанные в универсальных аргументах, в экземпляр типа объединения. Это дает дополнительное преимущество предоставления проверка времени компиляции, что обработчик маршрутов фактически возвращает результаты, объявленные им. Попытка вернуть тип, который не объявлен как один из универсальных аргументов, приводит к Results<> ошибке компиляции.

Рассмотрим следующий код:

[HttpGet("{id}")]
public Results<NotFound, Ok<Product>> GetById(int id)
{
    var product = _productContext.Products.Find(id);
    return product == null ? TypedResults.NotFound() : TypedResults.Ok(product);
}

В предшествующем действии:

  • возвращается код состояния 404, если продукт не существует в базе данных;
  • Код состояния 200 возвращается с соответствующим Product объектом при наличии продукта, созданного типом TypedResults.Ok<T>.
[HttpPost]
public async Task<Results<BadRequest, Created<Product>>> CreateAsync(Product product)
{
    if (product.Description.Contains("XYZ Widget"))
    {
        return TypedResults.BadRequest();
    }

    _productContext.Products.Add(product);
    await _productContext.SaveChangesAsync();

    var location = Url.Action(nameof(GetById), new { id = product.Id }) ?? $"/{product.Id}";
    return TypedResults.Created(location, product);
}

В предшествующем действии:

  • Код состояния 400 возвращается, когда:
    • Атрибут [ApiController] был применен, а проверка модели завершается ошибкой.
    • Описание продукта содержит XYZ Widget.
  • Код состояния 201 генерируется методом TypedResults.Create при создании продукта. В этом пути к коду объект Product предоставляется в тексте ответа. Также предоставляется заголовок ответа Location с URL-адресом только что созданного продукта.

Дополнительные ресурсы

Просмотреть или скачать образец кода (описание загрузки)

ASP.NET Core предоставляет следующие параметры для типов возвращаемых значений действий контроллера веб-API:

В этом документе объясняется, когда лучше использовать каждый тип возвращаемого значения.

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

Простейшее действие возвращает элементарный или сложный тип данных (например, string или пользовательский тип объекта). Рассмотрим следующее действие, которое возвращает коллекцию пользовательских объектов Product:

[HttpGet]
public List<Product> Get() =>
    _repository.GetProducts();

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

Если возможны несколько типов возвращаемых данных, обычно можно смешать ActionResult возвращаемый тип с примитивным или сложным типом возвращаемого значения. Для удовлетворения этого типа действия необходимы либо IActionResult, либо ActionResult<T>. В этом документе представлено несколько примеров нескольких типов возвращаемых данных.

Возврат IEnumerable<T> или IAsyncEnumerable<T>

ASP.NET Core буферизирует результат действий, возвращаемых IEnumerable<T> перед записью в ответ. Рассмотрите возможность объявления возвращаемого типа подписи действия, чтобы IAsyncEnumerable<T> гарантировать асинхронную итерацию. То есть режим итерации зависит от возвращаемого базового конкретного типа. MVC автоматически буферизует все конкретные типы, которые реализуют IAsyncEnumerable<T>.

Рассмотрим следующее действие, которое возвращает записи о продуктах со сниженной ценой как IEnumerable<Product>:

[HttpGet("syncsale")]
public IEnumerable<Product> GetOnSaleProducts()
{
    var products = _repository.GetProducts();

    foreach (var product in products)
    {
        if (product.IsOnSale)
        {
            yield return product;
        }
    }
}

Эквивалентом IAsyncEnumerable<Product> для предшествующего действия является:

[HttpGet("asyncsale")]
public async IAsyncEnumerable<Product> GetOnSaleProductsAsync()
{
    var products = _repository.GetProductsAsync();

    await foreach (var product in products)
    {
        if (product.IsOnSale)
        {
            yield return product;
        }
    }
}

Тип IActionResult

Тип возвращаемого значения IActionResult можно использовать, если в действии допускаются несколько типов возвращаемого значения ActionResult. Типы ActionResult представляют различные коды состояния HTTP. Любой неабстрактный класс, унаследованный от квалификаторов ActionResult в виде допустимого типа возвращаемого значения. Некоторые распространенные типы возвращаемых значений в этой категории: BadRequestResult (400), NotFoundResult (404) и OkObjectResult (200). Кроме того, удобные методы в классе ControllerBase можно использовать для получения типов ActionResult из действия. Например, return BadRequest(); — это сокращенная форма return new BadRequestResult();.

Так как в типе действия есть несколько типов возвращаемого значения и путей, необходимо использовать атрибут [ProducesResponseType]. Этот атрибут создает описательные сведения об ответе для страниц справки по веб-API, создаваемых с помощью таких инструментов, как Swagger. [ProducesResponseType] указывает известные типы и коды состояния HTTP, возвращаемые действием.

Синхронное действие

Рассмотрим следующее синхронное действие, в котором возможны два типа возвращаемых значений:

[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Product))]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult GetById(int id)
{
    if (!_repository.TryGetProduct(id, out var product))
    {
        return NotFound();
    }

    return Ok(product);
}

В предшествующем действии:

  • возвращается код состояния 404, если продукт, представленный id, не существует в базовом хранилище данных; вызывается удобный метод NotFound в качестве сокращения return new NotFoundResult();.
  • Код состояния 200 возвращается с объектом Product, если продукт не существует. вызывается удобный метод Ok в качестве сокращения return new OkObjectResult(product);.

Асинхронное действие

Рассмотрим следующее асинхронное действие, в котором возможны два типа возвращаемых значений:

[HttpPost]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> CreateAsync(Product product)
{
    if (product.Description.Contains("XYZ Widget"))
    {
        return BadRequest();
    }

    await _repository.AddProductAsync(product);

    return CreatedAtAction(nameof(GetById), new { id = product.Id }, product);
}

В предшествующем действии:

  • Код состояния 400 возвращается, если описание продукта содержит строку XYZ Widget. вызывается удобный метод BadRequest в качестве сокращения return new BadRequestResult();.
  • Код состояния 201 генерируется удобным методом CreatedAtAction при создании продукта. В качестве альтернативы вызову CreatedAtAction можно использовать return new CreatedAtActionResult(nameof(GetById), "Products", new { id = product.Id }, product);. В этом пути к коду объект Product предоставляется в тексте ответа. Также предоставляется заголовок ответа Location с URL-адресом только что созданного продукта.

Например, следующая модель указывает на то, что запросы должны включать свойства Name и Description. Если Name и Description не были указаны в запросе, происходит сбой проверки модели.

public class Product
{
    public int Id { get; set; }

    [Required]
    public string Name { get; set; }

    [Required]
    public string Description { get; set; }

    public bool IsOnSale { get; set; }
}

[ApiController] Если атрибут применен, ошибки проверки модели приводят к коду состояния 400. Дополнительные сведения см. в разделе Автоматические отклики HTTP 400.

ActionResult и IActionResult

В следующем разделе сравнивается ActionResult с IActionResult

Тип ActionResult<T>

ASP.NET Core включает тип возвращаемого значения ActionResult<T> для действий контроллера веб-API. Он позволяет возвращать тип, производный от ActionResult или определенный тип. ActionResult<T> имеет следующие преимущества по сравнению с типом IActionResult:

  • Свойство Type атрибута [ProducesResponseType] можно исключить. Например, [ProducesResponseType(200, Type = typeof(Product))] упрощается до [ProducesResponseType(200)]. Ожидаемый тип возвращаемого значения действия вместо этого выводится из T в ActionResult<T>.
  • Операторы неявного приведения поддерживают преобразование T и ActionResult в ActionResult<T>. T преобразуется в ObjectResult, то есть return new ObjectResult(T); упрощается до return T;.

C# не поддерживает операторы неявных приведений в интерфейсах. Следовательно, для преобразования в конкретный тип необходимо использовать ActionResult<T>. Например, использование IEnumerable не работает в следующем примере:

[HttpGet]
public ActionResult<IEnumerable<Product>> Get() =>
    _repository.GetProducts();

Один из способов исправить приведенный выше код — возвратить _repository.GetProducts().ToList();.

Большинство действий имеют тип возвращаемого значения. При выполнении действия могут возникнуть непредвиденные условия, и в этом случае определенный тип не возвращается. Например, входной параметр действия может не пройти проверку модели. В этом случае обычно возвращается подходящий тип ActionResult вместо конкретного типа.

Синхронное действие

Рассмотрим синхронное действие, в котором возможны два типа возвращаемых значений:

[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<Product> GetById(int id)
{
    if (!_repository.TryGetProduct(id, out var product))
    {
        return NotFound();
    }

    return product;
}

В предшествующем действии:

  • возвращается код состояния 404, если продукт не существует в базе данных;
  • возвращается код состояния 200 с соответствующим объектом Product, если продукт существует.

Асинхронное действие

Рассмотрим асинхронное действие, в котором возможны два типа возвращаемых значений:

[HttpPost]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<Product>> CreateAsync(Product product)
{
    if (product.Description.Contains("XYZ Widget"))
    {
        return BadRequest();
    }

    await _repository.AddProductAsync(product);

    return CreatedAtAction(nameof(GetById), new { id = product.Id }, product);
}

В предшествующем действии:

  • Код состояния 400 (BadRequest) возвращается средой выполнения ASP.NET Core, когда:
    • Атрибут [ApiController] был применен, и проверка модели не пройдена.
    • Описание продукта содержит XYZ Widget.
  • Код состояния 201 генерируется методом CreatedAtAction при создании продукта. В этом пути к коду объект Product предоставляется в тексте ответа. Также предоставляется заголовок ответа Location с URL-адресом только что созданного продукта.

Дополнительные ресурсы