为什么我应该使用IHttpActionResult而不是HttpResponseMessage?

362

我一直在使用WebApi进行开发,现在已经转向了WebApi2,微软引入了一个新的IHttpActionResult接口,似乎建议使用它而不是返回HttpResponseMessage。我对这个新接口的优势感到困惑。它似乎主要提供了一种稍微更容易创建HttpResponseMessage的方法。

我认为这是“为了抽象而抽象”,我有什么遗漏吗?除了可能节省一行代码外,我使用这个新接口可以获得什么实际的好处呢?

旧的方式(WebApi):

public HttpResponseMessage Delete(int id)
{
    var status = _Repository.DeleteCustomer(id);
    if (status)
    {
        return new HttpResponseMessage(HttpStatusCode.OK);
    }
    else
    {
        throw new HttpResponseException(HttpStatusCode.NotFound);
    }
}

新途径(WebApi2):

public IHttpActionResult Delete(int id)
{
    var status = _Repository.DeleteCustomer(id);
    if (status)
    {
        //return new HttpResponseMessage(HttpStatusCode.OK);
        return Ok();
    }
    else
    {
        //throw new HttpResponseException(HttpStatusCode.NotFound);
        return NotFound();
    }
}

1
我刚发现了一些有趣的东西。我不确定这些结果是否可以被其他人验证。但是,通过我所做的一些调用,性能得到了很大的提升:
  • 使用 HttpResponseMessage 的示例中,我在 9545 毫秒 内收到了响应。
  • 使用 IHttpActionResult,我在 294 毫秒 内收到了相同的响应。
- chris lesage
@chrislesage,9545毫秒几乎等于10秒。即使294毫秒也有点慢。如果有任何需要超过100毫秒的操作,则可能存在其他问题。这个故事还有更多内容等待揭示。 - AaronLS
8个回答

336

您可能决定不使用 IHttpActionResult,因为您现有的代码构建了一个HttpResponseMessage,它不符合预设响应之一。但是,您可以使用ResponseMessage的预设响应将HttpResponseMessage适应为IHttpActionResult。我花了一些时间才弄明白这一点,所以我想发布这篇文章来表明,您不必选择其中一个:

public IHttpActionResult SomeAction()
{
   IHttpActionResult response;
   //we want a 303 with the ability to set location
   HttpResponseMessage responseMsg = new HttpResponseMessage(HttpStatusCode.RedirectMethod);
   responseMsg.Headers.Location = new Uri("http://customLocation.blah");
   response = ResponseMessage(responseMsg);
   return response;
}

请注意,ResponseMessageApiController 基类的一个方法,您的控制器应该继承该基类。

3
花了我一些时间才发现ResponseMessage现在改名为ResponseMessageResult。也许它最近被重命名了?附言:您还错过了答案中的一个新关键字,非常感谢您的建议 :) - Ilya Chernomordik
14
ResponseMessageResponseMessageResult是两个不同的东西。ResponseMessage()ApiController的一个方法,你的控制器应该继承自它,因此它只是一个方法调用。所以在那里不需要使用new关键字。你可能没有继承自ApiController或者在一个静态方法中。ResponseMessageResultResponseMessage()的返回类型。 - AaronLS
3
@IlyaChernomordik 我本可以写成 response = base.ResponseMessage(responseMsg),以更清晰地表明它是基类 ApiController 的一个方法。 - AaronLS
谢谢,我的确有一个服务没有继承自ApiController,这就是为什么花费了一些时间找到它的原因。 - Ilya Chernomordik
1
@AaronLS是正确的。应该是return new ResponseMessageResult(new HttpResponseMessage(HttpStatusCode.RedirectMethod)); - arviman

88
您仍然可以使用HttpResponseMessage。这种功能不会消失。我和您有着相同的想法,并与团队进行了广泛的讨论,认为不需要额外的抽象层。一些论点被提出来试图证明它存在的必要性,但没有什么能说服我这是值得的。
直到我看到来自Brad Wilson这个示例。如果您以可以链接的方式构造IHttpActionResult类,您将获得创建“操作级”响应管道以生成HttpResponseMessage的能力。在幕后,这就是ActionFilters的实现方式,然而,在读取操作方法时,这些ActionFilters的排序并不明显,这就是我不喜欢操作过滤器的一个原因。
然而,通过在您的操作方法中创建可以明确链接的IHttpActionResult,您可以组合各种不同的行为来生成响应。

73
以下是Microsoft ASP.Net Documentation中提到的IHttpActionResult相对于HttpResponseMessage的几个优点:
  • 简化了控制器单元测试。
  • 将创建HTTP响应的常见逻辑移至单独的类中。
  • 通过隐藏构建响应的低级细节,使控制器操作的意图更加清晰。
