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

↘锁芯ラ 提交于 2019-11-28 03:14:01

You'd need to write your own action result if you want to customize the response message shape.

We wanted to provide the most common response message shapes out of the box for things like simple empty 404s, but we also wanted to keep these results as simple as possible; one of the main advantages of using action results is that it makes your action method much easier to unit test. The more properties we put on action results, the more things your unit test needs to consider to make sure the action method is doing what you'd expect.

I often want the ability to provide a custom message as well, so feel free to log a bug for us to consider supporting that action result in a future release: https://aspnetwebstack.codeplex.com/workitem/list/advanced

One nice thing about action results, though, is that you can always write your own fairly easily if you want to do something slightly different. Here's how you might do it in your case (assuming you want the error message in text/plain; if you want JSON, you'd do something slightly different with the content):

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

Then, in your action method, you can just do something like this:

public class TestController : ApiController
{
    public IHttpActionResult Get()
    {
        return this.NotFound("These are not the droids you're looking for.");
    }
}

If you used a custom controller base class (instead of directly inheriting from ApiController), you could also eliminate the "this." part (which is unfortunately required when calling an extension method):

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

Here's a one-liner for returning a IHttpActionResult NotFound with a simple message:

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

You could use ResponseMessageResult if you like:

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

yeah, if you need much shorter versions, then I guess you need to implement your custom action result.

You may use ReasonPhrase property of HttpResponseMessage class

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

I solved it by simply deriving from OkNegotiatedContentResult and overriding the HTTP code in the resulting response message. This class allows you to return the content body with any HTTP response code.

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

You can create a custom negotiated content result as d3m3t3er suggested. However I would inherit from. Also, if you need it only for returning NotFound, you don't need to initialize the http status from constructor.

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

If you inherit from the base NegotitatedContentResult<T>, as mentioned, and you don't need to transform your content (e.g. you just want to return a string), then you don't need to override the ExecuteAsync method.

All you need to do is provide an appropriate type definition and a constructor that tells the base which HTTP Status Code to return. Everything else just works.

Here are examples for both NotFound and 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) { }
}

And then you can create corresponding extension methods for ApiController (or do it in a base class if you have one):

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

And then they work just like the built-in methods. You can either call the existing NotFound() or you can call your new custom NotFound(myErrorMessage).

And of course, you can get rid of the "hard-coded" string types in the custom type definitions and leave it generic if you want, but then you may have to worry about the ExecuteAsync stuff, depending on what your <T> actually is.

You can look over the source code for NegotiatedContentResult<T> to see all it does. There isn't much to it.

I was needing to create an IHttpActionResult instance in the body of an IExceptionHandler class, in order to set the ExceptionHandlerContext.Result property. However I also wanted to set a custom ReasonPhrase.

I found that a ResponseMessageResult could wrap a HttpResponseMessage (which allows ReasonPhrase to be set easily).

For Example:

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" });
        }
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!