如何返回带有错误消息或异常的 NotFound() IHttpActionResult?

当在我的 WebApi GET 操作中没有找到某些内容时,我将返回 NotFound IHttpActionResult。除了这个响应,我还想发送一个自定义消息和/或异常消息(如果有的话)。当前 ApiControllerNotFound()方法不提供重载来传递消息。

有没有什么办法可以做到这一点? 或者我将不得不写我自己的定制 IHttpActionResult

113273 次浏览

可以使用 HttpResponseMessage 类的 RequonPhase 属性

catch (Exception exception)
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound)
{
ReasonPhrase = exception.Message
});
}

如果你喜欢,你可以使用 ResponseMessageResult:

var myCustomMessage = "your custom message which would be sent as a content-negotiated response";
return ResponseMessage(
Request.CreateResponse(
HttpStatusCode.NotFound,
myCustomMessage
)
);

是的,如果您需要更短的版本,那么我想您需要实现您的自定义操作结果。

如果要自定义响应消息形状,则需要编写自己的操作结果。

我们希望提供最常见的响应消息形状,例如简单的空404,但我们也想让这些结果尽可能简单; 使用动作结果的一个主要优点是,它使您的动作方法更容易进行单元测试。我们在操作结果上放置的属性越多,单元测试需要考虑的事情就越多,以确保操作方法正在执行您所期望的操作。

I often want the ability to provide a custom message as well, so feel free to log a bug for us to consider supporting that action result in a future release: Https://aspnetwebstack.codeplex.com/workitem/list/advanced

不过,关于行动结果的一个好处是,如果你想做一些稍微不同的事情,你总是可以相当容易地编写自己的结果。下面是您在自己的情况下可能会采用的方法(假设您希望使用文本/纯文本格式的错误消息; 如果您希望使用 JSON,那么您将对内容进行稍微不同的处理) :

public class NotFoundTextPlainActionResult : IHttpActionResult
{
public NotFoundTextPlainActionResult(string message, HttpRequestMessage request)
{
if (message == null)
{
throw new ArgumentNullException("message");
}


if (request == null)
{
throw new ArgumentNullException("request");
}


Message = message;
Request = request;
}


public string Message { get; private set; }


public HttpRequestMessage Request { get; private set; }


public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
return Task.FromResult(Execute());
}


public HttpResponseMessage Execute()
{
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.NotFound);
response.Content = new StringContent(Message); // Put the message in the response body (text/plain content).
response.RequestMessage = Request;
return response;
}
}


public static class ApiControllerExtensions
{
public static NotFoundTextPlainActionResult NotFound(this ApiController controller, string message)
{
return new NotFoundTextPlainActionResult(message, controller.Request);
}
}

然后,在你的行动方法中,你可以这样做:

public class TestController : ApiController
{
public IHttpActionResult Get()
{
return this.NotFound("These are not the droids you're looking for.");
}
}

如果使用自定义控制器基类(而不是直接从 ApiController 继承) ,也可以消除“ this”Part (不幸的是,在调用扩展方法时需要使用该部分) :

public class CustomApiController : ApiController
{
protected NotFoundTextPlainActionResult NotFound(string message)
{
return new NotFoundTextPlainActionResult(message, Request);
}
}


public class TestController : CustomApiController
{
public IHttpActionResult Get()
{
return NotFound("These are not the droids you're looking for.");
}
}

我通过简单地从 OkNegotiatedContentResult派生并在结果响应消息中重写 HTTP 代码来解决这个问题。此类允许您使用任何 HTTP 响应代码返回内容正文。

public class CustomNegotiatedContentResult<T> : OkNegotiatedContentResult<T>
{
public HttpStatusCode HttpStatusCode;


public CustomNegotiatedContentResult(
HttpStatusCode httpStatusCode, T content, ApiController controller)
: base(content, controller)
{
HttpStatusCode = httpStatusCode;
}


public override Task<HttpResponseMessage> ExecuteAsync(
CancellationToken cancellationToken)
{
return base.ExecuteAsync(cancellationToken).ContinueWith(
task => {
// override OK HTTP status code with our own
task.Result.StatusCode = HttpStatusCode;
return task.Result;
},
cancellationToken);
}
}

您可以按照 d3m3t3er 的建议创建自定义协商内容结果。但我会继承。另外,如果只是为了返回 NotFind 才需要它,则不需要从构造函数初始化 http 状态。

