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


98

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

有什么办法吗?还是我必须写自己的习惯IHttpActionResult


您是否要为所有未找到的结果返回相同的消息?
Nikolai Samteladze 2013年

@NikolaiSamteladze不,根据情况可能是不同的消息。
Ajay Jadhav 2013年

Answers:


84

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

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

1
我编写了与“ IHttpActionResult”完全相似的实现,但没有专门针对“ NotFound”结果。这可能适用于所有“ HttpStatusCodes”。我的CustomActionResult代码看起来像这样, 而我控制器的'Get()'操作看起来像这样:'public IHttpActionResult Get(){return CustomNotFoundResult(“ Meessage to Return。”); }'另外,我在CodePlex上记录了一个错误,以在将来的版本中考虑此问题。
Ajay Jadhav 2013年

我使用ODataControllers,不得不使用this.NotFound(“ blah”);
Jerther 2014年

1
非常好的帖子,但我只想针对继承技巧提出建议。我的团队很久以前就决定这样做,并且这样做使课程大大膨胀。我最近才将其全部重构为扩展方法,并脱离了继承链。我强烈建议人们仔细考虑何时应该使用这种继承。通常,合成要好得多,因为它解耦得多。
julealgon 2015年

6
此功能应该是开箱即用的。包含可选的“ ResponseBody”参数不应影响单元测试。
Theodore Zographos

230

这是一个返回带有简单消息的IHttpActionResult NotFound的单行代码:

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

24
人们应该投票赞成这个答案。很好,很容易!
杰西2015年

2
请注意,此解决方案不会将HTTP标头状态设置为“ 404 Not Found”。
Kasper Halvas Jensen

4
@KasperHalvasJensen服务器上的http状态代码是404,您还需要其他内容吗?
安东尼F

4
@AnthonyF你是对的。我正在使用Controller.Content(...)。Shoud使用过ApiController.Content(...)-我的糟糕。
Kasper Halvas Jensen

谢谢队友,这正是我想要的
Kaptein Babbalas 16-10-6

28

ResponseMessageResult如果愿意,可以使用:

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

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


我采用这种方法,因为它看起来很整洁。我只是在其他地方定义了自定义消息并缩进了返回代码。
ozzy432836 '16

我比Content更好,因为它实际上返回了一个我可以使用Message属性解析的对象,就像标准的BadRequest方法一样。
user1568891

7

您可以使用HttpResponseMessage类的ReasonPhrase属性

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

谢谢。嗯..这应该工作,但是随后我将不得不在每个操作中自行构建HttpResponseException。为了使代码更少,我在考虑是否可以使用任何WebApi 2功能(就像现成的NotFount()Ok()方法一样)并将ReasonPhrase消息传递给它。
Ajay Jadhav 2013年

您可以创建自己的扩展方法NotFound(Exception exception),该方法将引发正确的HttpResponseException
Dmytro Rudenko

@DmytroRudenko:引入了动作结果以提高可测试性。通过在此处抛出HttpResponseException,您将对此有所妥协。同样在这里,我们没有任何例外,但是OP正在寻找发回消息。
Kiran Challa

好的,如果您不想使用NUint进行测试,则可以编写自己的NotFoundResult实现,并重写其ExecuteAsync以返回消息数据。并根据您的操作调用返回此类的实例。
Dmytro Rudenko 2013年

1
请注意,现在您可以直接传递状态代码,例如HttpResponseException(
HttpStatusCode.NotFound

3

您可以按照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);
    }
}

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

1

NegotitatedContentResult<T>如前所述,如果您从base继承,而无需转换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)

当然,您可以摆脱自定义类型定义中的“硬编码”字符串类型,并根据需要将其保留为通用类型,但是您可能不得不担心这些ExecuteAsync内容,具体取决于您的<T>实际情况。

您可以通过看源代码NegotiatedContentResult<T>看到它所做的一切。没什么。


1

为了设置属性,我需要IHttpActionResultIExceptionHandler类的主体中创建一个实例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" });
        }
    }
}

0

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

0

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

我在这里介绍了一些代码“ 更好的ApiController NotFound方法 ”,它将提供一种不易出错的方法,该方法不需要开发人员知道“发送404的更好方法”。

  • 创建一个类继承ApiController称为ApiController
    • 我使用这种技术来防止开发人员使用原始类
  • 覆盖其NotFound方法,使开发人员可以使用第一个可用的api
  • 如果您不想这样做,请将其标记为 [Obsolete("Use overload instead")]
  • 添加一个额外的 protected NotFoundResult NotFound(string message)您想要鼓励
  • 问题:结果不支持身体回应。解决方案:继承和使用NegotiatedContentResult。见附上更好的NotFoundResult类
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.