How to make your Web API responses consistent and useful

A custom wrapper for your Web API responses can provide meaningful information and ensure consistency regardless of success or failure

How to make your Web API responses consistent and useful
Credit: Thinkstock

When working with ASP.Net Web API, it is important to return a consistent response for all the requests that are processed by your API regardless of success or failure. This makes it a lot easier to consume the API, without requiring complex code on the client. By using a custom wrapper for Web API responses, you can ensure that all of your responses have a consistent structure, and you can also include custom metadata. This article presents a discussion on how we can decorate Web API responses with useful information.

Let's understand the problem we are trying to solve. In essence, your standard Web API response will vary when executing different action methods, and we need a way to get around the inconsistency of these responses. When you are trying to retrieve data using HTTP GET, the response may contain data for one or more records. When you are executing a HTTP POST, the response may not even include a body, but only a header. And when there is an error when executing the request, the API will return an object with the appropriate error message.

The differences among all of these responses make it difficult to consume the API, because the consumer needs to know the type and structure of the data that is being returned in each case. Both the client code and the service code become difficult to manage.

Now that we know what the problem is all about, let’s examine the two ways to overcome the problem in Web API. One of the options is to use a message handler. The other option is to intercept the call using a filter and override the OnActionExecuted method. We would prefer to use a message handler because it is called earlier in the request processing pipeline. (Incidentally, you can take advantage of message handlers to process a request even before it reaches the HttpControllerDispatcher.) So, let’s implement a custom message handler to wrap the response by extending the DelegatingHandler class. This will provide a way to send out responses from the Web API in a consistent manner.

Implementing a custom response handler in Web API

Create a new Web API project in Visual Studio and save it with the name of your choice. Now, select the Web API project you have created in the Solution Explorer Window and create a Solution Folder. Create a file named CustomResponseHandler.cs inside this folder. This file will contain our custom message handler. Next, copy the following code and past it inside this file. This is the skeleton of our custom message handler.

public class CustomResponseHandler :DelegatingHandler
    {
        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            throw new NotImplementedException("Not yet implemented.");
        }
    }

Here's a class named ResponseMetadata. I will contain all of the information we want to return from our Web API as part of the response.

public class ResponseMetadata
    {
        public string Version { get; set; }
        public HttpStatusCode StatusCode { get; set; }
        public string ErrorMessage { get; set; }
        public object Result { get; set; }
        public DateTime Timestamp { get; set; }
        public long? Size { get; set; }
    }

We will now write a method that checks to see whether or not a response is valid. The following method, IsResponseValid, returns true if the response is valid and false otherwise. We will use this method in our custom delegating handler.

private bool IsResponseValid(HttpResponseMessage response)
    {
        if ((response != null) && (response.StatusCode == HttpStatusCode.OK))
            return true;
        return false;
    }

You can retrieve the response content using the following code snippet and check if there is any error.

object responseContent;
if(response.TryGetContentValue(out responseContent))
{
    HttpError httpError = responseContent as HttpError;
    if(httpError !=null)
    {
        errorMessage = httpError.Message;
        statusCode = HttpStatusCode.InternalServerError;
        responseContent = null;
    }
}

The following code snippet shows how you can create an instance of ResponseMetadata and initialize its properties with relevant information.

ResponseMetadata responseMetadata = new ResponseMetadata();
responseMetadata.Version = "1.0";
responseMetadata.StatusCode = statusCode;
responseMetadata.Content = responseContent;
DateTime dt = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, DateTime.Now.Hour, DateTime.Now.Minute, DateTime.Now.Second, DateTime.Now.Millisecond);
responseMetadata.Timestamp = dt;
responseMetadata.ErrorMessage = errorMessage;
responseMetadata.Size = responseContent.ToString().Length;

The next thing we need to do is create a new response object using the CreateResponse method on the request object as shown below.

var result = request.CreateResponse(response.StatusCode, responseMetadata);

Note how the response metadata we just created has been passed in the second argument of the CreateResponse method.

Our complete custom response handler for Web API

Here is the complete code listing of the CustomResponseHandler class for your reference.

public class CustomResponseHandler : DelegatingHandler
    {
        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            var response = await base.SendAsync(request, cancellationToken);
            try
            {
                return GenerateResponse(request, response);
            }
            catch (Exception ex)
            {
                return request.CreateResponse(HttpStatusCode.InternalServerError, ex.Message);
            }
        }
        private HttpResponseMessage GenerateResponse(HttpRequestMessage request, HttpResponseMessage response)
        {          
            string errorMessage = null;
            HttpStatusCode statusCode = response.StatusCode;
            if (!IsResponseValid(response))
            {
                return request.CreateResponse(HttpStatusCode.BadRequest, "Invalid response..");
            }
            object responseContent;
            if(response.TryGetContentValue(out responseContent))
            {
                HttpError httpError = responseContent as HttpError;
                if(httpError !=null)
                {
                    errorMessage = httpError.Message;
                    statusCode = HttpStatusCode.InternalServerError;
                    responseContent = null;
                }
            }
            ResponseMetadata responseMetadata = new ResponseMetadata();
            responseMetadata.Version = "1.0";
            responseMetadata.StatusCode = statusCode;
            responseMetadata.Content = responseContent;
            DateTime dt = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, DateTime.Now.Hour, DateTime.Now.Minute, DateTime.Now.Second, DateTime.Now.Millisecond);
            responseMetadata.Timestamp = dt;
            responseMetadata.ErrorMessage = errorMessage;
            responseMetadata.Size = responseContent.ToString().Length;
            var result = request.CreateResponse(response.StatusCode, responseMetadata);                       
            return result;
        }
        private bool IsResponseValid(HttpResponseMessage response)
        {
            if ((response != null) && (response.StatusCode == HttpStatusCode.OK))
                return true;
            return false;          
        }
    }

Finally, you should add the following statement in the Register method of the WebApiConfig class to register the custom handler.

config.MessageHandlers.Add(new CustomResponseHandler());

And that’s it! You can now execute your Web API methods and see how the responses are wrapped using the custom message handler we implemented.

In this article we implemented a custom message handler that was used to wrap Web API responses to include additional metadata. Note that this was just a simple implementation. I will leave it to you to add more features such as response headers, cookie information, etc. Happy coding!