Jeroen Bernsen's Blog
A blog about using Microsoft .NET Development Technology

WCF ExceptionHandling in Silverlight

22 November 2009 15:31 by Administrator

I have recently done some LOB application development in Silveright in which we used a WCF service to communicate to the backend. There was an unexpected surprise that Exceptions throw in the WCF service were not transferred to the Silverlight Client! In Silverlight you always get a System.ServiceModel.CommunicationException: The remote server returned an error: NotFound.... error. We wanted to have the server Exception available at the  Silverlight client or at least the error message. (NOTE for security reasons you might not want to send all error information back to the client).

Silverlight version 3 enables support for the Windows Communication Foundation (WCF) SOAP fault, but I could not find any complete simple working examples on the Internet so here a working example.

First we have to change the default HTTP Code for a Fault message from 500 to 200 otherwise the Silverlight client cannot handle them. Use this attribute, also see http://msdn.microsoft.com/en-us/library/dd470096(VS.96).aspx.


    public class WcfSilverlightFaultBehavior : IDispatchMessageInspector
    {
        public void BeforeSendReply(ref Message reply, object correlationState)
        {
            if (reply.IsFault)
            {
                HttpResponseMessageProperty property = new HttpResponseMessageProperty();

                // Here the response code is changed to 200.
                property.StatusCode = System.Net.HttpStatusCode.OK;

                reply.Properties[HttpResponseMessageProperty.Name] = property;
            }
        }

        public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
        {
            // Do nothing to the incoming message.
            return null;
        }
    }

    public sealed class WcfSilverlightFaultBehaviorAttribute : WcfBehaviorAttributeBase
    {
        public WcfSilverlightFaultBehaviorAttribute()
            : base(typeof(WcfSilverlightFaultBehavior))
        {
        }
    }
[/code]

Next we build an attribute to process exceptions and tranform them to the way we want to send them to the client. You should change this to your needs (to much detail could be a security vulnerability) and also do some generic logging of the execption:


    public class WcfErrorBehavior : IErrorHandler
    {
 
        void IErrorHandler.ProvideFault(Exception error, MessageVersion version, ref Message fault)
        {
            try
            {
                // Add code here to build faultreason for client based on exception
                FaultReason faultReason = new FaultReason(error.Message);
                ExceptionDetail exceptionDetail = new ExceptionDetail(error);

                // For security reasons you can also decide to not give the ExceptionDetail back to the client or change the message, etc
                FaultException<ExceptionDetail> faultException = new FaultException<ExceptionDetail>(exceptionDetail, faultReason, FaultCode.CreateSenderFaultCode(new FaultCode("0")));

                MessageFault messageFault = faultException.CreateMessageFault();
                fault = Message.CreateMessage(version, messageFault, faultException.Action);
            }
            catch
            {
                // Todo log error
            }  
        }
    
        /// <summary>
        /// Handle all WCF Exceptions
        /// </summary>
        bool IErrorHandler.HandleError(Exception ex)
        {
            try
            {
                // Add logging of exception here!
                Debug.WriteLine(ex.ToString());
            }
            catch
            {
                // Todo log error
            }
           
            // return true means we handled the error.
            return true;
        }


    }

    public sealed class WcfErrorBehaviorAttribute : WcfBehaviorAttributeBase
    {
        public WcfErrorBehaviorAttribute()
            : base(typeof(WcfErrorBehavior))
        {
        }
    }


[/code]

Now we use the following class to make sure the attributes get applied automatically by defining them on the service:

    public abstract class WcfBehaviorAttributeBase : Attribute, IServiceBehavior
    {
        private Type _behaviorType;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="typeBehavior">IDispatchMessageInspector, IErrorHandler of IParameterInspector</param>
        public WcfBehaviorAttributeBase(Type typeBehavior)
        {
            _behaviorType = typeBehavior;
        }

        void IServiceBehavior.AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {
        }

        void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
        {
                object behavior;
                try
                {
                    behavior = Activator.CreateInstance(_behaviorType);
                }
                catch (MissingMethodException e)
                {
                    throw new ArgumentException("The Type specified in the BehaviorAttribute constructor must have a public empty constructor.", e);
                }
                catch (InvalidCastException e)
                {
                    throw new ArgumentException("The Type specified in the BehaviorAttribute constructor must implement IDispatchMessageInspector, IParamaterInspector of IErrorHandler", e);
                }

                foreach (ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers)
                {
                    if (behavior is IParameterInspector)
                    {
                        foreach (EndpointDispatcher epDisp in channelDispatcher.Endpoints)
                        {
                            foreach (DispatchOperation op in epDisp.DispatchRuntime.Operations)
                                op.ParameterInspectors.Add((IParameterInspector)behavior);
                        }
                    }
                    else if (behavior is IErrorHandler)
                    {
                        channelDispatcher.ErrorHandlers.Add((IErrorHandler)behavior);
                    }
                    else if (behavior is IDispatchMessageInspector)
                    {
                        foreach (EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints)
                        {
                            endpointDispatcher.DispatchRuntime.MessageInspectors.Add((IDispatchMessageInspector)behavior);
                        }
                    }
                }

        }

        void IServiceBehavior.Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
        {
        }
    }

 

The only thing we then have to do is add the attributes to the service so silverlight receives FaultExceptions now:

    [WcfErrorBehavior]
    [WcfSilverlightFaultBehavior]
    [ServiceContract(Namespace = "")]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class TestExceptionService
    {
        [OperationContract]
        public void DoWork()
        {
            // Force an exception
            throw new ApplicationException("Application Error in DoWork");
        }

        // Add more operations here and mark them with [OperationContract]
    }

I have included a fully working sample in VS2008.

 

 

WcfExceptionExample.zip (50.37 kb)

Comments