ASP.NET Core Web API 中控制器操作的返回类型

注意

此版本不是本文的最新版本。 对于当前版本,请参阅此文的 .NET 8 版本

重要

此信息与预发布产品相关,相应产品在商业发布之前可能会进行重大修改。 Microsoft 对此处提供的信息不提供任何明示或暗示的保证。

对于当前版本,请参阅此文的 .NET 8 版本

查看或下载示例代码如何下载

ASP.NET Core 提供以下 Web API 控制器操作返回类型选项:

本文说明了每种返回类型的最佳适用情况。

特定类型

最基本的操作返回基元或复杂数据类型(例如string或自定义对象)。 请参考以下操作,该操作返回自定义 Product 对象的集合:

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

无需防范已知条件,返回特定类型即可满足要求。 上述操作不接受任何参数,因此不需要参数约束验证。

如果存在多种返回类型,通常会将 ActionResult 返回类型与基元或复杂返回类型混合。 要支持此类操作,必须使用 IActionResultActionResult<T>。 本文中提供了多个返回类型的几个示例。

返回 IEnumerable<T> 或 IAsyncEnumerable<T>

有关性能注意事项,请参阅返回IEnumerable<T>IAsyncEnumerable<T>

ASP.NET Core 缓冲返回IEnumerable<T>的操作的结果,然后再将其写入响应。 考虑将操作签名的返回类型声明为IAsyncEnumerable<T>以保证异步迭代。 最终,迭代模式基于要返回的基础具体类型,所选格式化程序会影响结果的处理方式:

  • 使用System.Text.Json格式化程序时,MVC 依赖于System.Text.Json添加到流式传输结果的支持。
  • 使用Newtonsoft.JsonXML-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 类型

当操作中可能有多个 ActionResult 返回类型时,适合使用 IActionResult 返回类型。 ActionResult 类型表示多种 HTTP 状态代码。 派生自 ActionResult 的任何非抽象类都限定为有效的返回类型。 此类别中的某些常见返回类型为 BadRequestResult (400)、NotFoundResult (404) 和 OkObjectResult (200)。 或者,可以使用 ControllerBase 类中的便利方法从操作返回 ActionResult 类型。 例如,return BadRequest();return new BadRequestResult(); 的简写形式。

由于此操作类型中有多个返回类型和路径,因此必须自由使用 [ProducesResponseType] 特性。 此特性可针对 Swagger 等工具生成的 Web API 帮助页生成更多描述性响应详细信息。 [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);
}

在上述操作中:

  • id 代表的产品不在基础数据存储中时,则返回 404 状态代码。 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);
}

在上述操作中:

  • 当产品说明包含“XYZ 小组件”时,返回 400 状态代码。 BadRequest 便利方法作为 return new BadRequestResult(); 的简写调用。

  • 在创建产品后,CreatedAtAction 便利方法生成 201 状态代码。 以下代码是调用CreatedAtAction的替代方法:

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

    在前面的代码路径中,在响应正文中提供Product对象。 提供了包含新建产品 URL 的 Location 响应标头。

例如,以下模型指明请求必须包含 NameDescription 属性。 未在请求中提供 NameDescription 会导致模型验证失败。

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

以下部分比较 ActionResultIActionResult

ActionResult<T> 类型

ASP.NET Core 包括面向 Web API 控制器操作的ActionResult<T>返回类型。 它支持返回从ActionResult派生的类型或返回特定类型ActionResult<T> 通过 IActionResult 类型可提供以下优势:

  • 可排除 [ProducesResponseType] 特性的 Type 属性。 例如,[ProducesResponseType(200, Type = typeof(Product))] 简化为 [ProducesResponseType(200)]。 此操作的预期返回类型根据ActionResult<T>中的T进行推断。
  • 隐式强制转换运算符支持将 TActionResult 均转换为 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);
}

在上述操作中:

  • 在以下情况下,ASP.NET Core 运行时返回 400 状态代码 (BadRequest):
    • 已应用 [ApiController] 特性,且模型验证失败。
    • 产品说明包含“XYZ 小组件”。
  • 在创建产品后,CreatedAtAction 方法生成 201 状态代码。 在此代码路径中,将在响应正文中提供 Product 对象。 提供了包含新建产品 URL 的 Location 响应标头。

HttpResults 类型

除了特定于 MVC 的内置结果类型(IActionResultActionResult<T>)之外,ASP.NET Core 还包括可在最小 API和 Web API 中使用的HttpResults类型。

