HttpResponseMessage和HttpResponseException有什么区别?

59

我试着理解两者并编写了示例代码:

 public HttpResponseMessage Get()
 {
     var response = ControllerContext.Request
                         .CreateResponse(HttpStatusCode.BadRequest, "abc");

     throw new HttpResponseException(response);
 }

并且:

 public HttpResponseMessage Get()
 {
     return ControllerContext.Request
                        .CreateResponse(HttpStatusCode.BadRequest, "abc");
 }

看了Fiddle的结果,我真的没发现它们之间有什么区别,那么使用 HttpResponseException 的目的是什么?


5个回答

67

两者的主要区别在于:异常用于立即停止处理并退出。例如,假设我有以下代码:

public class CustomerController : ApiController {
  private ICustomerContext repo;

  public CustomerController(ICustomerContext repo) {
    this.repo = repo;
  }

  public Customer Get(int id) {
    var customer = repo.Customers.SingleOrDefault(c=>c.CustomerID == id);
    if (customer == null) {
      throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
    }
    return customer;
  }
}
如果这段代码运行,并且我传递了一个不存在的id,它将立即停止处理并返回404状态码。
如果我返回HttpResponseMessage,请求将愉快地继续其余的处理,并返回404。主要区别在于是否结束请求。
正如Darrel所说,异常在某些情况下非常有用,比如当找到顾客时我希望处理继续进行,在其他情况下则不需要。
你可能想使用类似HttpResponseMessage这样的东西来返回状态码201并设置位置头,通常在Http POST中使用。在那种情况下,我确实希望处理继续进行。这是我会用这段代码实现的内容。
public class CustomerController : ApiController {
  private ICustomerContext repo;

  public CustomerController(ICustomerContext repo) {
    this.repo = repo;
  }

  public HttpResponseMessage Post(Customer customer) {
    repo.Add(customer);
    repo.SaveChanges();
    var response = Request.CreateResponse(HttpStatusCode.Created, customer);
    response.Headers.Location = new Uri(Request.RequestUri, string.format("customer/{0}", customer.id));
    return response;
  }
}

*注意:如果您正在使用beta版本,则需要创建新的HttpResponseMessage。但我正在使用较新的版本,所以需要使用Request的CreateResponse扩展方法。

以上代码创建了一个响应,将状态码设置为201,将客户端传递进去,然后设置位置头。

然后返回该响应并继续处理请求。

希望这能帮到您。


