【问题标题】:How do I return NotFound() IHttpActionResult with an error message or exception?如何返回带有错误消息或异常的 NotFound() IHttpActionResult?
【发布时间】:2013-12-07 00:34:35
【问题描述】:

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

有没有办法做到这一点?或者我将不得不编写自己的自定义IHttpActionResult

【问题讨论】:

  • 您想为所有 Not Found 结果返回相同的消息吗?
  • @NikolaiSamteladze 不,可能会根据情况提供不同的信息。

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


【解决方案1】:

这里有一个简单的消息返回 IHttpActionResult NotFound 的方法:

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

【讨论】:

  • 人们应该投票赞成这个答案。这很好,很容易!
  • 请注意,此解决方案不会将 HTTP 标头状态设置为“404 Not Found”。
  • @KasperHalvasJensen 服务器的http状态码是404,你还需要什么吗?
  • @AnthonyF 你是对的。我正在使用 Controller.Content(...)。应该使用 ApiController.Content(...) - 我的错。
  • 谢谢伙计,这正是我要找的
【解决方案2】:

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

我们希望为简单的空 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.");
    }
}

【讨论】:

  • 我编写了与“IHttpActionResult”完全相同的实现,但不是针对“NotFound”结果。这可能适用于所有“HttpStatusCodes”。我的 CustomActionResult 代码类似于 this 而我的控制器的 'Get()' 操作如下所示: 'public IHttpActionResult Get() { return CustomNotFoundResult("Meessage to Return."); }' 另外,我在 CodePlex 上记录了 bug,以便在未来的版本中考虑这一点。
  • 我使用 ODataControllers,我不得不使用 this.NotFound("blah");
  • 非常好的帖子,但我只想建议不要继承提示。我的团队很久以前就决定这样做,这样做会使课程变得臃肿。我最近刚刚将所有这些重构为扩展方法,并远离了继承链。我强烈建议人们仔细考虑何时应该使用这样的继承。通常,组合要好得多,因为它更加解耦。
  • 这个功能应该是开箱即用的。包含可选的“ResponseBody”参数不应影响单元测试。
【解决方案3】:

如果你愿意,可以使用ResponseMessageResult

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

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

【讨论】:

  • 我采用了这种方法,因为它看起来很整洁。我只是在别处定义了自定义消息并缩进了返回码。
  • 我比 Content 更喜欢这个,因为它实际上返回了一个我可以用 Message 属性解析的对象,就像标准的 BadRequest 方法一样。
【解决方案4】:

您可以使用 HttpResponseMessage 类的 ReasonPhrase 属性

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

【讨论】:

  • 谢谢。好吧..这应该可以,但是我必须在每个操作中自己构建 HttpResponseException 。为了减少代码,我在考虑是否可以使用任何 WebApi 2 功能(就像现成的 NotFount()Ok() 方法一样)并通过ReasonPhrase 消息给它。
  • 您可以创建自己的扩展方法 NotFound(Exception exception),它会抛出正确的 HttpResponseException
  • @DmytroRudenko:引入操作结果是为了提高可测试性。通过在此处抛出 HttpResponseException,您将受到影响。同样在这里我们没有任何例外,但 OP 正在寻找发回消息。
  • 好的,如果您不想使用 NUint 进行测试,您可以编写自己的 NotFoundResult 实现并重写其 ExecuteAsync 以返回您的消息数据。并作为您的操作调用的结果返回此类的实例。
  • 请注意,现在您可以直接传递状态码,例如HttpResponseException(HttpStatusCode.NotFound)
【解决方案5】:

您可以按照 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】:

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

    【讨论】:

      【解决方案7】:

      我需要在 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】:

        如果您从基类NegotitatedContentResult&lt;T&gt; 继承,如上所述,并且您不需要转换您的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)

        当然,如果需要,您可以摆脱自定义类型定义中的“硬编码”字符串类型并保留其通用性,但是您可能不得不担心@ 987654332@ 的东西,取决于你的 &lt;T&gt; 实际上是什么。

        您可以查看source codeNegotiatedContentResult&lt;T&gt; 以了解它的所有功能。没什么大不了的。

        【讨论】:

          【解决方案9】:

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

          【讨论】:

            【解决方案10】:

            这里的答案缺少一个小开发者故事问题。 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

            【讨论】:

              【解决方案11】:

              asp.net core中的一行代码:

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

              【讨论】:

                猜你喜欢
                • 2017-10-29
                • 2018-05-31
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-08-20
                • 1970-01-01
                • 1970-01-01
                • 2011-07-16
                相关资源
                最近更新 更多