Web Api 2 - 返回 NotFound(); vs 使用全局异常处理程序抛出异常
Web Api 2 - Returning NotFound(); vs throwing an exception with global exception handler
我正在使用 .net Web Api 2 开发 API。我看过很多关于 Web Api 版本 1 的博文和问题,但是相比之下,使用版本 2 中所做更改的答案似乎很少。
在控制器中比较这两种处理方式 'errors' ItemsController
一个。使用从 System.Web.Http.Results
创建对象的方法
// GET api/user/userID/item/itemID
[Route("{itemID:int}", Name="GetItem")]
[ResponseType(typeof(ItemDTO))]
public IHttpActionResult Get(int userID, int itemID)
{
if (userID < 0 || itemID < 0) return BadRequest("Provided user id or item id is not valid");
ItemDTO item = _repository.GetItem(itemID);
if (item == null) return NotFound();
if (item.UserID != userID) return BadRequest("Item userID does not match route userID");
return Ok<ItemDTO>(item);
}
乙。可以通过注册自定义全局异常处理程序捕获的抛出异常
// ex) in WebApiConfig.cs
// config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler());
public class GlobalExceptionHandler : ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
Exception exception = context.Exception;
HttpException httpException = exception as HttpException;
if (httpException != null)
{
context.Result = new SimpleErrorResult(context.Request, (HttpStatusCode)httpException.GetHttpCode(), httpException.Message);
return;
}
if (exception is RootObjectNotFoundException)
{
context.Result = new SimpleErrorResult(context.Request, HttpStatusCode.NotFound, exception.Message);
return;
}
if (exception is BadRouteParametersException || exception is RouteObjectPropertyMismatchException)
{
context.Result = new SimpleErrorResult(context.Request, HttpStatusCode.BadRequest, exception.Message);
return;
}
if (exception is BusinessRuleViolationException)
{
context.Result = new SimpleErrorResult(context.Request, (HttpStatusCode)422, exception.Message);
return;
}
context.Result = new SimpleErrorResult(context.Request, HttpStatusCode.InternalServerError, exception.Message);
}
}
GET api/user/userID/item/itemID
[Route("{itemID:int}", Name="GetItem")]
[ResponseType(typeof(ItemDTO))]
public IHttpActionResult Get(int userID, int itemID)
{
if (userID < 0 || itemID < 0)
throw new BadRouteParametersException("Provided user or item ID is not valid");
ItemDTO item = _repository.GetItem(itemID);
if (item.UserID != userID)
throw new RouteObjectPropertyMismatchException("Item userID does not match route userID");
return Ok<ItemDTO>(item);
}
这两个似乎都是有效的选择。由于我能够 return System.Web.Http.Results
对象,因此解决方案 A. 似乎是最好的。
但是考虑一下在我的 _repository
中,我的 GetItem
方法是这样实现的
public ItemDTO GetItem(int itemId)
{
ItemInfo itemInfo = ItemInfoProvider.GetItemInfo(itemId);
if (itemInfo == null) throw new RootObjectNotFoundException("Item not found");
ItemDTO item = _autoMapper.Map<ItemDTO>(itemInfo);
return item;
}
在这里,我可以跳过在 GetItem
中对 null 调用 autoMapper,也可以跳过在控制器中检查 null。
问题
- 哪种方式更有意义?
- 我应该尝试 A 和 B 的组合吗?
- 我应该尝试让我的控制器变薄,还是应该保留这种类型的验证和处理逻辑,因为我可以访问 NotFound() 和 BadRequest() 方法?
- 我应该在框架管道的其他地方执行这种类型的逻辑吗?
我意识到我的问题更具架构性,而不是 'how do i use this feature' 但是同样,我没有找到太多关于如何以及何时使用这些不同功能的解释。
从我的角度来看,全局异常处理程序使单元测试每个操作更容易(阅读:更清晰)。您现在正在检查特定的 [expected] 异常与(本质上)比较状态代码。 (404 vs. 500 vs. 等)它还使 changes/logging 的错误通知(在 global/unified 级别)变得更加容易,因为你有一个单一的责任单位。
比如,你更喜欢写哪个单元测试?
[Test]
public void Id_must_not_be_less_than_zero()
{
var fooController = new FooController();
var actual = fooController.Get(-1);
Assert.IsInstanceOfType(actual, typeof(BadRequestResult));
}
[Test]
[ExpectedException(typeof(BadRouteParametersException))]
public void Id_must_not_be_less_than_zero()
{
var fooController = new FooController();
var actual = fooController.Get(-1);
}
一般来说,我会说这更像是一种偏好,而不是一成不变的规则,你应该选择你认为最易于维护和最容易理解的任何东西,从入职的角度来看(项目上的新眼光)and/or 以后自己维护。
正如 Brad 所说,这在一定程度上取决于偏好。
使用 HTTP 代码与网络的工作方式一致,所以这是我的做法。
另一个考虑因素是抛出异常是有代价的。如果您愿意支付这笔费用,并在您的设计中考虑到这一点,那么做出这样的选择是可以的。请注意这一点,尤其是当您将异常用于并非真正异常的情况,而是您知道在正常应用程序流程中可能会遇到的事情时。
这是一个较旧的post,但这里有一个关于异常和性能主题的有趣讨论:
http://blogs.msdn.com/b/ricom/archive/2006/09/14/754661.aspx
及后续:
http://blogs.msdn.com/b/ricom/archive/2006/09/25/the-true-cost-of-net-exceptions-solution.aspx
我正在使用 .net Web Api 2 开发 API。我看过很多关于 Web Api 版本 1 的博文和问题,但是相比之下,使用版本 2 中所做更改的答案似乎很少。
在控制器中比较这两种处理方式 'errors' ItemsController
一个。使用从 System.Web.Http.Results
创建对象的方法
// GET api/user/userID/item/itemID
[Route("{itemID:int}", Name="GetItem")]
[ResponseType(typeof(ItemDTO))]
public IHttpActionResult Get(int userID, int itemID)
{
if (userID < 0 || itemID < 0) return BadRequest("Provided user id or item id is not valid");
ItemDTO item = _repository.GetItem(itemID);
if (item == null) return NotFound();
if (item.UserID != userID) return BadRequest("Item userID does not match route userID");
return Ok<ItemDTO>(item);
}
乙。可以通过注册自定义全局异常处理程序捕获的抛出异常
// ex) in WebApiConfig.cs
// config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler());
public class GlobalExceptionHandler : ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
Exception exception = context.Exception;
HttpException httpException = exception as HttpException;
if (httpException != null)
{
context.Result = new SimpleErrorResult(context.Request, (HttpStatusCode)httpException.GetHttpCode(), httpException.Message);
return;
}
if (exception is RootObjectNotFoundException)
{
context.Result = new SimpleErrorResult(context.Request, HttpStatusCode.NotFound, exception.Message);
return;
}
if (exception is BadRouteParametersException || exception is RouteObjectPropertyMismatchException)
{
context.Result = new SimpleErrorResult(context.Request, HttpStatusCode.BadRequest, exception.Message);
return;
}
if (exception is BusinessRuleViolationException)
{
context.Result = new SimpleErrorResult(context.Request, (HttpStatusCode)422, exception.Message);
return;
}
context.Result = new SimpleErrorResult(context.Request, HttpStatusCode.InternalServerError, exception.Message);
}
}
GET api/user/userID/item/itemID
[Route("{itemID:int}", Name="GetItem")]
[ResponseType(typeof(ItemDTO))]
public IHttpActionResult Get(int userID, int itemID)
{
if (userID < 0 || itemID < 0)
throw new BadRouteParametersException("Provided user or item ID is not valid");
ItemDTO item = _repository.GetItem(itemID);
if (item.UserID != userID)
throw new RouteObjectPropertyMismatchException("Item userID does not match route userID");
return Ok<ItemDTO>(item);
}
这两个似乎都是有效的选择。由于我能够 return System.Web.Http.Results
对象,因此解决方案 A. 似乎是最好的。
但是考虑一下在我的 _repository
中,我的 GetItem
方法是这样实现的
public ItemDTO GetItem(int itemId)
{
ItemInfo itemInfo = ItemInfoProvider.GetItemInfo(itemId);
if (itemInfo == null) throw new RootObjectNotFoundException("Item not found");
ItemDTO item = _autoMapper.Map<ItemDTO>(itemInfo);
return item;
}
在这里,我可以跳过在 GetItem
中对 null 调用 autoMapper,也可以跳过在控制器中检查 null。
问题
- 哪种方式更有意义?
- 我应该尝试 A 和 B 的组合吗?
- 我应该尝试让我的控制器变薄,还是应该保留这种类型的验证和处理逻辑,因为我可以访问 NotFound() 和 BadRequest() 方法?
- 我应该在框架管道的其他地方执行这种类型的逻辑吗?
我意识到我的问题更具架构性,而不是 'how do i use this feature' 但是同样,我没有找到太多关于如何以及何时使用这些不同功能的解释。
从我的角度来看,全局异常处理程序使单元测试每个操作更容易(阅读:更清晰)。您现在正在检查特定的 [expected] 异常与(本质上)比较状态代码。 (404 vs. 500 vs. 等)它还使 changes/logging 的错误通知(在 global/unified 级别)变得更加容易,因为你有一个单一的责任单位。
比如,你更喜欢写哪个单元测试?
[Test]
public void Id_must_not_be_less_than_zero()
{
var fooController = new FooController();
var actual = fooController.Get(-1);
Assert.IsInstanceOfType(actual, typeof(BadRequestResult));
}
[Test]
[ExpectedException(typeof(BadRouteParametersException))]
public void Id_must_not_be_less_than_zero()
{
var fooController = new FooController();
var actual = fooController.Get(-1);
}
一般来说,我会说这更像是一种偏好,而不是一成不变的规则,你应该选择你认为最易于维护和最容易理解的任何东西,从入职的角度来看(项目上的新眼光)and/or 以后自己维护。
正如 Brad 所说,这在一定程度上取决于偏好。
使用 HTTP 代码与网络的工作方式一致,所以这是我的做法。
另一个考虑因素是抛出异常是有代价的。如果您愿意支付这笔费用,并在您的设计中考虑到这一点,那么做出这样的选择是可以的。请注意这一点,尤其是当您将异常用于并非真正异常的情况,而是您知道在正常应用程序流程中可能会遇到的事情时。
这是一个较旧的post,但这里有一个关于异常和性能主题的有趣讨论:
http://blogs.msdn.com/b/ricom/archive/2006/09/14/754661.aspx
及后续:
http://blogs.msdn.com/b/ricom/archive/2006/09/25/the-true-cost-of-net-exceptions-solution.aspx