none
WCF Global Exception Handling RRS feed

  • Question

  •  

    In Web Applications, you can tap into Application_Error in Global.asax

     

    In ASMX Web Services, you can write a Soap Extension where you can get at any exception before it gets sent to the client

     

    How can I have global exception handling in WCF services hosted in IIS?

     

    At the moment, I've got Try Catches on every one of my methods (in the Catch I then simply call a generic exception handler).

     

    Is there a better way? Basically I don't want to have to put Try Catches in each of my methods.

    Sunday, February 24, 2008 10:17 PM

Answers

  • You don't necessarily need to put the fault contract in the operations; that's mostly a way to tell clients that an error of that type may happen in the server. Below is a modified version of the code I posted before which doesn't use it. The clients will receive the errors wrapped in a FaultException, so you need to use the Reason property if you want to pass / retrieve detailed information:

     

    public class Post2898359b

    {

        [ServiceContract]

        public interface ITest

        {

            [OperationContract]

            //[FaultContract(typeof(string))]

            int GetLength(string input);

            [OperationContract]

            //[FaultContract(typeof(string))]

            int Divide(int x, int y);

        }

        public class Service : ITest

        {

            public int GetLength(string input) { return input.Length; }

            public int Divide(int x, int y) { return x / y; }

        }

        public class MyErrorHandler : IErrorHandler

        {

            public bool HandleError(Exception error)

            {

                Console.WriteLine("This error occurred somewhere: {0}", error.Message);

                return true;

            }

            public void ProvideFault(Exception error, MessageVersion version, ref Message fault)

            {

                fault = Message.CreateMessage(

                    version,

                    new FaultException<string>(error.Message, new FaultReason(error.Message)).CreateMessageFault(),

                    "http://the.fault.action");

            }

        }

        public class MyServiceBehavior : IServiceBehavior

        {

            public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)

            {

            }

            public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)

            {

                foreach (ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers)

                {

                    channelDispatcher.ErrorHandlers.Add(new MyErrorHandler());

                }

            }

            public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)

            {

            }

        }

        public static void Test()

        {

            string baseAddress = "http://" + Environment.MachineName + ":8000/Service";

            ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));

            host.AddServiceEndpoint(typeof(ITest), new BasicHttpBinding(), "");

            host.Description.Behaviors.Add(new MyServiceBehavior());

            host.Open();

            Console.WriteLine("Host opened");

     

            ChannelFactory<ITest> factory = new ChannelFactory<ITest>(new BasicHttpBinding(), new EndpointAddress(baseAddress));

            ITest proxy = factory.CreateChannel();

            Console.WriteLine(proxy.GetLength("Hello"));

            try

            {

                Console.WriteLine(proxy.GetLength(null));

            }

            catch (FaultException e)

            {

                Console.WriteLine("FaultException: {0}", e.Message);

            }

            catch (Exception e)

            {

                Console.WriteLine("Exception on GetLength(null): {0}", e);

            }

            Console.WriteLine(proxy.Divide(4, 3));

            try

            {

                Console.WriteLine(proxy.Divide(4, 0));

            }

            catch (FaultException e)

            {

                Console.WriteLine("FaultException: {0}", e.Message);

            }

            catch (Exception e)

            {

                Console.WriteLine("Exception on Divide(4, 0): {0}", e);

            }

     

            ((IClientChannel)proxy).Close();

            factory.Close();

            host.Close();

        }

    }

    Thursday, February 28, 2008 5:28 PM

