Custom Reason Phrase in ASP.NET Web API Controller Action

The source code for this post can be found at https://github.com/DmitryZinchenko/blog-WrappedHttpActionResult .

So, you are in the middle of your iteration or heavy refactoring. The API documentation is not ready yet but you deployed your ASP.NET Web API application (Release build) to remote server for dev testing. Your UX teammates started using it periodically checking browser’s Developer Tools Network tab.

As long as they see 200 OK or 404 Not Found – everything is clear and fine. But once in a while you get 400 Bad Request and 500 Internal Server Error and you wish you could get a hint what have caused it.

HTTP 400 BadRequest

HTTP 500 Internal Server Error

I have recently found a nice solution by Elton Stoneman that I want to share with you. I use it for adding a custom Reason Phrase to HTTP 4xx and 5xx Status Codes and then retrieving them in $http Angular service.

public IHttpActionResult CalcCValue(int value)
{
    try
    {
        return Ok(_calcEngine.Calc(value));
    }
    catch (Exception ex)
    {
        return InternalServerError(ex).With("Internal Server Error: Check your calculation!");
    }
}

Usually unit testing action methods for HTTP GET is pretty straightforward, but in this case you have to do API Controller configuration, similar to what we do for action methods for HTTP POST.

public static CalcController ConfigureForGet(this CalcController controller, int value)
{
    var config = new HttpConfiguration();
    var request = new HttpRequestMessage(HttpMethod.Get, string.Format("http://localhost/api/calc/c/{0}", value));
    IHttpRoute route = config.Routes.MapHttpRoute(name: "CalcApi", routeTemplate: "api/calc/c/{value}");
    var routeData = new HttpRouteData(route, new HttpRouteValueDictionary { { "value", value } });
    controller.ControllerContext = new HttpControllerContext(config, routeData, request);
    controller.Request = request;
    controller.Request.Properties[HttpPropertyKeys.HttpConfigurationKey] = config;

    return controller;
}

And this is the way unit tests for HTTP 200 and HTTP 500 might look like:

[Fact]
public void CalcCValue_ReturnsOkNegotiatedContentResult()
{
	// arrange
	var mock = new Mock<ICalcEngine>();
	mock.Setup(x => x.Calc(It.IsInRange<int>(1, int.MaxValue, Range.Inclusive))).Returns<int>(v => 10 % v);

	var controller = new CalcController(mock.Object);

	// act
	IHttpActionResult actionResult = controller.CalcCValue(10);

	//  assert
	Assert.IsType<OkNegotiatedContentResult<int>>(actionResult);
}
[Fact]
public async void CalcCValue_ReturnsInternalServerErrorWithReasonPhrase()
{
	// arrange
	var mock = new Mock<ICalcEngine>();
	mock.Setup(x => x.Calc(It.IsAny<int>())).Throws<DivideByZeroException>();

	var controller = new CalcController(mock.Object).ConfigureForGet(int.MinValue);

	// act
	IHttpActionResult actionResult = controller.CalcCValue(int.MinValue);

	// assert
	Assert.IsType<WrappedHttpActionResult>(actionResult);

	HttpResponseMessage response = await actionResult.ExecuteAsync(CancellationToken.None);
	Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
	Assert.Equal("Internal Server Error: Check your calculation!", response.ReasonPhrase);
}

Now, this is what UX developer would see in the Network tab:

HTTP 500 Internal Server Error

Much better!

Custom Reason Phrase in ASP.NET Web API Controller Action