public class NotFoundNegotiatedContentResult<T> : NegotiatedContentResult<T>
{
public NotFoundNegotiatedContentResult(T content, ApiController controller)
: base(HttpStatusCode.NotFound, content, controller)
{
}


public override Task<HttpResponseMessage> ExecuteAsync(
CancellationToken cancellationToken)
{
return base.ExecuteAsync(cancellationToken).ContinueWith(
task => task.Result, cancellationToken);
}
}

下面是返回带有简单消息的 IHttpActionResult NotFound 的一行程序:

return Content(HttpStatusCode.NotFound, "Foo does not exist.");

If you inherit from the base NegotitatedContentResult<T>, as mentioned, and you don't need to transform your content (e.g. you just want to return a string), then 您不需要重写 ExecuteAsync方法。

你所需要做的就是提供一个合适的类型定义和一个构造函数来告诉基本 HTTP状态码返回哪个。其他一切都很正常。

下面是 NotFoundInternalServerError的例子:

public class NotFoundNegotiatedContentResult : NegotiatedContentResult<string>
{
public NotFoundNegotiatedContentResult(string content, ApiController controller)
: base(HttpStatusCode.NotFound, content, controller) { }
}


public class InternalServerErrorNegotiatedContentResult : NegotiatedContentResult<string>
{
public InternalServerErrorNegotiatedContentResult(string content, ApiController controller)
: base(HttpStatusCode.InternalServerError, content, controller) { }
}

然后您可以为 ApiController创建相应的扩展方法(如果有的话,也可以在基类中创建) :

public static NotFoundNegotiatedContentResult NotFound(this ApiController controller, string message)
{
return new NotFoundNegotiatedContentResult(message, controller);
}


public static InternalServerErrorNegotiatedContentResult InternalServerError(this ApiController controller, string message)
{
return new InternalServerErrorNegotiatedContentResult(message, controller);
}

然后它们就像内置的方法一样工作。您可以调用现有的 NotFound(),也可以调用新的自定义 NotFound(myErrorMessage)

当然,您可以去掉自定义类型定义中的“硬编码”字符串类型,如果您愿意,可以将其保留为泛型,但是 必须考虑 ExecuteAsync的问题,这取决于您的 <T>实际上是什么。

你可以看看 源代码NegotiatedContentResult<T>,看看它所做的一切。

我需要在 IExceptionHandler类的主体中创建一个 IHttpActionResult实例,以便设置 ExceptionHandlerContext.Result属性。然而,我也想设置一个自定义 ReasonPhrase

I found that a ResponseMessageResult could wrap a HttpResponseMessage (which allows ReasonPhrase to be set easily).

For Example:

public class MyExceptionHandler : ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
var ex = context.Exception as IRecordNotFoundException;
if (ex != null)
{
context.Result = new ResponseMessageResult(new HttpResponseMessage(HttpStatusCode.NotFound) { ReasonPhrase = $"{ex.EntityName} not found" });
}
}
}

我知道 PO 用消息文本请求,但是另一个只返回404的选项是使方法返回 IHttpActionResult 并使用 StatusCode 函数

    public async Task<IHttpActionResult> Get([FromUri]string id)
{
var item = await _service.GetItem(id);
if(item == null)
{
StatusCode(HttpStatusCode.NotFound);
}
return Ok(item);
}

Answers here are missing a little developer story problem. The ApiController class is still exposing a NotFound() method that developers may use. This would cause some 404 response to contain a uncontrolled result body.

I present here a few parts of code "更好的 ApiController NotFind 方法" that will provide a less error-prone method that does not require developers to know "the better way of sending a 404".

  • 创建一个名为 < a href = “ https://gist.github.com/sandrock/726cb5c1862688ff2127e13120adac92 # file-apiconController-cs”rel = “ nofollow noReferrer”> ApiController的类 继承自 ApiController
    • 我使用这种技术来防止开发人员使用原始类
  • 覆盖它的 NotFound方法 ,让开发人员使用第一个可用的 API
  • if you want to discourage this, mark this as [Obsolete("Use overload instead")]
  • 增加一个额外的 protected NotFoundResult NotFound(string message),你想鼓励
  • 问题: 结果不支持使用 body 进行响应。解决方案: 继承并使用 NegotiatedContentResult。参见附加的 更好的 NotFoundResult 类

Asp.net 核心中的一行代码:

Return StatusCode(404, "Not a valid request.");

另一个很好的可能性是使用不同的内置结果类型: NotFoundObjectResult(message)