Exception Shielding for JSON WCF Services

October, 2009

The DAL in mfLY! provides a very cool remote provider which allows you to execute saves and selects across the wire to a server repository. Actually, you aren't even limited to querying/saving to a server database as the server side code also supports the same provider model effectively allowing you create either a store and forward integration layer or a real time pass through to some other system or anything else you might dream up and plug into the  provider model.

The remote provider talks to the server over WCF JSON web services which gives us some very nice performance benefits on the devices. Once of the issues we've always had was the inability to use any of the Ent Lib Exception Shielding and instead ended up having our own try catch around all service methods in order to log errors and return proper information to the client. So to fix this, I've extended WCF to allow exception shielding behavior similar to Ent Libs and added the ability to return faults to the client serialized as json (instead of soap xml). This is very much a first pass, but I thought it would be interesting to share.

First of all, we need to create our own IErrorHandler:

public class JsonExceptionShieldingErrorHandler : IErrorHandler
{
  private readonly string exceptionPolicyName;

  public JsonExceptionShieldingErrorHandler()
  : this("WCF Exception Shielding")
  {
  }

  public JsonExceptionShieldingErrorHandler(string exceptionPolicyName)
  {
    this.exceptionPolicyName = exceptionPolicyName;
  }

  public string ExceptionPolicyName
  {
    get { return exceptionPolicyName; }
  }

  public bool HandleError(Exception error)
  {
    return true;
  }

  public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
  {
    try
    {
      ExceptionPolicy.HandleException(error, exceptionPolicyName);
      if (!exceptionPolicyName.Equals("WCF Exception Shielding", StringComparison.InvariantCultureIgnoreCase))
      {
        ExceptionPolicy.HandleException(error, "WCF Exception Shielding");
      }
      ProcessUnhandledException(error, ref fault, version);
    }

    catch (FaultContractWrapperException exception)
    {
      HandleFault(exception, ref fault, version);
    }

    catch (FaultException)
    {
    }

    catch (Exception exception2)
    {
      ProcessUnhandledException(error, exception2, ref fault, version);
    }
  }

  private static void HandleFault(
    FaultContractWrapperException faultContractWrapper,
    ref Message fault,
    MessageVersion version)
  {
    try
    {
      fault = Message.CreateMessage(version, "", faultContractWrapper.FaultContract, new DataContractJsonSerializer(faultContractWrapper.FaultContract.GetType()));

      var wbf = new WebBodyFormatMessageProperty(WebContentFormat.Json);
      fault.Properties.Add(WebBodyFormatMessageProperty.Name, wbf);
      var rmp = new HttpResponseMessageProperty
      {
      StatusCode = HttpStatusCode.BadRequest,
      StatusDescription = faultContractWrapper.Message
      };
      fault.Properties.Add(HttpResponseMessageProperty.Name, rmp);
    }
    catch (Exception unhandledException)
    {
      // There was an error during MessageFault build process, so treat it as an Unhandled Exception
      // log the exception and send an unhandled server exception
      Guid handlingInstanceId = ExceptionUtility.LogServerException(unhandledException);
      HandleFault(unhandledException, ref fault, handlingInstanceId, version);
    }
  }

  private static void HandleFault(
    Exception error,
    ref Message fault,
    Guid handlingInstanceId,
    MessageVersion version)
  {
    var reason = ExceptionUtility.FormatExceptionMessage(Resources.ClientUnhandledExceptionMessage, ExceptionUtility.GetHandlingInstanceId(error, handlingInstanceId));

    fault = Message.CreateMessage(version, "", reason, new DataContractJsonSerializer(typeof(string)));
    var wbf = new WebBodyFormatMessageProperty(WebContentFormat.Json);
    fault.Properties.Add(WebBodyFormatMessageProperty.Name, wbf);
    var rmp = new HttpResponseMessageProperty
    {
      StatusCode = HttpStatusCode.InternalServerError,
      StatusDescription = reason
    };
    fault.Properties.Add(HttpResponseMessageProperty.Name, rmp);
  }



  private static bool IsFaultException(Exception exception)
  {
    return typeof(FaultException).IsInstanceOfType(exception);
  }

  private static void ProcessUnhandledException(Exception originalException, ref Message fault, MessageVersion version)
  {
    ProcessUnhandledException(originalException, originalException, ref fault, version);
  }

  private static void ProcessUnhandledException(Exception originalException, Exception unhandledException, ref Message fault, MessageVersion version)
  {
    if (!IsFaultException(unhandledException))
    {
      Guid handlingInstanceId =
      ExceptionUtility.GetHandlingInstanceId(
      unhandledException, Guid.Empty);
      if (handlingInstanceId.Equals(Guid.Empty))
      {
        handlingInstanceId = ExceptionUtility.LogServerException(unhandledException);
      }

      HandleFault(unhandledException, ref fault, handlingInstanceId, version);
    }
  }
}

Notice that I'm hard coding this error handler to use "WCF Exception Shielding" as the name of the exception handling block (when you get into your config file).

Next we create a custom WebHttpBehavior which is  pretty quick and easy:

public class JsonExceptionShieldingBehavior : WebHttpBehavior
{
  protected override void AddServerErrorHandlers(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
  {
    endpointDispatcher.ChannelDispatcher.ErrorHandlers.Clear();
    endpointDispatcher.ChannelDispatcher.ErrorHandlers.Add(new JsonExceptionShieldingErrorHandler());
  }
}

Finally we have to add a factory to our service to load this custom behavior. I started trying to do this using attributes, but didn't seem to be able to get into the WCF stack in the right place to actually return JSON. Create your factory:

public class CustomHostFactory : WebServiceHostFactory
{
  public override ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses)
  {
    var sh = new ServiceHost(typeof (EntityJsonService), baseAddresses);
    sh.Description.Endpoints[0].Behaviors.Add(new JsonExceptionShieldingBehavior());
    return sh;
  }

  protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
  {
    return base.CreateServiceHost(serviceType, baseAddresses);
  }
}

In your .svc add a reference to that factory that you  just created:

<%@ ServiceHost Language="C#" Debug="true" Service="BlueDot.Eam.Inventory.Integration.Services.EntityJsonService" Factory="BlueDot.Eam.Inventory.Services.CustomHostFactory" %>

And that's it!