当WebApi的GET操作未找到内容时,我将返回一个NotFound IHttpActionResult
。除此之外,我想发送自定义消息和/或异常消息(如果有)。当前的ApiController
的NotFound()
方法不提供传递消息的重载选项。
有没有什么办法可以做到这一点?还是我必须编写自己的自定义IHttpActionResult
?
当WebApi的GET操作未找到内容时,我将返回一个NotFound IHttpActionResult
。除此之外,我想发送自定义消息和/或异常消息(如果有)。当前的ApiController
的NotFound()
方法不提供传递消息的重载选项。
有没有什么办法可以做到这一点?还是我必须编写自己的自定义IHttpActionResult
?
以下是一行代码,用于返回一个具有简单消息的IHttpActionResult NotFound:
return Content(HttpStatusCode.NotFound, "Foo does not exist.");
如果您想自定义响应消息的格式,您需要编写自己的操作结果。
我们希望为一些常见的响应消息形状提供开箱即用的支持,例如简单的空404,但我们也希望保持这些结果尽可能简单;使用操作结果的主要优点之一是它使您的操作方法更容易进行单元测试。我们在操作结果上放置的属性越多,您的单元测试需要考虑的事情就越多,以确保操作方法正在按预期运行。
通常我希望能够提供自定义消息的功能,因此请随时向我们记录错误以考虑在未来版本中支持该操作结果: https://aspnetwebstack.codeplex.com/workitem/list/advanced
不过,操作结果的一个好处是如果您想做一些略微不同的事情,那么您总是可以很容易地编写自己的操作结果。以下是在您的情况下如何实现它的方式(假设您希望错误消息采用text/plain格式;如果您想使用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.”部分(在调用扩展方法时不幸是必需的):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.");
}
}
如果您喜欢的话,可以使用 ResponseMessageResult
:
var myCustomMessage = "your custom message which would be sent as a content-negotiated response";
return ResponseMessage(
Request.CreateResponse(
HttpStatusCode.NotFound,
myCustomMessage
)
);
如果你需要更短的版本,那么我想你需要实现自定义操作结果。
catch (Exception exception)
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound)
{
ReasonPhrase = exception.Message
});
}
在 ASP.NET Core 中的一行代码:
Return StatusCode(404, "Not a valid request.");
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);
}
}
IExceptionHandler
类的主体中创建一个 IHttpActionResult
实例,以设置 ExceptionHandlerContext.Result
属性。然而,我还想设置自定义的 ReasonPhrase
。
我发现可以使用 ResponseMessageResult
来包装一个 HttpResponseMessage
(这允许轻松设置 ReasonPhrase)。
例如:
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" });
}
}
}
我通过简单地派生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);
}
}
NotFoundObjectResult(message)
。NegotitatedContentResult<T>
,并且不需要转换你的content
(例如,你只想返回一个字符串),那么你不需要重写ExecuteAsync
方法。
你只需要提供一个适当的类型定义和一个构造函数,告诉基类要返回哪个HTTP状态码。其他所有的都会自动工作。NotFound
和InternalServerError
的示例: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>
的 源代码 以了解它所做的所有事情。它没有太多内容。