In Part I, I discussed how to handle errors from your WCF services so that you can give your API users a consistent error result.

In Part II, I'll be discussing how to handle a few of those errors generated by WCF that don't get sent to your IErrorHandler implementation.

WCF Generated Errors
Handling the application errors will get you most of the way there. However, there are two cases where WCF will display that custom blue and white HTTP web page similar to what was shown above; 405 Method Not Allowed, and 404 Endpoint Not Found. These cases arise when WCF can't match the request with a service or a method. Unfortunately these errors don't go through the IErrorHandler interface, so you have to do something else to replace the output with your error data contract. I was pretty stumped on this one and had to resort to using Reflector, breakpoints, and stack trace examination to figure out what to do. There's a property on the DispatchRuntime called UnhandledDispatchOperation that WCF uses to dispatch operations that don't match an operation in your service. It's the IOperationInvoker instance stored in this property that is responsible for the blue and white HTML versions of the 405 and 404 responses.

The solution is to replace this instance with your own implementation that doesn't return HTML. The tricky part in writing the implementation is trying to determine whether to return a 405 or a 404. I'm guessing that it's possible to use the WCF API to figure out at runtime which case applies to the request. Rather than figure this all out, I cheated and just copied what the original IOperationInvoker instance did.
public class UnhandledOperationInvoker : IOperationInvoker
    {
        public object[] AllocateInputs()
        {
            return new object[1];
        }

        public object Invoke(object instance, object[] inputs, out object[] outputs)
        {
            outputs = null;
            bool uriMatch = false;
            Message message = inputs[0] as Message;

            if (message.Properties.ContainsKey("UriMatched"))
            {
                uriMatch = (bool)message.Properties["UriMatched"];
            }

            if (!uriMatch)
            {
                return new ItemNotFoundMessage();
            }
            else
            {
                return new MethodNotAllowedMessage();
            }
        }

        public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
        {
            throw new NotImplementedException();
        }

        public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
        {
            throw new NotImplementedException();
        }

        public bool IsSynchronous
        {
            get { return true; }
        }
    }


Apparently something in the communication stack figures this out for us and saves this information in the current message's properties. Once you know which case applies to you, just return your standard error response appropriately. In my example, I'm returning an instance of one of my custom classes that inherits from System.ServiceModel.Channels.Message. This way I can control exactly the way the response looks. WARNING! Use the above approach at your own risk. Obviously it will stop working if a new version of .NET starts doing it differently.

In Part III, I'll discuss how to handle errors generated from IIS that don't get handled by WCF at all.

No comments:

Post a Comment