3

I have a WCF 4 REST service configured to use json. I want to catch exceptions and return a HTTP Status code of 400 with the exception message as a json object. I have followed examples on the web to implement my own IErrorHandler and IService interface to do this.

For example:

http://zamd.net/2008/07/08/error-handling-with-webhttpbinding-for-ajaxjson/

Returning Error Details from AJAX-Enabled WCF Service

http://social.msdn.microsoft.com/Forums/en/wcf/thread/fb906fa1-8ce9-412e-a16a-5d4a2a0c2ac5

However, just as in this post

jQuery success callback called with empty response when WCF method throws an Exception

I get a 202 Accepted response with no data which is due to a serialization error when I try to create my fault. This is logged from my service as follows:

2012-01-31 00:37:19,229 [8] DEBUG JsonWebScriptServiceHostFactory: creating service host
2012-01-31 00:37:19,292 [8] DEBUG JsonErrorHandler.ApplyDispatchBehavior: adding error handler
2012-01-31 00:43:06,995 [10] DEBUG ForemanSvc.GetSessionID
2012-01-31 00:43:39,292 [10] DEBUG ForemanSvc.GetProjects
2012-01-31 00:43:39,448 [10] DEBUG JsonErrorHandler.ProvideFault: creating fault
2012-01-31 00:43:39,635 [10] ERROR ForemanSvc exeption
Type: System.ServiceModel.CommunicationException
Message: Server returned an invalid SOAP Fault.  Please see InnerException for more details.
Source: System.ServiceModel
StackTrace:    
at System.ServiceModel.Channels.MessageFault.CreateFault(Message message, Int32 maxBufferSize)
at System.ServiceModel.Description.WebScriptEnablingBehavior.JsonErrorHandler.ProvideFault(Exception error, MessageVersion version, Message& fault)
at System.ServiceModel.Dispatcher.ErrorBehavior.ProvideFault(Exception e, FaultConverter faultConverter, ErrorHandlerFaultInfo& faultInfo)
at System.ServiceModel.Dispatcher.ErrorBehavior.ProvideMessageFaultCore(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage8(MessageRpc& rpc)
Type: System.Xml.XmlException
Message: Start element 'Fault' from namespace 'http://schemas.microsoft.com/ws/2005/05/envelope/none' expected. Found element 'root' from namespace ''.
Source: System.Runtime.Serialization
StackTrace:    
at System.Xml.XmlExceptionHelper.ThrowXmlException(XmlDictionaryReader reader, String res, String arg1, String arg2, String arg3)
at System.Xml.XmlExceptionHelper.ThrowStartElementExpected(XmlDictionaryReader reader, String localName, String ns)
at System.Xml.XmlDictionaryReader.ReadStartElement(XmlDictionaryString localName, XmlDictionaryString namespaceUri)
at System.ServiceModel.Channels.ReceivedFault.CreateFault12Driver(XmlDictionaryReader reader, Int32 maxBufferSize, EnvelopeVersion version)
at System.ServiceModel.Channels.MessageFault.CreateFault(Message message, Int32 maxBufferSize)

It's not clear from that post how to fix it. I have tried all sorts - using an attribute, using an endpoint behavior, trying a simple CreateMessage with no json formatting or extra info returned - nothing seems to work. Can anyone help?

Here's some code snippets - the error handler

public class JsonErrorHandler : IServiceBehavior, IErrorHandler
{
  private static readonly ILog log =
    LogManager.GetLogger(System.Reflection.MethodInfo.GetCurrentMethod().DeclaringType);


  public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
  {
    //Dont do anything
  }

  public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase,
                                 Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
  {
    //dont do anything
  }

  public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
  {
    log.IfDebug("JsonErrorHandler.ApplyDispatchBehavior: adding error handler");
    foreach (ChannelDispatcherBase dispatcherBase in serviceHostBase.ChannelDispatchers)
    {
      ChannelDispatcher channelDispatcher = dispatcherBase as ChannelDispatcher; 
      if (channelDispatcher != null)
      {
        channelDispatcher.ErrorHandlers.Add(this);
      } 
    }
  }


  public bool HandleError(Exception error)
  {
    log.IfError("ForemanSvc exeption", error);
    //Tell the system that we handle all errors here.
    return true;
  }

  public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
  {
    log.IfDebug("JsonErrorHandler.ProvideFault: creating fault");

    JsonError msErrObject =
    new JsonError
    {
      Message = error.Message,
      Source = error.Source,
      Detail = error.InnerException != null ? error.InnerException.Message : null
    };


    //The fault to be returned
    fault = Message.CreateMessage(version, "", msErrObject, new DataContractJsonSerializer(msErrObject.GetType()));

    // tell WCF to use JSON encoding rather than default XML
    WebBodyFormatMessageProperty wbf = new WebBodyFormatMessageProperty(WebContentFormat.Json);

    // Add the formatter to the fault
    fault.Properties.Add(WebBodyFormatMessageProperty.Name, wbf);

    //Modify response
    HttpResponseMessageProperty rmp = new HttpResponseMessageProperty();

    if (error is SecurityException &&
      (error.Message == "Session expired" || error.Message == "Authentication ticket expired"))
    {
      rmp.StatusCode = HttpStatusCode.Unauthorized;
      rmp.StatusDescription = "Unauthorized";
    }
    else
    {
      // return custom error code, 400.
      rmp.StatusCode = HttpStatusCode.BadRequest;
      rmp.StatusDescription = "Bad request";
    }

    //Mark the jsonerror and json content
    rmp.Headers[HttpResponseHeader.ContentType] = "application/json";
    rmp.Headers["jsonerror"] = "true";

    //Add to fault
    fault.Properties.Add(HttpResponseMessageProperty.Name, rmp);

  } 
}

and where I add the custom error handler for the service

public class JsonWebScriptServiceHostFactory : WebScriptServiceHostFactory
{
  private static readonly ILog log =
   LogManager.GetLogger(System.Reflection.MethodInfo.GetCurrentMethod().DeclaringType);

  protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
  {
    log.IfDebug("JsonWebScriptServiceHostFactory: creating service host");
    ServiceHost host = base.CreateServiceHost(serviceType, baseAddresses);
    host.Description.Behaviors.Add(new JsonErrorHandler());
    return host;
  } 
}

and the custom error

[DataContract(Namespace = "VSS.Nighthawk.Foreman", Name = "JsonError")]
public class JsonError
{
  [DataMember]
  public string Message { get; set; }

  [DataMember]
  public string Source { get; set; }

  [DataMember]
  public string Detail { get; set; }
}
Community
  • 1
  • 1

1 Answers1

0

What binding and encoder are you using, and what settings do you have configured on them? Also, what behavior have you plugged in? If you've plugged in WebScriptEnablingBehavior (because WebScriptServiceHostFactory plugs it in automatically), your problem may be that WSEB plugs in an error handler of its own, which does a LOT of the same things you're trying to do.

What I'd also do is use Reflector and look at the error handler that is embedded in WebScriptEnablingBehavior, and see what you're doing differently, and whether you could be doing anything else that you're not already doing. It's a very, very tricky and hairy area plagued with many subtleties, so you probably did not get the error handler right the first time.

You may also have to stop using WebScriptEnablingBehavior at all (if you're using it) -- so just make sure you aren't. You may have to re-implement WebScriptEnablingBehavior no your own, from scratch, and plug it in from scratch in your service host factory, instead of plugging in just a JSON error handler.

Hope this helps!

krisragh MSFT
  • 1,908
  • 1
  • 13
  • 22