不同于 MVC 特定的结果类型,HttpResults

  • 是通过调用IResult.ExecuteAsync处理的结果实现。

  • 利用配置的格式化程序。 不利用配置的格式化程序意味着:

    • Content negotiation等一些功能不可用。
    • 生成的Content-TypeHttpResults实现决定。

在最小 API 和 Web API 之间共享代码时,HttpResults可能很有用。

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 小组件”。
  • 在创建产品后,Results.Create 方法生成 201 状态代码。 在此代码路径中,将在响应正文中提供 Product 对象。 提供了包含新建产品 URL 的 Location 响应标头。

Results<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 小组件”。
  • 在创建产品后,TypedResults.Created 方法生成 201 状态代码。 在此代码路径中,将在响应正文中提供 Product 对象。 提供了包含新建产品 URL 的 Location 响应标头。

其他资源

查看或下载示例代码如何下载

ASP.NET Core 提供以下 Web API 控制器操作返回类型选项:

本文说明了每种返回类型的最佳适用情况。

特定类型

最基本的操作返回基元或复杂数据类型(例如string或自定义对象)。 请参考以下操作,该操作返回自定义 Product 对象的集合:

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

无需防范已知条件,返回特定类型即可满足要求。 上述操作不接受任何参数,因此不需要参数约束验证。

如果存在多种返回类型,通常会将 ActionResult 返回类型与基元或复杂返回类型混合。 要支持此类操作,必须使用 IActionResultActionResult<T>。 本文中提供了多个返回类型的几个示例。

返回 IEnumerable<T> 或 IAsyncEnumerable<T>

有关性能注意事项,请参阅返回IEnumerable<T>IAsyncEnumerable<T>

ASP.NET Core 缓冲返回IEnumerable<T>的操作的结果,然后再将其写入响应。 考虑将操作签名的返回类型声明为IAsyncEnumerable<T>以保证异步迭代。 最终,迭代模式基于要返回的基础具体类型,所选格式化程序会影响结果的处理方式:

  • 使用System.Text.Json格式化程序时,MVC 依赖于System.Text.Json添加到流式传输结果的支持。
  • 使用Newtonsoft.JsonXML-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 类型

当操作中可能有多个 ActionResult 返回类型时,适合使用 IActionResult 返回类型。 ActionResult 类型表示多种 HTTP 状态代码。 派生自 ActionResult 的任何非抽象类都限定为有效的返回类型。 此类别中的某些常见返回类型为 BadRequestResult (400)、NotFoundResult (404) 和 OkObjectResult (200)。 或者,可以使用 ControllerBase 类中的便利方法从操作返回 ActionResult 类型。 例如,return BadRequest();return new BadRequestResult(); 的简写形式。

由于此操作类型中有多个返回类型和路径,因此必须自由使用 [ProducesResponseType] 特性。 此特性可针对 Swagger 等工具生成的 Web API 帮助页生成更多描述性响应详细信息。 [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);
}

在上述操作中:

  • id 代表的产品不在基础数据存储中时,则返回 404 状态代码。 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);
}

在上述操作中:

  • 当产品说明包含“XYZ 小组件”时,返回 400 状态代码。 BadRequest 便利方法作为 return new BadRequestResult(); 的简写调用。

  • 在创建产品后,CreatedAtAction 便利方法生成 201 状态代码。 以下代码是调用CreatedAtAction的替代方法:

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

    在前面的代码路径中,在响应正文中提供Product对象。 提供了包含新建产品 URL 的 Location 响应标头。

例如,以下模型指明请求必须包含 NameDescription 属性。 未在请求中提供 NameDescription 会导致模型验证失败。

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

以下部分比较 ActionResultIActionResult

ActionResult<T> 类型

ASP.NET Core 包括面向 Web API 控制器操作的ActionResult<T>返回类型。 它支持返回从ActionResult派生的类型或返回特定类型ActionResult<T> 通过 IActionResult 类型可提供以下优势:

  • 可排除 [ProducesResponseType] 特性的 Type 属性。 例如,[ProducesResponseType(200, Type = typeof(Product))] 简化为 [ProducesResponseType(200)]。 此操作的预期返回类型根据ActionResult<T>中的T进行推断。
  • 隐式强制转换运算符支持将 TActionResult 均转换为 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);
}

在上述操作中:

  • 在以下情况下,ASP.NET Core 运行时返回 400 状态代码 (BadRequest):
    • 已应用 [ApiController] 特性,且模型验证失败。
    • 产品说明包含“XYZ 小组件”。
  • 在创建产品后,CreatedAtAction 方法生成 201 状态代码。 在此代码路径中,将在响应正文中提供 Product 对象。 提供了包含新建产品 URL 的 Location 响应标头。

