43

Is there a way I can use the new IHttpActionResult interface to return a HttpStatusCode.NoContent response message?

I am currently using return new HttpResponseMessage( HttpStatusCode.NoContent ); and would like to convert this into return NoContent();.

IHttpActionResult has already got Ok(), Conflict() and NotFound() but I cannot find any for Forbidden() and NoContent() which I need to use in my project.

How easy is it to add other result types?

Liam
  • 27,717
  • 28
  • 128
  • 190
Intrepid
  • 2,781
  • 2
  • 29
  • 54

6 Answers6

57

There's no convenience method for no-content result because, by default, when a action returns void, the response will have the HTTP status 204.

If you wish to explicitly indicate that on the action, you could also return a StatusCode(HttpStatusCode.NoContent) from your action or a

ResponseMessage(new HttpResponseMessage(HttpStatusCode.NoContent)).

The Unauthorized() convenience method gives you a 401 status so, for Forbidden (403), you would also have to use StatusCode(HttpStatusCode.Forbidden) or

ResponseMessage(new HttpResponseMessage(HttpStatusCode.Forbidden))
Nerdroid
  • 13,398
  • 5
  • 58
  • 69
Rafael Companhoni
  • 1,780
  • 1
  • 15
  • 31
25

I found this example site that shows how to add a custom IHttpActionResult method and I've used this to create the Forbidden() and NoContent() methods with great success.

public abstract class CommonApiController : ApiController
{
    public class ForbiddenResult : IHttpActionResult
    {
        private readonly HttpRequestMessage _request;
        private readonly string _reason;

        public ForbiddenResult(HttpRequestMessage request,string reason)
        {
            _request = request;
            _reason = reason;
        }

        public ForbiddenResult(HttpRequestMessage request)
        {
            _request = request;
            _reason = "Forbidden";
        }

        public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
        {
            var response = _request.CreateResponse(HttpStatusCode.Forbidden,_reason);
            return Task.FromResult(response);
        }
    }

    public class NoContentResult : IHttpActionResult
    {
        private readonly HttpRequestMessage _request;
        private readonly string _reason;

        public NoContentResult(HttpRequestMessage request,string reason)
        {
            _request = request;
            _reason = reason;
        }

        public NoContentResult(HttpRequestMessage request)
        {
            _request = request;
            _reason = "No Content";
        }

        public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
        {
            var response = _request.CreateResponse(HttpStatusCode.NoContent,_reason);
            return Task.FromResult(response);
        }
    }
}

And then I can use it like this:

public class InvoiceController : CommonApiController
{
    public async Task<IHttpActionResult> Post([FromBody]Invoice invoice)
    {
        if(User.IsInRole("Readonly"))
        {
            return Forbidden();
        }

        // Rest of code

    }
}
Intrepid
  • 2,781
  • 2
  • 29
  • 54
9

I tried the @Intrepid implementation and I ran into some problems. I see two solutions here:

Solution 1: The part: return Forbidden(); should not work.

The compiler would not recognize this.

Instead it should be: return new ForbiddenResult(Request, "my reason");

UPDATE 1

Solution 2:

I think this is what @Interpid intended in his implementation, but he was missing a few things.

In order to use return Forbidden(); the CommonApiController should be updated with the functions that return the custom IHttpActionResult for Forbidden and NoContent

The class should look like this:

public abstract class CommonApiController: ApiController {

    protected ForbiddenResult Forbidden() {
     return new ForbiddenResult(this.Request);
    }

    protected ForbiddenResult Forbidden(string reason) {
     return new ForbiddenResult(this.Request, reason);
    }

    protected NoContentResult NoContent() {
     return new NoContentResult(this.Request);
    }

    public class ForbiddenResult: IHttpActionResult {
     private readonly HttpRequestMessage _request;
     private readonly string _reason;

     public ForbiddenResult(HttpRequestMessage request, string reason) {
      _request = request;
      _reason = reason;
     }

     public ForbiddenResult(HttpRequestMessage request) {
      _request = request;
      _reason = "Forbidden";
     }

     public Task < HttpResponseMessage > ExecuteAsync(CancellationToken cancellationToken) {
      var response = _request.CreateResponse(HttpStatusCode.Forbidden, _reason);
      return Task.FromResult(response);
     }
    }

    public class NoContentResult: IHttpActionResult {
     private readonly HttpRequestMessage _request;
     private readonly string _reason;

     public NoContentResult(HttpRequestMessage request, string reason) {
      _request = request;
      _reason = reason;
     }

     public NoContentResult(HttpRequestMessage request) {
      _request = request;
      _reason = "No Content";
     }

     public Task < HttpResponseMessage > ExecuteAsync(CancellationToken cancellationToken) {
      var response = _request.CreateResponse(HttpStatusCode.NoContent, _reason);
      return Task.FromResult(response);
     }
    }
   }

Anyway, if I am wrong and @Interpid's answer is correct. What am I missing here to make his implementation work?

Asaf Epelbaum
  • 556
  • 1
  • 9
  • 22
  • 1
    Updated the answer. Added another alternative solution. I think the second solution is what @Interpid meant in the first place. – Asaf Epelbaum Aug 10 '17 at 09:14
  • 1
    I think you've got the right idea here. This should be the accepted answer. – valentin Mar 07 '18 at 10:33
  • 1
    Note: you should NOT return a content body with a 204 No Content. I would remove the "protected NoContentResult NoContent(string reason)" method from the code. See https://stackoverflow.com/a/23374724/2463738 for more info. – valentin Mar 07 '18 at 12:24
  • @valentin Noted. I updated the question. Thank you for noticing – Asaf Epelbaum Oct 08 '18 at 09:59
8

You can now use the following (.Net Standard):

return StatusCode(HttpStatusCode.NoContent);

or (.Net Core 2.1+)

return NoContent();
Scott Wendt
  • 81
  • 1
  • 3
3

If you want to include a reason phrase with your response without adding a sub-class to ApiController, build a ResponseMessage object and return it from the action by the ResponseMessage() method. Try this:

public class InvoiceController : ApiController
{
    public async Task<IHttpActionResult> Post([FromBody]Invoice invoice)
    {
        if(User.IsInRole("Readonly"))
        {
            var response = new HttpResponseMessage(HttpStatusCode.Forbidden);

            response.ReasonPhrase = "User has the Readonly role";
            return ResponseMessage(response);
        }

        // Rest of code

    }
}
kmac.mcfarlane
  • 307
  • 2
  • 6
2

This worked well for me:

public class CodeAndReason : IHttpActionResult
{
    private readonly HttpStatusCode code;
    private readonly string reason;

    public CodeAndReason(HttpStatusCode code, string reason)
    {
        this.code = code;
        this.reason = reason;
    }

    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        var response = new HttpResponseMessage(code)
        {
            ReasonPhrase = reason,
            Content = new StringContent(reason),
        };

        return Task.FromResult(response);
    }

    public static IHttpActionResult NotFound(string reason)
    {
        return new CodeAndReason(HttpStatusCode.NotFound, reason);
    }

    public static IHttpActionResult Conflict(string reason)
    {
        return new CodeAndReason(HttpStatusCode.Conflict, reason);
    }

    public static IHttpActionResult Unauthorized(string reason)
    {
        return new CodeAndReason(HttpStatusCode.Unauthorized, reason);
    }
}

Used as:

return CodeAndReason.NotFound("Record {blah} not found");
joehoper
  • 404
  • 3
  • 5