Custom web faults with System.ServiceModel.Web 3.x

by Matt Milner 14. June 2010 10:04

A former student approached me with a problem related to the Web programming model using WCF in .NET 3.5.  In short, he was using a custom IErrorHandler to create a custom fault message, but the client was always receiving a generic error.  Even more of a problem was that the custom error was an HTML formatted message, despite having set the response format on the service to JSON.  This caused big problems for the AJAX client trying to reason over that response.  I knew that WCF REST Starter Kit and WCF 4 both allowed for custom error messages, so I did some digging to see what might be at the root of the problem.  It turns out that the WebHttpBehavior inserts its own IErrorHandler and it was getting in the way of the custom handler he was adding.  After pointing this out to Dave, he quickly realized he could create a class that derived from the WebHttpBehavior and override the AddServerErrorHandlers to insert his own error handler.  He also created the requisite BehaviorExtensionElement so the new endpoint behavior could be added in the configuration file. 

 

public class JsonWebHttpBehavior : WebHttpBehavior
    {
        protected override void AddServerErrorHandlers(ServiceEndpoint endpoint,
        System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
        {
            endpointDispatcher.DispatchRuntime.ChannelDispatcher.ErrorHandlers.Add(
               new JsonErrorHandler(endpointDispatcher.DispatchRuntime.ChannelDispatcher.IncludeExceptionDetailInFaults));
        }
    }

    public class JsonWebHttpElement : BehaviorExtensionElement
    {
        protected override object CreateBehavior()
        {
            return new JsonWebHttpBehavior();
        }

        public override Type BehaviorType
        {
            get { return typeof(JsonWebHttpBehavior); }
        }
    }

 

The job of the custom error handler is to create a custom fault class that provides data back to the calling application.  This solution nicely takes into consideration the IncludeExceptionDetailsInFaults property to correctly send the details only when configured to do so.  In this case, the status code is always set to 500 to trigger the correct error handling in the client library, but you could also modify this to send more specific HTTP status codes depending on the error message caught on the server. 

 

[DataContract]
    public class JsonFault
    {
        [DataMember]
        public string ExceptionType;

        [DataMember]
        public string Message;

        [DataMember]
        public string StackTrace;
    }

    public class JsonErrorHandler : IErrorHandler
    {
        public JsonErrorHandler(bool includeExceptionDetailInFaults)
        {
            this.includeExceptionDetailInFaults = includeExceptionDetailInFaults;
        }

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

        public void ProvideFault(Exception error,
            System.ServiceModel.Channels.MessageVersion version,
            ref System.ServiceModel.Channels.Message fault)
        {
            JsonFault jsonFault;
            if (includeExceptionDetailInFaults)
            {
                jsonFault = new JsonFault
                {
                    ExceptionType = error.GetType().FullName,
                    Message = error.Message,
                    StackTrace = error.StackTrace
                };
            }
            else
            {
                jsonFault = new JsonFault
                {
                    ExceptionType = typeof(System.Exception).FullName,
                    Message =
                        "An error occurred on the server. See server logs for details.",
                    StackTrace = null
                };
            }

            DataContractJsonSerializer serializer =
                new DataContractJsonSerializer(typeof(JsonFault));

            fault = Message.CreateMessage(version, null, jsonFault, serializer);
            fault.Properties.Add(WebBodyFormatMessageProperty.Name,
                new WebBodyFormatMessageProperty(WebContentFormat.Json));
            WebOperationContext.Current.OutgoingResponse.ContentType = "application/json";
            WebOperationContext.Current.OutgoingResponse.StatusCode =
                System.Net.HttpStatusCode.InternalServerError;
        }

        private bool includeExceptionDetailInFaults;
    }

 

 

You could certainly make modifications to only provide faults for certain types of exceptions (which is what .NET 4 does) log information in the HandleError method, etc.  Many thanks to Dave Grundgeiger for the inspiration to look into this and the final solution which he designed and allowed me to share here. 

Tags:

Windows Communication Foundation