繁体   English   中英

如何返回带有错误消息或异常的 NotFound() IHttpActionResult?

[英]How do I return NotFound() IHttpActionResult with an error message or exception?

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

有什么办法吗? 或者我将不得不编写自己的自定义IHttpActionResult

这是返回 IHttpActionResult NotFound 和一条简单消息的单行代码:

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

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

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

如果您愿意,可以使用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);
    }
}

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

如果您从基础NegotitatedContentResult<T>继承,如上所述,并且您不需要转换您的content (例如,您只想返回一个字符串),那么您不需要覆盖ExecuteAsync方法。

您需要做的就是提供一个适当的类型定义和一个构造函数,告诉 base 返回哪个 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>源代码以了解它所做的一切。 没什么可做的。

另一个不错的可能性是使用不同的内置结果类型: NotFoundObjectResult(message)

我知道 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)
  • 问题:结果不支持用body响应。 解决方案:继承并使用NegotiatedContentResult 请参阅附加更好的 NotFoundResult 类

asp.net核心中的一行代码:

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

需要返回 404 Not Found 的错误消息,我使用的是 Dot Net 6.0。

这是代码

Problem(statusCode: 404, detail: "Put your detailed error message here");

其中 Problem 是 ControllerBase 类中存在的方法。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM