WCF Simplified Part 5: Handling Exceptions and Faults

Today we’ll look at how WCF handles exceptions. Before we look at the code, a few things to note,

    1. Uncaught CLR exceptions are turned into a SOAP fault (FaultException).

    2. By default, exception details (even exception type) are not shared with clients.
        Use IncludeExceptionDetailsInFaults (includes type, message, and stack trace) for debugging purposes only.

    3. Uncaught exceptions will kill WCF sessions.

Let’s see some of these in action,

1. Open the WCF service app from the introductory post (run Visual Studio as admin) and change the IService1.cs to have this,

[DataContract]
public class MyCustomException {
    [DataMember]
    public string MyMessage { get; set; }
}

[ServiceContract]
public interface IService1 {

    [OperationContract]
    string ThrowsCLRException();

    [OperationContract(IsOneWay=true)]
    void ThrowsCLRExceptionOneWay();

    [OperationContract]
    string ThrowsFaultException();

    [OperationContract(IsOneWay = true)]
    void ThrowsFaultExceptionOneWay();

    [OperationContract]
    [FaultContract(typeof(IndexOutOfRangeException))]
    string ThrowsTypedCLRFaultException();

    [OperationContract]
    [FaultContract(typeof(MyCustomException))]
    string ThrowsTypedCustomFaultException();
}

 

2. Change the implementation file, Service1.cs to this,

public class Service1 : IService1 {
    public string ThrowsCLRException(){
        // Throw a CLR exception
        throw new IndexOutOfRangeException("ThrowsCLRException: IndexOutOfRangeException");
    }

    public void ThrowsCLRExceptionOneWay() {
        throw new IndexOutOfRangeException("ThrowsCLRExceptionOneWay: IndexOutOfRangeException");
    }

    public string ThrowsFaultException() {
        // Throw a fault exception
        throw new FaultException("ThrowsFaultException: FaultException", new FaultCode("CalculationError"));
    }

    public void ThrowsFaultExceptionOneWay() {
        throw new FaultException("ThrowsFaultExceptionOneWay: FaultException");
    }

    public string ThrowsTypedCLRFaultException() {
        // Throw a typed FaultException with a CLR exception
        throw new FaultException<IndexOutOfRangeException>(new IndexOutOfRangeException(), 
            new FaultReason("ThrowsTypedCLRFaultException: FaultException<IndexOutOfRangeException>"));
    }

    public string ThrowsTypedCustomFaultException() {
        // Throw a typed FaultException with a custom exception
        MyCustomException fault = new MyCustomException();
        fault.MyMessage = "ThrowsTypedCustomFaultException: FaultException<MyCustomException>";
        throw new FaultException<MyCustomException>(fault, new FaultReason("No reason!"));
    }
}

3. And the Main method to,

using (ServiceHost host = new ServiceHost(typeof(WcfServiceLibrary1.Service1), new Uri("http://localhost:8000/HelloWCF"))) {
    // Set up a service endpoint [Contract, Binding, Address]
    host.AddServiceEndpoint(typeof(WcfServiceLibrary1.IService1), new BasicHttpBinding(), "HelloWCF");

    // Enable metadata exchange
    ServiceMetadataBehavior smb = new ServiceMetadataBehavior() { HttpGetEnabled = true };
    host.Description.Behaviors.Add(smb);

    // Enable exeption details
    ServiceDebugBehavior sdb = host.Description.Behaviors.Find<ServiceDebugBehavior>();
    sdb.IncludeExceptionDetailInFaults = false;

    host.Open();

    Console.WriteLine("Ready...");
    Console.ReadLine();
}

4. Now run the service app and update the Service Reference in the client console app. Change the client code to,

EndpointAddress epoint = new EndpointAddress("http://localhost:8000/HelloWCF/HelloWCF");
ServiceReference1.IService1 proxy = ChannelFactory<ServiceReference1.IService1>.CreateChannel(new BasicHttpBinding(), epoint);

using (proxy as IDisposable) {
    
    // Even though a CLR exception is thrown; WCF converts it to a FaultException on the client
    try { proxy.ThrowsCLRException(); }
    catch (FaultException fex) { Console.WriteLine("1. " + fex.Message); }
    Console.WriteLine();

    // One-way - no reply message - nothing gets caught
    try { proxy.ThrowsCLRExceptionOneWay(); }
    catch (FaultException fex) { Console.WriteLine("2. " + fex.Message); }
    catch (Exception ex) { Console.WriteLine("2. " + ex.Message); }
    Console.WriteLine();

    // WCF service threw a FaultException
    try { proxy.ThrowsFaultException(); }
    catch (FaultException fex) {
        if (fex.Code.Name == "CalculationError") { Console.WriteLine("3. " + fex.Message); }
    }
    Console.WriteLine();

    // One-way - no reply message - nothing gets caught
    try { proxy.ThrowsFaultExceptionOneWay(); }
    catch (FaultException fex) { Console.WriteLine("4. " + fex.Message); }
    catch (Exception ex) { Console.WriteLine("4. " + ex.Message); }
    Console.WriteLine();

    // WCF service threw a typed FaultException - note the catch
    try { proxy.ThrowsTypedCLRFaultException(); }
    catch (FaultException<IndexOutOfRangeException> fex) { Console.WriteLine("5. " + fex.Message); }
    Console.WriteLine();

    // WCF service threw a typed FaultException - note the catch
    try { proxy.ThrowsTypedCustomFaultException(); }
    catch (FaultException<ServiceReference1.MyCustomException> fex) 
    { 
        Console.WriteLine("6. Reason:" + fex.Message);
        Console.WriteLine(fex.Detail.MyMessage);
    }
}
Console.ReadLine();

5. When you run this code, you’ll see,

image

Some things to note,

1. There is no output for ThrowsCLRExceptionOneWay and ThrowsFaultExceptionOneWay obviously.

2. In the ThrowsCLRException case – a CLR exception is caught as a FaultException, but there are no associated details about the exception.

3. We can throw a typed FaultException using a <CLRException> or a <CustomException>.

Now to see CLR exception details, in step3 change to,

sdb.IncludeExceptionDetailInFaults = true;

Now running the client you should get the CLR exception details,

image

The last thing to discuss is that throwing a CLR exception will actually kill the WCF session/channel, but a FaultException will not. Since this example was written using BasicHttpBinding which has no concept of WCF sessions, everything worked. To see the problem change the binding on both the host app and the client app to use WSHttpBinding. Now running the service and client results in,

image

because the WCF session was crashed by the thown CLR exception (even though it shows up as a FaultException on the client). This can be fixed by designing your service code to catch all CLR exceptions and always throwing FaultException from the service.

Advertisements

About soumya chattopadhyay
I live and work in Seattle, WA. I work with Microsoft technologies, and I'm especially interested in C#.

3 Responses to WCF Simplified Part 5: Handling Exceptions and Faults

  1. Pingback: WCF Simplified Series « I.Net

  2. Luis says:

    Hi, when I run this example, the exception is not propagated to the client.
    I’m receiving the message “IndexOutOfRangeException was unhandled by user code” in the method ThrowsCLRException at the server application.
    Do you know which could be the problem?
    Thanks

  3. Hasmukh says:

    Good one

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: