Building consistent responses in ASP.NET Web Api

This is my second part of the article about building consistent Web Api response. First was just high level overview about idea for building consistent web api responses. This article is about building consistent responses in ASP.NET Web Api specifically.

Main idea to implement this in ASP.NET Web Api is to make own message handler. A message handler is a class that receives an HTTP request and return an HTTP response. Message handlers derive from an abstract HttpMessageHandler class. Typically, a series of message handlers are chained together. The first handler receives an HTTP request, does some processing, and gives the request to the next handler. At some point, the response is created and goes back up the chain. This pattern is called delegating handler.

To make custom message handler it is necessary to derive from System.Net.Http.DelegatingHandler and override SendAsync method.

 public class MyCustomResponseHandler : DelegatingHandler
 {
        protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
          var response=await base.SendAsync(request,cancellationToken);
        }
}

Currently, this custom message handler does nothing more than default message handler. But we will change that to make consistent web api response. For beggining, let’s see how that response model will look like:

public class ResponsePackage
{
public List<Error> Errors { get; set; }

public object Result { get; set; }

public HttpStatusCode StatusCode { get; set; }

public string StatusCodeMessage { get; set; }
public ResponsePackage(object result, List<Error> errors,HttpStatusCode statusCode)
{
Errors = errors;
Result = result;
StatusCode = statusCode;
StatusCodeMessage = statusCode.ToString();
}
}

Same response model will be returned in case of success and failure. The difference will be in status code, and in the fact that Result property will be null in case of failure, and Errors property will obviosly contain at least one error. In success scenario it will be the other way around: Result property will contain same object, and errors will be empty array. Notice how Result property is of generic type “object” meaning that can contain any structure. First thing that will have to handle is to make ASP.NET Web Api return errors inside Errors property of our ResponsePackage class. By default, JSON that was returned from Web Api when error happens looks something like this:

{
  "Message": "The request is invalid.",
  "ModelState": {
    "update.MobileNumber": [
      "The field MobileNumber must match the regular expression '^\\d{8}$|^00\\d{6,20}$|^\\+\\d{6,20}$'.",
      "The field MobileNumber must be a string with a minimum length of 8 and a maximum length of 22."
    ]
  }
}

To be complete, let’s write controller that returned this error:

[HttpPut]
public async Task<IHttpActionResult> UpdateAsync(UpdateModel update)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    // perform the update

    return StatusCode(HttpStatusCode.NoContent);
}

This is UpdateModel class:

public class Update {
    [StringLength(22, MinimumLength = 8)]
    [RegularExpression(@"^\d{8}$|^00\d{6,20}$|^\+\d{6,20}$")]
    public string MobileNumber { get; set; }
}

As you can see, by default ASP.NET Web Api reads data annotations on model class, and creates aproppriate  response. However, this is not what web want but we will try to get data from ModelState that ASP.NET Web Api created and put them in our own response.

Let’s create method BuildConsistentWebApiResponse  that will do all processing. Most code inside that method will be about making ModelState conform our shape of the response object.

public class MyCustomResponseHandler : DelegatingHandler { 
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
 var response=await base.SendAsync(request,cancellationToken);
 return BuildConsistentWebApiResponse(request,response);
 } 
}

First thing we have to do inside BuildConsistentWebApiResponse  method is to check if we have response body and if it is response code different than 200. If all conditions are met we can try to retrieve errors from ModelState  property of the HttpError object.

  List<Error> modelStateErrors = new List<Error>();
if (response.TryGetContentValue(out content) && !response.IsSuccessStatusCode) { 
      HttpError error = content as HttpError;
      if (error != null) { 
            content = null; 
            if (error.ModelState != null) 
            {      
              PopulateErrorsArrayFromModelStateProperty(response, modelStateErrors);
            } 
           else 
           { 
                 if (error.Values != null) 
                {
                PopulateErrorsArrayFromValuesProperty(modelStateErrors, error); 
                } 
           } 
      }
 }

PopulateErrorsArrayFromModelStateProperty  method look like this:

private static void PopulateErrorsArrayFromModelStateProperty(HttpResponseMessage response, List<Error> modelStateErrors)
{
    var httpErrorObject = response.Content.ReadAsStringAsync().Result;
    var anonymousErrorObject = new { message = "", ModelState = new Dictionary<string, string[]>() };
    var deseralizedErrorObject = JsonConvert.DeserializeAnonymousType(httpErrorObject, anonymousErrorObject);
    var modelStateValues = deseralizedErrorObject.ModelState.Select(kvp => string.Join(".", kvp.Value));

    for (int i = 0; i < modelStateValues.Count(); i++)
     {
      modelStateErrors.Add(new Error() { Message = modelStateValues.ElementAt(i) });
      }
}

Inside that method we are deserializing response content, reading errors inside ModelState property and putting them inside our modelStateErrors list.

Sometimes, there will not be ModelState  property.  In that case, PopulateErrorsArrayFromValuesProperty  method will be called.

private static void PopulateErrorsArrayFromValuesProperty(List<Error> modelStateErrors, HttpError error)
{
      foreach (var errorValue in error.Values)
      {
         var ifItIsDic = errorValue as Dictionary<string, object>;
         if (ifItIsDic != null)
         {
           if (ifItIsDic["Message"] != null)
           {
            modelStateErrors.Add(new Error() { Message = ifItIsDic["Message"].ToString() });
           }
        }
        else
        {
          modelStateErrors.Add(new Error() { Message = errorValue.ToString() });
        }
      }
}

Last thing we have to do inside BuildConsistentWebApi  response method is to create response object and add back response headers:

var newResponse = request.CreateResponse(response.StatusCode, new ResponsePackage(content, modelStateErrors, response.StatusCode));
foreach (var header in response.Headers) //Add back the response headers
{
newResponse.Headers.Add(header.Key, header.Value);
}

return newResponse;

To put this message handler in work you will have to register it inside register method of your web api.

public static class WebApiConfig
{
     public static void Register(HttpConfiguration config)
    {
     config.MessageHandlers.Add(new MyCustomResponseHandler());
    }

}

Result of this is that you will have consistent responses across your Web Api making implementation of it much more easier and enjoyable. All things from ASP.NET Web Api like data annotations still work and that is great thing about message handlers and ASP.NET Web Api. You can custom tailor it to your needs while other things you didn’t touch still continue to work.

 

Leave a Reply

Your email address will not be published. Required fields are marked *.

*
*
You may use these <abbr title="HyperText Markup Language">HTML</abbr> tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>