Simple, proper exception handling?

Topics: Web Api
Oct 20, 2011 at 5:05 AM

Hi,

I have recently began looking into using WCF Web API.

I am trying to create a global exception handler, so that when an exception occurs, a Json (or Xml) response is returned containing the exception message.

This seems ridiculously difficult to do. Stackoverflow, Google, random blogs, all say to implement IErrorHandler and override ProvideFault(), but that does not work.

A breakpoint shows ProvideFault() is accessed when an exception is raised, but the standard "Request Error" WCF page is returned instead, even though I provided a FaultMessage to return via ProvideFault().

Here is the code I am using:

	public class ServiceExceptionHandlerAttribute : Attribute, IServiceBehavior
	{
		public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase,
		                                 Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
		{
		}

		public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
		{
			foreach (ChannelDispatcher chDisp in serviceHostBase.ChannelDispatchers) {
				chDisp.IncludeExceptionDetailInFaults = true;
				chDisp.ErrorHandlers.Clear();

				chDisp.ErrorHandlers.Add(new ServiceExceptionHandler());
			}
		}

		public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
		{
		}
	}

	public class ServiceExceptionHandler : IErrorHandler
	{
		public bool HandleError(Exception error)
		{
			return true;
		}

		public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
		{
			var faultException = new FaultException(error.Message);
			var messageFault = faultException.CreateMessageFault();
			fault = Message.CreateMessage(version, messageFault, faultException.Action);
		}
	}

And I decorate my service class with the [ServiceExceptionHandlerAttribute] attribute.

1) The code is being invoked OK in ProvideFault(), which I test via a breakpoint

2) Yet the standard WCF error page is returned, instead of the expected simple fault message.

Can anyone shed some light?

This is really frustrating.

Thank you

Oct 20, 2011 at 6:30 AM
Edited Oct 20, 2011 at 6:31 AM

Have you checked out the new HttpErrorHandler?  It makes writing a error handler much, much easier!  You only need to extend HttpErrorHandler and you can add your custom error handler through HttpConfiguration.

Here is a sample demonstrating how to use the HttpErrorHandler:
http://wcf.codeplex.com/SourceControl/changeset/view/5074812a6d06#WCFWebApi%2fHttp%2fTest%2fMicrosoft.ApplicationServer.Http%2fScenarios%2fHttpConfiguration%2fAddErrorHandler%2fAlwaysOkErrorHandler.cs

    public class AlwaysOkErrorHandler : HttpErrorHandler
    {
        protected override bool OnTryProvideResponse(Exception exception, ref HttpResponseMessage message)
        {
            message = new HttpResponseMessage(HttpStatusCode.OK)
            {
                Content = new StringContent(exception.Message)
            };

            return true;
        }
    }

http://wcf.codeplex.com/SourceControl/changeset/view/5074812a6d06#WCFWebApi%2fHttp%2fTest%2fMicrosoft.ApplicationServer.Http%2fScenarios%2fHttpConfiguration%2fAddErrorHandler%2fAddErrorHandler.cs 

                var config = new HttpConfiguration
                {
                    // Adding an error handler that always returns 200 with the error message in the body 
                    ErrorHandlers = (handlers, endpoint, descriptions) =>
                    {
                        handlers.Add(new AlwaysOkErrorHandler());
                    }
                };

Now, say my service operation throw an exception, AlwaysOkErrorHandler will set the status code to 200 and will return a HttpResponseMessage with a string content of the exception message.

Please let us know if you have any further question.

Thanks,
Maggie Ying

 

Oct 20, 2011 at 7:02 AM
Edited Oct 20, 2011 at 7:17 AM

Hi Maggie,

Thank you kindly for your reply.

It looks like references mentioned on the internet to IErrorHandler apply to WCF SOAP services, not the REST implementaton Web API provides.

In case anyone else wants this; here is what I came up with:

I created an error handler class that wraps the exception information so that it is serializable:

 

	public class RestServiceFault
	{
		public string Reason { get; set; }
		public string Exception { get; set; }
	}

 

I then created a custom HttpErrorHandler that will capture any unhandled exceptions and return a response in the same request format (eg JSON or XML):

	public class WcfHttpErrorHandler : HttpErrorHandler
	{
		protected override bool OnTryProvideResponse(Exception exception, ref HttpResponseMessage message)
		{
			// Describe our exception for the client
			var fault = new RestServiceFault();
			fault.Reason = exception.Message;
			fault.Exception = exception.ToString();

			// Respond with the same request format that was used to invoke the service
			var contentType = HttpContext.Current.Request.ContentType;
			if (string.IsNullOrWhiteSpace(contentType))
				contentType = "application/json";
			
			// Send a serialized response to the client
			message = new HttpResponseMessage(HttpStatusCode.InternalServerError)
			          	{
			          		Content = new ObjectContent<RestServiceFault>(fault, contentType)
			          	};

			return true;
		}
	}

Implementers may wish to set fault.Exception to NULL if their site is not in 'debug mode' for example, to prevent leaking stack trace information to clients that aren't developers.

Finally, I wire up the handler using:
			// Add our error handler
			webApiConfiguration.ErrorHandlers = (handlers, endpoint, descriptions) => handlers.Add(new WcfHttpErrorHandler());

The end result is that I now get customised exception information sent back as the response in the right format, and optionally I can turn off the stack trace info should I be in a production environment.

Regards,
Andrew
Nov 2, 2011 at 9:06 PM

How does this work for you?

            var contentType = HttpContext.Current.Request.ContentType;
            if (string.IsNullOrWhiteSpace(contentType))
                contentType = "application/json"; 

HttpContext.Current.Request is always null (or is it HttpContext.Current?) and this causes all kinds of issues for me. I would love to be able to set the content type but it does not seem to be possible. I keep trying to get this right.

 

Jeff

Nov 21, 2011 at 2:55 PM

I created the WcfHttpErrorHandler class and then inserted it in my Global.asax:

var config = new HttpConfiguration() { EnableTestClient = true };
            config.ErrorHandlers = (handlers, endpoint, descriptions) => handlers.Add(new WcfHttpErrorHandler());

However if there is an error after executing the web method then it doesn't go into this method at all. An example is if you are using a lazy loading ORM like NHibernate and it errors that session was closed when you return IQueryable objects from the web method, then it won't get captured here... You end up getting an empty response, not good!

Coordinator
Nov 21, 2011 at 9:08 PM

Could you please open an issue in Issue Tracker so that we can investigate? Please attach the issue a simplified project that reproduces the problem if possible.

Thanks.

Daniel Roth