如何使用错误消息或异常返回NotFound()IHttpActionResult?

时间:2013-11-22 07:44:00

标签: c# asp.net-web-api http-status-code-404 httpresponse

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

有没有办法做到这一点?或者我必须编写自己的自定义NotFound()

10 个答案:

答案 0 :(得分:198)

以下是使用简单消息返回IHttpActionResult NotFound的一行内容:

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

答案 1 :(得分:78)

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

我们希望提供开箱即用的最常见的响应消息形状,例如简单的空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”。 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.");
    }
}

答案 2 :(得分:28)

如果您愿意,可以使用ResponseMessageResult

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

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

答案 3 :(得分:7)

您可以使用HttpResponseMessage类

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

答案 4 :(得分:2)

我通过简单地从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);
    }
}

答案 5 :(得分:2)

您可以创建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);
    }
}

答案 6 :(得分:1)

如果您继承基地NegotitatedContentResult<T>,如上所述,并且您不需要转换content(例如,您只想返回一个字符串),那么你不需要覆盖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)

当然,你可以摆脱&#34;硬编码&#34;自定义类型定义中的字符串类型,如果需要,可以保留通用,但是可能必须担心ExecuteAsync内容,具体取决于<T>实际上是什么。

您可以查看NegotiatedContentResult<T>的{​​{3}},了解它的全部内容。它并不多。

答案 7 :(得分:1)

我需要在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" });
        }
    }
}

答案 8 :(得分:0)

知道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);
    }

答案 9 :(得分:0)

这里的答案缺少一个开发人员故事的小问题。 ApiController类仍在公开开发人员可以使用的NotFound()方法。这将导致某些404响应包含不受控制的结果主体。

我在这里展示了代码“ better ApiController NotFound method”的一些部分,这些部分将提供一种不那么容易出错的方法,不需要开发人员知道“发送404的更好方法”。

  • 创建一个继承自ApiController 的类,称为ApiController
    • 我使用这种技术来防止开发人员使用原始类
  • 覆盖其NotFound方法,以允许开发人员使用第一个可用的api
  • 如果您不想这样做,请将其标记为[Obsolete("Use overload instead")]
  • 添加您要鼓励的protected NotFoundResult NotFound(string message)
  • 问题:结果不支持身体回应。解决方案:继承并使用NegotiatedContentResult。参见附件better NotFoundResult class