异步任务 IActionResult 和 Task<T> 的区别

17

我有一个只有一个操作的控制器。在这个操作方法中,我调用了一个async方法,然后就这样了。这是我正在使用的代码:

我有一个只有一个操作的控制器。在这个操作方法中,我调用了一个async方法,然后就这样了。这是我正在使用的代码:

[HttpGet]
public Task<MyObject> Get()
{
    return _task.GetMyObject()
}

这个对象序列化为我所期望的JSON格式。但是我的经理坚持要将签名更改为以下内容:

[HttpGet]
public async Task<IActionResult> Get()
{
    var data = await _task.GetMyObject();
    return Ok(data);
}

我认为在控制器中没有理由使用 await,可以直接返回 Task,因为后面没有任何依赖于结果。除了为 await 创建状态机等额外的代码生成之外,从 WebApi 的角度来看,这种方法有什么影响吗?为了澄清,我想知道返回一个 IActionResult 是否比只返回 Task<MyObject> 更好,即使它们看起来得到的结果相同。


3
不是重复问题,只是稍微使用了 await 关键字并不意味着它与另一个带有 await 关键字的问题重复。 - Akash Kava
@SeM 不是重复内容。我已经看过那个帖子了,但我想了解更多关于IActionResult响应的信息。 - JohanP
@JohanP:这个问题之前已经被问过了,可以看看这里 - Tseng
@tseng 这差不多就是了,IActionResult 是其中的一部分,另一部分是我等待异步调用然后返回 Ok() 是否会有任何损失/收益,或者只返回我的任务是否会有任何损失/收益,以及我会失去/获得哪些东西。 - JohanP
显示剩余2条评论
4个回答

12

任务< T>

优势

单元测试不需要任何转换,

 Product product = await controller.Get();

一个很大的优势是,你的单元测试变得与底层HTTP堆栈完全独立。

Swagger不需要任何额外的属性来生成响应模式,因为Swagger可以轻松检测结果类型。

另一个很大的优势是,在逻辑保持不变的情况下,您可以在其他控制器中重用您的控制器。

此外,避免在返回之前使用await会略微提高性能,因为该部分代码不需要任务状态机。我认为未来的C#版本将省略单个等待作为编译器优化。

缺点

返回错误状态码需要抛出异常。

    throw new HttpStatusException(404, "File not found");
    throw new HttpStatusException(409, "Unauthorized");

任务<IAsyncResult>

优点

您可以返回HTTP状态码,例如

 return NotFound(); // (Status Code = 404)
 return Unauthorized(); // (Status Code = 409)

缺点

单元测试需要额外的转换处理。

 Product productResult = ((await controller.Get()) as OkResult).Result as Product;

由于这样的强制转换,使得在其他控制器中重用你的控制器变得困难,导致逻辑重复。
Swagger生成器需要额外的属性来生成响应模式。
 [ProducesResponseType(typeof(Product), 200)]

只有在处理不属于单元测试和业务逻辑的逻辑时才建议使用此方法,例如与第三方服务集成的OAuth,其中您希望更专注于基于IActionResult的结果,例如ChallengeRedirect等。


它不仅仅是返回状态码,这也可以通过在http响应上设置属性来使用Task<T>实现。它允许您选择返回类型的序列化方式,例如使用FileResult返回/流式传输文件或使用JsonResult返回json,即使请求的媒体类型为xml并且请求了xml序列化器,也将始终返回json,甚至可以使用ViewResultPartialViewResult返回完整或部分呈现的html文件。 - Tseng
@Tseng 这个问题涉及到 Task<T>Task<IActionResult>,而不是 FileResultViewResult 等等。如果我在 Api 中期望一个包含 Xml 或 Json 模型的结果,为什么我要使用 FileResultViewResult 呢? - Akash Kava
翻白眼 FileResult IActionResult 的一种实现。当您有一个操作并启用 XML 格式化程序时,每个返回模型或 IActionResult 的控制器操作都将根据浏览器请求返回 Xml 或 Json,尽管您可以通过显式返回 JsonResult(或使用本质上执行相同操作的 Json 帮助器方法)来覆盖它。仅仅说 IActionResult 的唯一优点是返回状态码就是完全错误的 - Tseng
1
@Tseng,你真的认为我不知道FileResultIActionResult的实现吗?我的观点很简单,如果我期望得到一组字段的产品,那么我会用FileResult做什么呢?你是对的,IActionResult是最好的选择,继续使用它,没有人强迫你不使用它。 - Akash Kava
你对IActionResult的评论仅限于说明它的优点,即可以返回诸如HTTP状态码之类的内容。这确实体现了该类型的特点。 - Tseng
那么,在涉及业务逻辑的 API 中,90% 的情况下应该只需要使用 Task<T> 而不是 Task<IActionResult>,对吗? - Naga

5

操作可以返回任何东西,大多数情况下它们返回一个 IActionResult 实例(或对于异步方法为 Task<IActionResult>),该实例生成响应。操作方法负责选择返回哪种类型的响应,而操作结果则进行响应。

如果操作返回一个 IActionResult 实现者,并且控制器继承自 Controller,则开发人员有许多帮助程序对应于许多选择。返回非 IActionResult 类型的对象的操作结果将使用适当的 IOutputFormatter 实现进行序列化。

对于具有多个返回类型或选项的非平凡操作(例如,根据执行的操作结果不同的 HTTP 状态代码),请优先使用 IActionResult 作为返回类型。


3
ASP.NET MVC是一个约定优于配置的框架。这意味着你的代码的任何未来维护者,包括你自己,都会期望按照某种方式编写代码,以减少检查类文件数以进行更改或添加的数量。
虽然从技术上讲,你的两个选项的结果可能相同,但常规方法是使用async/await处理结果。除了这种约定之外的任何其他方法都可能会导致未来的维护者感到困惑。此外,MVC的未来发布可能会以未知的方式破坏你的代码,因为你没有遵循这种约定。
良好的软件开发团队领导应该灌输一种通过简化代码的潜在未来维护来减少组织整体人力需求的愿望。你的经理可能正在尝试推广这个概念。

1
ASP.NET Core团队在统一MVC和WEB API(控制器和API控制器)时,为了实现强大的异常处理机制,抽象出了IActionResult。在操作方法中,通过抛出异常来控制流程是一种反模式。
[HttpGet]
public async Task<MyObject> Get()
{
    var data = await _task.GetMyObject()
    if(data == null)
    {
        return NotFound(); //Or, return Request.CreateResponse(HttpStatusCode.NotFound)
        // Versus, throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
    }

    return Ok(data);
}

注意:在WEBAPI2时代,NotFound()Ok()等都是IHttpActionResult的东西,而不是asp.net core新出现的。

不,那不是我的意思。我的意思是按照我的描述所写的方式,返回任务并立即返回结果,而不是等待它。 - JohanP
@JohanP,返回任务而不等待它,这是相互矛盾的。你能澄清一下吗? - Bishnu Rawal
在操作方法中,您只需返回一个任务,框架将会等待它。 - JohanP

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接