IHttpActionResult
当在WebApi GET操作中找不到某些内容时,我将返回NotFound 。与此响应一起,我要发送自定义消息和/或异常消息(如果有)。当前ApiController
的NotFound()
方法不提供重载来传递消息。
有什么办法吗?还是我必须写自己的习惯IHttpActionResult
?
IHttpActionResult
当在WebApi GET操作中找不到某些内容时,我将返回NotFound 。与此响应一起,我要发送自定义消息和/或异常消息(如果有)。当前ApiController
的NotFound()
方法不提供重载来传递消息。
有什么办法吗?还是我必须写自己的习惯IHttpActionResult
?
Answers:
如果要自定义响应消息形状,则需要编写自己的操作结果。
我们希望提供开箱即用的最常见的响应消息形状,例如简单的空404,但是我们也希望使这些结果尽可能简单。使用动作结果的主要优点之一是它使您的动作方法更容易进行单元测试。我们为操作结果提供的属性越多,单元测试就需要考虑的内容越多,以确保操作方法能够达到预期的效果。
我通常也希望能够提供自定义消息,因此请随时记录一个错误,以供我们考虑在将来的发行版中支持该操作结果: 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”。部分(不幸的是,调用扩展方法时需要):
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.");
}
}
这是一个返回带有简单消息的IHttpActionResult NotFound的单行代码:
return Content(HttpStatusCode.NotFound, "Foo does not exist.");
ResponseMessageResult
如果愿意,可以使用:
var myCustomMessage = "your custom message which would be sent as a content-negotiated response";
return ResponseMessage(
Request.CreateResponse(
HttpStatusCode.NotFound,
myCustomMessage
)
);
是的,如果您需要短得多的版本,那么我想您需要实现自定义操作结果。
您可以使用HttpResponseMessage类的ReasonPhrase属性
catch (Exception exception)
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound)
{
ReasonPhrase = exception.Message
});
}
您可以按照d3m3t3er的建议创建自定义的协商内容结果。但是我会继承。另外,如果只需要用于返回NotFound,则无需从构造函数初始化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);
}
}
我通过简单地从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);
}
}
NegotitatedContentResult<T>
如前所述,如果您从base继承,而无需转换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>
看到它所做的一切。没什么。
为了设置属性,我需要IHttpActionResult
在IExceptionHandler
类的主体中创建一个实例ExceptionHandlerContext.Result
。但是我也想设置一个习惯ReasonPhrase
。
我发现a ResponseMessageResult
可以包装a 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" });
}
}
}
Iknow 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);
}
这里的答案缺少一个开发人员故事的小问题。该ApiController
班还在暴露NotFound()
方法,开发人员可以使用。这将导致某些404响应包含不受控制的结果主体。
我在这里介绍了一些代码“ 更好的ApiController NotFound方法 ”,它将提供一种不易出错的方法,该方法不需要开发人员知道“发送404的更好方法”。
ApiController
称为ApiController
NotFound
方法,使开发人员可以使用第一个可用的api[Obsolete("Use overload instead")]
protected NotFoundResult NotFound(string message)
您想要鼓励NegotiatedContentResult
。见附上更好的NotFoundResult类。