How to return a custom HTTP status code using WebAPI 2

How to return a custom HTTP status code using WebAPI 2

When developing a RESTful API we sometimes need to return an HTTP status code that is not included in Microsoft's HttpStatusCode enumeration. For example this is the case for HTTP 423 (Locked) and others. This post will give you an example on how to return such a custom HTTP code.

Have a look here for the natively supported HTTP Status Codes.

Let's assume we have the following simple controller that offers the possibility to execute an order based on an order id.

[RoutePrefix("api/v1/order-mgmt")]
public class OrderController : ApiController
{
    // ...

    [HttpGet, Route("execute")]
    public IHttpActionResult Execute(string orderid)
    {
        if (string.IsNullOrEmpty(orderid))
        {
            // HTTP 400
            return BadRequest("No value for parameter orderid provided");
        }
       
        if (someService.IsLocked())
        {
            // HTTP 423
            return ???
        }

        // ...

        // HTTP 200
        return Ok("Everything fine!");
    }
}

As we are using attribute-routing here the action Execute(string orderid) can be reached via the URL http://localhost:port/api/v1/order-mgmt/execute?orderid=<orderid-string>.

In general it's advisable to return an IHttpActionResult over an HttpResponseMessage. It provides several benefits like:

  • Simplified unit testing of your controllers
  • Moves common logic for creating HTTP responses into seperate classes
  • Makes the intent of the controller action clearer, by hiding the low-level detauls of constructing the repsonse

More information can be found here.

So how are we now able to return an HTTP 423 in case someService.IsLocked() returns true? There are basically two scenarios:

  1. It's sufficient to return HTTP 423 without a message
  2. You'd like to return HTTP 423 and include a message

For the first scenario all we need to return is return new StatusCode((HttpStatusCode) 423); and we are finished! For the second scenario however we need our own implementation of IHttpActionResult. Here is how I solved it.

public class Locked : IHttpActionResult
{
    private readonly string message;
    private readonly HttpRequestMessage request;
 
    public Locked(string message, HttpRequestMessage request)
    {
        this.message = message;
        this.request = request;
    }
 
    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        var response = new HttpResponseMessage
        {
            StatusCode = (HttpStatusCode) 423,
            RequestMessage = this.request,
            Content = new StringContent(this.message)
        };
 
        return Task.FromResult(response);
    }
}

This implementation allows us to return HTTP 423 with a message. It can be used as follows:

[RoutePrefix("api/v1/order-mgmt")]
public class OrderController : ApiController
{
    // ...
    [HttpGet, Route("execute")]
    public IHttpActionResult Execute(string orderid)
    {
        if (string.IsNullOrEmpty(orderid))
        {
            // HTTP 400
            return BadRequest("No value for parameter orderid provided");
        }
       
        if (someService.IsLocked())
        {
            // HTTP 423
            return new Locked("The resource is currently locked!"); 
        }
       
        // ...
       
        // HTTP 200
        return Ok("Everything fine!");
    }
}

That's it for today. Happy hacking!