3
我想补充一点,异常处理/日志记录是另一个原因。您可能希望抛出以记录404错误,但不希望抛出并记录412错误。 - Luke Puplett
5
我真的很困惑你所说的“请求继续处理”的意思是什么。难道当你使用return时,请求不是已经完成了吗? - user247702
1
@Stijn - Glenn的博客(http://goo.gl/ErSG9h)上有一些评论,使得问题更加清晰。听起来Glenn指的是Web.Api在控制器返回后进行的处理,例如消息处理程序。然而,Steven Solomon质疑这是否是事实。 - Iain
@Iain谢谢你提供的链接,这是一个有趣的评论。我并不是不相信Glenn,但如果他能够证明这种差异就太好了。 - user247702
1
@GlennBlock,我不确定这是否是正确的答案。这个答案似乎有所不同。 - Richard Tozier

28

当您的控制器操作签名如下时,HttpResponseException很有用:

  Foo Get(int id)

在这种情况下,你无法轻松地返回状态码(如400)。

请注意,在Web API的下一个版本中,HttpResponseMessage<T>将被淘汰。


4
只有通用的HttpResponseMessage<T>将被移除。 - ozba
@ozba 谢谢,我没有注意到标记语法会防止尖括号出现。 - Darrel Miller
这也是在《Professional ASP.Net MVC 4》中提到的情况,http://www.wrox.com/WileyCDA/WroxTitle/Professional-ASP-NET-MVC-4.productCd-111834846X.html。当您的API基于返回像`Foo`这样的域类型时,如果出现错误,您无法返回`HttpResponseMessage`,但可以返回/抛出`HttpResponseException`,从而向使用者提供状态码等信息。为什么要返回域类型?与其他答案不同,它们说这简化了处理域对象而不是像`HttpResponseMessage`这样的低级框架对象的UT。 - rism
@rism HttpResponseMessage不是一个“低级框架”对象,它是代表你返回的HTTP消息的对象。HTTP是一个应用层协议。你不会通过HTTP返回对象,而是返回HTTP消息。试图假装你正在返回域对象将会在长期运行中带来很多痛苦。 - Darrel Miller
好的,你需要与Brad Wilson沟通,他说:“然而,返回响应对象是一项相当低级的操作,因此Web API控制器几乎总是返回原始对象值(或值序列)。” “返回原始对象的能力非常强大,但是我们在远离ActionResult的过程中失去了某些东西;即成功和失败返回不同值的能力。” 就我个人而言,我会根据情况挑选使用,但考虑到有这么多内置的HttpResponseMessage支持,似乎没有理由不使用它。 - rism
@rism IHttpActionResult是一个合理的折衷方案。http://www.bizcoder.com/composing-api-responses-for-maximum-reuse - Darrel Miller

13

假设您想对响应进行单元测试,始终返回HttpResponseMessage是否有意义?我不太喜欢从ApiController返回纯类型的想法,因为它不遵循典型的开发模式。

在一个非Web API类中获取客户端时,您可能会返回null,并且调用代码检查null响应:

public Customer GetCustomer(int id)
{
    return db.Customers.Find(id);
}

但在Web API中,你不会返回null,你必须返回一些东西,即使那些东西是在你抛出HttpResponseException之后创建的。在这种情况下,为了方便测试,为什么不始终返回一个HttpResponseMessage,并将其作为您的签名?

public HttpResponseMessage GetCustomer(int id)
{
    var customer = db.Customers.Find(id);
    if (customer == null)
    {
        return Request.CreateResponse(HttpStatusCode.NotFound);
    }

    return Request.CreateResponse(HttpStatusCode.OK, customer);
}

1
我同意,Brian。这也为返回对象和状态码提供了更好的学习路径。当模板将返回类型设置为业务对象时,它会让你想知道处理其他响应的正确方式是什么。 - Luke Puplett
5
@Brian这是一种风格问题。有些人希望深入了解HTTP并感觉它是应用程序设计的一部分。其他人认为HTTP是一个实现细节,更愿意使用ActionFilters和HttpMessageHandlers来抽象化HTTP。如果你知道你在做什么,这两种方法都没有错。我倾向于采取你的方法,因为我觉得隐藏HTTP会使事情看起来太像RPC。 - Darrel Miller
这是我自然而然地做的方式。它被称为WebAPI - 所以对我来说,它应该始终返回有效的HTTP响应 - 并且该响应告诉调用者发生了什么事情,这很合理。 - Morvael

2

HttpResponseException继承自Exception并嵌入了HttpResponseMessage。 由于它继承自Exception,因此在try-catch场景中非常有用。

HttpResponseException默认返回的状态码是HttpStatusCode.InternalServerError


0

正如原始问题所述,返回的响应没有实质性差别。

HttpResponseException 的真正目的是允许子方法创建并“抛出”自己的 HttpResponseMessages,这些消息流回调用堆栈并返回给客户端。

public class CustomerController : ApiController {
  private ICustomerContext repo;
  public CustomerController(ICustomerContext repo) {
    this.repo = repo;
  }

  public HttpResponseMessage Get(int id) {

    Customer customer = getCustomer(id);

    return Request.CreateResponse(customer);
  }

  private Customer getCustomer(int id){
    .....do some work
    .....we have a problem so throw exception
    throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.BadRequest, "Id out of range");
    return repo.Customers.SingleOrDefault(c=>c.CustomerID == id)
}

请原谅任何错误,代码是即兴编写的。抛出的HttpResponseException会通过操作调用堆栈冒泡上升,不会被普通的异常处理程序捕获,并像操作方法本身一样返回其HttpResponseMessage。


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