但使用IHttpActionResult还有其他值得一提的优点:
  • 遵循单一职责原则:使操作方法负责服务 HTTP 请求,不让其涉及到创建 HTTP 响应消息。
  • 在 System.Web.Http.Results 中已经定义了一些 有用的实现,包括: Ok NotFound Exception Unauthorized BadRequest Conflict Redirect InvalidModelState (链接到完整列表)
  • 默认使用 异步和等待
  • 通过实现 ExecuteAsync 方法,轻松创建自己的 ActionResult
  • 您可以使用 ResponseMessageResult ResponseMessage(HttpResponseMessage response)将 HttpResponseMessage 转换为 IHttpActionResult

35
// this will return HttpResponseMessage as IHttpActionResult
return ResponseMessage(httpResponseMessage); 

20

这只是我的个人意见,Web API团队的人可能能更好地表达它,但以下是我的想法。

首先,我认为这不是一个取代另一个的问题。您可以根据操作方法中要做什么来使用两者,但为了理解 IHttpActionResult 的真正威力,您可能需要走出那些方便的帮助器方法,如ApiController中的 Ok,NotFound等等。

基本上,我认为实现 IHttpActionResult 接口的类就像是 HttpResponseMessage 的工厂。有了这种思维方式,它现在变成了一个需要返回的对象和生成它的工厂。从一般编程的角度来看,在某些情况下,您可以自己创建对象,在某些情况下,您需要一个工厂来完成。在这里也是如此。

如果您想返回通过复杂逻辑构建的响应,比如很多响应头等等,您可以将所有逻辑抽象到实现 IHttpActionResult 接口的操作结果类中,并在多个操作方法中使用它来返回响应。

使用 IHttpActionResult 作为返回类型的另一个优点是,它使得ASP.NET Web API动作方法类似于MVC。您可以返回任何操作结果,而不会被媒体格式化器限制。

当然,正如Darrel所指出的那样,您可以链接操作结果并创建类似于API管道中的消息处理程序本身的强大微型管道。这取决于您的操作方法的复杂性。

长话短说 - 这不是 IHttpActionResultHttpResponseMessage 的对立。基本上,它是关于您如何创建响应。自己动手还是通过工厂。


我对工厂模式的问题在于,我可以轻松地创建静态方法,例如 ResponseFactory.CreateOkResponse(),返回 HttpResponseMessage,而且在创建响应时不必处理异步操作。其中一位团队成员提到了异步操作可能会在需要进行 I/O 以生成标头值时有用。不过我不确定这种情况有多常见。 - Darrel Miller
我认为这就像问我们为什么需要工厂方法模式。为什么不只使用静态方法来创建对象呢?当然,这是可行的 - 并非每个对象创建都需要一个适当的工厂模式。但在我看来,这取决于您想如何建模您的类。您想将创建不同类型响应的逻辑全部塞进一个类中,以多个静态方法的形式呈现,还是基于接口创建不同的类。同样,没有好坏之分。这完全取决于情况。无论如何,我的观点是,这不是一种胜过另一种的情况,我个人更喜欢工厂方法 :) - Badri

8
Web API 基本上返回 4 种类型的对象:voidHttpResponseMessageIHttpActionResult 和其他强类型。Web API 的第一个版本返回 HttpResponseMessage,它是非常直接的 HTTP 响应消息。 IHttpActionResult 是由 WebAPI 2 引入的一种 HttpResponseMessage 封装。它包含 ExecuteAsync() 方法来创建一个 HttpResponseMessage。它简化了控制器的单元测试。
其他返回类型是通过 Web API 使用媒体格式化程序序列化为响应主体的强类型类。缺点是您不能直接返回错误代码(如 404)。你只能抛出一个 HttpResponseException 错误。

4
我们使用IHttpActionResult相比HttpResponseMessage有以下好处:
  1. 使用IHttpActionResult,我们只关注要发送的数据而不是状态码。因此,代码更加简洁易维护。
  2. 实现控制器方法的单元测试将更加容易。
  3. 默认使用asyncawait

3
我更倾向于为IHttpActionResult实现TaskExecuteAsync接口函数。类似这样的内容:
    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        var response = _request.CreateResponse(HttpStatusCode.InternalServerError, _respContent);

        switch ((Int32)_respContent.Code)
        { 
            case 1:
            case 6:
            case 7:
                response = _request.CreateResponse(HttpStatusCode.InternalServerError, _respContent);
                break;
            case 2:
            case 3:
            case 4:
                response = _request.CreateResponse(HttpStatusCode.BadRequest, _respContent);
                break;
        } 

        return Task.FromResult(response);
    }

这里的_request是HttpRequest,_respContent是有效负载。


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