All replies

  • The interface IErrorHandler (http://msdn2.microsoft.com/en-us/library/system.servicemodel.dispatcher.ierrorhandler.aspx) should give you this functionality. In the example below, all exceptions thrown by the service are converted to a FaultException<string>.

     

    public class Post2898359

    {

        [ServiceContract]

        public interface ITest

        {

            [OperationContract]

            [FaultContract(typeof(string))]

            int GetLength(string input);

            [OperationContract]

            [FaultContract(typeof(string))]

            int Divide(int x, int y);

        }

        public class Service : ITest

        {

            public int GetLength(string input) { return input.Length; }

            public int Divide(int x, int y) { return x / y; }

        }

        public class MyErrorHandler : IErrorHandler

        {

            public bool HandleError(Exception error)

            {

                Console.WriteLine("This error occurred somewhere: {0}", error.Message);

                return true;

            }

            public void ProvideFault(Exception error, MessageVersion version, ref Message fault)

            {

                fault = Message.CreateMessage(

                    version,

                    new FaultException<string>(error.Message).CreateMessageFault(),

                    "http://the.fault.action");

            }

        }

        public class MyServiceBehavior : IServiceBehavior

        {

            public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)

            {

            }

            public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)

            {

                foreach (ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers)

                {

                    channelDispatcher.ErrorHandlers.Add(new MyErrorHandler());

                }

            }

            public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)

            {

            }

        }

        public static void Test()

        {

            string baseAddress = "http://localhost:8000/Service";

            ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));

            host.AddServiceEndpoint(typeof(ITest), new BasicHttpBinding(), "");

            host.Description.Behaviors.Add(new MyServiceBehavior());

            host.Open();

            Console.WriteLine("Host opened");

     

            ChannelFactory<ITest> factory = new ChannelFactory<ITest>(new BasicHttpBinding(), new EndpointAddress(baseAddress));

            ITest proxy = factory.CreateChannel();

            Console.WriteLine(proxy.GetLength("Hello"));

            try

            {

                Console.WriteLine(proxy.GetLength(null));

            }

            catch (Exception e)

            {

                Console.WriteLine("Exception on GetLength(null): {0}", e);

            }

            Console.WriteLine(proxy.Divide(4, 3));

            try

            {

                Console.WriteLine(proxy.Divide(4, 0));

            }

            catch (Exception e)

            {

                Console.WriteLine("Exception on Divide(4, 0): {0}", e);

            }

     

            ((IClientChannel)proxy).Close();

            factory.Close();

            host.Close();

        }

    }

    Monday, February 25, 2008 6:35 AM
  • Is there any way to tell the ServiceHost about the MyServiceBehavior using web.config? It's just that I'm using a .svc file in IIS to host it so I can't just add

     

    host.Description.Behaviors.Add(new MyServiceBehavior());

     

     

    Because I don't have access to the "host" object.
    Monday, February 25, 2008 8:32 PM
  • Another question: Is there a way I can avoid putting a [FaultContract] on each of my operations and still use this global exception handling solution? (do you see how I'm always looking to cut down on the number of lines of code?? Smile
    Monday, February 25, 2008 8:37 PM
  • I'm pretty new to WCF (only getting up to speed over the past  few days), but as I understand it:
    1) If you throw the generic FaultException()  (not the FaultException<MyCustomFault>() ), then you don't have to specify the [FaultContract] - but then I don't know if you'll be able to include anything in the detail.

    2) I think if you implement your own ServiceHostFactory class which inherits from ServiceHostFactoryBase, and then add the behaviour in the CreateServiceHost override, you should get what you want. Remember then, that in order to tell WCF to use this factory, and not the generic one from the framework, you must specify a Factory in the svc file definition.

    ...if something's unclear, I'll see if I can't post some sample code.
    Thursday, February 28, 2008 1:14 PM
  • You can use web.config to tell the host to use the behavior, but it's not too simple - basically, you'll need to implement a config element class, register it in the config extensions, then use it - the custom text encoder sample (http://msdn2.microsoft.com/en-us/library/ms751486.aspx) shows how to do it.


    Another way, which is a lot simpler, is to use a service host factory, as Inquisitor Jax mentioned. There's an example of how to do that in http://blogs.msdn.com/carlosfigueira/archive/2007/12/26/modifying-code-only-settings-on-webhosted-services.aspx.

    Thursday, February 28, 2008 5:23 PM
  • You don't necessarily need to put the fault contract in the operations; that's mostly a way to tell clients that an error of that type may happen in the server. Below is a modified version of the code I posted before which doesn't use it. The clients will receive the errors wrapped in a FaultException, so you need to use the Reason property if you want to pass / retrieve detailed information:

     

    public class Post2898359b

    {

        [ServiceContract]

        public interface ITest

        {

            [OperationContract]

            //[FaultContract(typeof(string))]

            int GetLength(string input);

            [OperationContract]

            //[FaultContract(typeof(string))]

            int Divide(int x, int y);

        }

        public class Service : ITest

        {

            public int GetLength(string input) { return input.Length; }

            public int Divide(int x, int y) { return x / y; }

        }

        public class MyErrorHandler : IErrorHandler

        {

            public bool HandleError(Exception error)

            {

                Console.WriteLine("This error occurred somewhere: {0}", error.Message);

                return true;

            }

            public void ProvideFault(Exception error, MessageVersion version, ref Message fault)

            {

                fault = Message.CreateMessage(

                    version,

                    new FaultException<string>(error.Message, new FaultReason(error.Message)).CreateMessageFault(),

                    "http://the.fault.action");

            }

        }

        public class MyServiceBehavior : IServiceBehavior

        {

            public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)

            {

            }

            public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)

            {

                foreach (ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers)

                {

                    channelDispatcher.ErrorHandlers.Add(new MyErrorHandler());

                }

            }

            public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)

            {

            }

        }

        public static void Test()

        {

            string baseAddress = "http://" + Environment.MachineName + ":8000/Service";

            ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));

            host.AddServiceEndpoint(typeof(ITest), new BasicHttpBinding(), "");

            host.Description.Behaviors.Add(new MyServiceBehavior());

            host.Open();

            Console.WriteLine("Host opened");

     

            ChannelFactory<ITest> factory = new ChannelFactory<ITest>(new BasicHttpBinding(), new EndpointAddress(baseAddress));

            ITest proxy = factory.CreateChannel();

            Console.WriteLine(proxy.GetLength("Hello"));

            try

            {

                Console.WriteLine(proxy.GetLength(null));

            }

            catch (FaultException e)

            {

                Console.WriteLine("FaultException: {0}", e.Message);

            }

            catch (Exception e)

            {

                Console.WriteLine("Exception on GetLength(null): {0}", e);

            }

            Console.WriteLine(proxy.Divide(4, 3));

            try

            {

                Console.WriteLine(proxy.Divide(4, 0));

            }

            catch (FaultException e)

            {

                Console.WriteLine("FaultException: {0}", e.Message);

            }

            catch (Exception e)

            {

                Console.WriteLine("Exception on Divide(4, 0): {0}", e);

            }

     

            ((IClientChannel)proxy).Close();

            factory.Close();

            host.Close();

        }

    }

    Thursday, February 28, 2008 5:28 PM
  • the above exercise wud mean to affect the sevice code...

    is there  any way to inject the error handling logic at the proxy level??

    most of the forums address error handling from the service side...

    additionally, the services are java/asmx/wcf services....

    thus, the injection of error handling logic needs to be handled at the proxy level..

     

    any help appreciated

     

    Sunday, May 18, 2008 11:34 AM
  • I agree with Pallz above, everybody seems to think that we have access to the web service and can make all these changes to the web service - NOT SO!  It it more likely that a third-party external partner has provided a web service for clients to use.  Partners usually provide WSDL documents to their clients and API instructions to connect to their services.  I have found that most often these providers stick with "old" tried and true SOAP apps using ASMX or the Java equivalent.  I have YET to have an external partner using WCF over the last three years.

    So the question is, how do you get SoapException fault details that are in the "detail" section of a standard SOAP Fault response.  For those who don't know, the "detail" section is where developers put the application level errors that they detect while processing the request sent to the service.  Using this method the web service providers create (throw) a custom SoapException with the particulars of the error(s) detected in the "detail" section of the SoapException fault.  This is like the WCF FaultException<CustomFaultType> response.  I like the WCF way of error handling except there are no partners that use WCF.  The major difference in WCF custom fault and the old SoapException is the "contract" between the service and the client.  This means that WCF service to WCF clients is strongly typed just like everything else in the service contract and handled just about the same way.  SoapExceptions are completely custom, they are not in the WSDL so are not generated by any tool.  It usually is accomplised in the API instruction provided by the web service partner.

    The problem I have found is that WCF does not provide the content (inner xml) of the detail section of the SoapException fault (FaultException in WCF).  So even with the API instructions I can't get the actual details of the error that the web service has thrown.

    This is a BIG problem.  Granted, many developers bypassed the Soap specifications and provided application level error responses as part of the normal response - kluge but effective on the cheap.  Nevertheless, the Soap specifications provisioned the SoapException fault mechanism and so far I can't find anyway to get at the "detail" using WCF!

    I sure wish someone would tell me how.

    Larry
    Monday, November 2, 2009 2:09 AM
  • I agree with J Larry. Someone plese tell us.
    Wednesday, June 2, 2010 6:17 PM
  • Simply throw a FaultException<TDetail> to provide a SOAP fault to the client.
    John Saunders
    WCF is Web Services. They are not two separate things.
    Use WCF for All New Web Service Development, instead of legacy ASMX or obsolete WSE
    Use File->New Project to create Web Service Projects
    Saturday, July 24, 2010 12:15 AM
    Moderator
  • Here is a link to example project code the demonstrates what John Saunders is alluding to in his reference to the FaultException.

    http://www.codeproject.com/KB/webservices/SoapClientTest.aspx 

    Larry

    Sunday, July 25, 2010 2:19 PM
  • Hi,

    you can create and attribute extension for the fault contract.

    You can use the same code given by carlos and put it in the library.

    Summarizing this:

    1. Create library with types implementing servicebehaviour,Ierrorhandler and attribute.

    2. Sign the assembly.

    3. Install the assembly in gac.

    4 put entry in the web.config as an attribute extension.

    It will get called whenever there is an exception in the application.

    Refer the link in case:

    http://dansen.wordpress.com/

    http://codeidol.com/csharp/wcf/Faults/Error-Handling-Extensions/

     

    Thanks,


    chandan mahajan
    • Proposed as answer by Nhancers Tuesday, November 30, 2010 11:24 AM
    Monday, November 29, 2010 11:23 AM