Client side programming model and error handling

Topics: Web Api
Feb 11, 2012 at 11:58 AM

Hi there,

I've been using the Web API for some application services and it can be adapted quite nicely. However, the default client side coding model means the caller needs to deal with MediaTypeFormatters, reading an HttpResponseMessage, getting the ObjectContent and so on.

So I've wrapped this common plumbing so that the client code in our applications can be more business focused, as in the below code. That is, responses contain objects which are either the business object (Customer) or an error object (either a general ClientError or a custom business error).

    try
    {
       // Call a service via a wrapper method that allows the following syntax
       var customer = client.Get<Customer>("http://myserver/myservice/customers/4);

// Do something with the data such as return it to the browser as a JSON view model
}
    catch (WebFaultException<ClientError> ex)
    {
      // The ClientError object represents the standard exception response with limited details displayed in error reports
        var clientError = ex.Detail;

        // Do something based on the status code value if required
        var statusCode = ex.StatusCode;
}
   catch (WebFaultException<CustomerValidationError> ex)
   {
     // Get a custom business error - this wouldn't be applicable to a Get but might be for some Invoke operations
       var validationError = ex.Detail;
}
  catch (WebFaultException<string> ex)
    {
     // Errors that don't reach the service return an HTML string response 
    }

Server side I do this via a custom HttpErrorHandler that writes an HttpResponseMessage with an ObjectContent equal to the custom error object. This feels reasonable since there is no reason why an HTTP response shouldn't have an error object in its body. I guess I want a RESTful programming model based on something similar to fault contracts.

However, client side the Microsoft HttpClient (a wrapper on top of the low level HttpWebRequest class) knows nothing about error objects. Therefore, when the response is an error, I have to write some cludgy code to try to deserialize the received error. This works (with data contract XML) because we're sharing an interface assembly containing service / data contracts between the client and service process. 

if(!response.IsSuccessStatusCode)
{
// Get the response as plain text
    var stringResponse = response.Content.ReadAsStringAsync().Result;

    // Try to deserialize it into a ClientError using the StringContent and ObjectContent classes
    var error = ParseResposeAs<ClientError>(stringResponse);
    if (error != null)
{
     // Use reflection to see if the exception matches any FaultContracts declared against the service interface
        var customBusinessErrorTypes = ReadCustomBusinessErrorTypes(serviceInterfaceType);
        foreach (var customErrorType in customBusinessErrorTypes)
        {
         error = ParseResposeAs(customErrorType, stringResponse);
            break;
        }
    }

    if (error != null)
    {
     // Throw a WebFaultException<T> to client code
        Type genericType = typeof(WebFaultException<>);
        var faultContractType = genericType.MakeGenericType(error.GetType());
        throw Activator.CreateInstance(faultContractType, error, response.StatusCode);
    }
    else
    {
     // Otherwise throw an exception containing HTML string text to client code
        throw new WebFaultException<string>(stringResponse, response.StatusCode);
    }
}

I'm no expert on REST but I'm trying to use domain focused and organized URLs, and to use the most applicable HTTP status codes. I'm a big fan of the visibility that REST URLs provide and I really like the test client. To my mind though, REST should not mean we lose the object oriented good practice and excellent error handling that existed in WCF SOAP services. 

I'd be interested in any feedback about where you think my approach is wrong. Otherwise I'd like to request better support for this type of thing in the final release.

Thanks ...