HttpResults 类型

除了特定于 MVC 的内置结果类型(IActionResultActionResult<T>)之外,ASP.NET Core 还包括可在最小 API和 Web API 中使用的HttpResults类型。

不同于 MVC 特定的结果类型,HttpResults

  • 是通过调用IResult.ExecuteAsync处理的结果实现。

  • 利用配置的格式化程序。 不利用配置的格式化程序意味着:

    • Content negotiation等一些功能不可用。
    • 生成的Content-TypeHttpResults实现决定。

在最小 API 和 Web API 之间共享代码时,HttpResults可能很有用。

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 小组件”。
  • 在创建产品后,Results.Create 方法生成 201 状态代码。 在此代码路径中,将在响应正文中提供 Product 对象。 提供了包含新建产品 URL 的 Location 响应标头。

Results<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 小组件”。
  • 在创建产品后,TypedResults.Create 方法生成 201 状态代码。 在此代码路径中,将在响应正文中提供 Product 对象。 提供了包含新建产品 URL 的 Location 响应标头。

其他资源

查看或下载示例代码如何下载

ASP.NET Core 提供以下 Web API 控制器操作返回类型选项:

本文档说明每种返回类型的最佳适用情况。

特定类型

最简单的操作返回基元或复杂数据类型(如 string 或自定义对象类型)。 请参考以下操作,该操作返回自定义 Product 对象的集合:

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

在执行操作期间无需防范已知条件,返回特定类型即可满足要求。 上述操作不接受任何参数,因此不需要参数约束验证。

如果存在多种返回类型,通常会将 ActionResult 返回类型与基元或复杂返回类型混合。 要支持此类操作,必须使用 IActionResultActionResult<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 类型

当操作中可能有多个 ActionResult 返回类型时,适合使用 IActionResult 返回类型。 ActionResult 类型表示多种 HTTP 状态代码。 派生自 ActionResult 的任何非抽象类都限定为有效的返回类型。 此类别中的某些常见返回类型为 BadRequestResult (400)、NotFoundResult (404) 和 OkObjectResult (200)。 或者,可以使用 ControllerBase 类中的便利方法从操作返回 ActionResult 类型。 例如,return BadRequest();return new BadRequestResult(); 的简写形式。

由于此操作类型中有多个返回类型和路径,因此必须自由使用 [ProducesResponseType] 特性。 此特性可针对 Swagger 等工具生成的 Web API 帮助页生成更多描述性响应详细信息。 [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);
}

在上述操作中:

  • id 代表的产品不在基础数据存储中时,则返回 404 状态代码。 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);
}

在上述操作中:

  • 当产品说明包含“XYZ 小组件”时,返回 400 状态代码。 BadRequest 便利方法作为 return new BadRequestResult(); 的简写调用。
  • 在创建产品后,CreatedAtAction 便利方法生成 201 状态代码。 调用 CreatedAtAction 的替代方法是 return new CreatedAtActionResult(nameof(GetById), "Products", new { id = product.Id }, product);。 在此代码路径中,将在响应正文中提供 Product 对象。 提供了包含新建产品 URL 的 Location 响应标头。

例如,以下模型指明请求必须包含 NameDescription 属性。 未在请求中提供 NameDescription 会导致模型验证失败。

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

以下部分比较 ActionResultIActionResult

ActionResult<T> 类型

ASP.NET Core 包括面向 Web API 控制器操作的ActionResult<T>返回类型。 它支持返回从 ActionResult 派生的类型或返回特定类型ActionResult<T> 通过 IActionResult 类型可提供以下优势:

  • 可排除 [ProducesResponseType] 特性的 Type 属性。 例如,[ProducesResponseType(200, Type = typeof(Product))] 简化为 [ProducesResponseType(200)]。 此操作的预期返回类型改为根据 ActionResult<T> 中的 T 进行推断。
  • 隐式强制转换运算符支持将 TActionResult 均转换为 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);
}

在上述操作中:

  • 在以下情况下,ASP.NET Core 运行时返回 400 状态代码 (BadRequest):
    • 已应用 [ApiController] 特性,且模型验证失败。
    • 产品说明包含“XYZ 小组件”。
  • 在创建产品后,CreatedAtAction 方法生成 201 状态代码。 在此代码路径中,将在响应正文中提供 Product 对象。 提供了包含新建产品 URL 的 Location 响应标头。

其他资源