Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / programming / exceptions

WCF - Exception Handling, FaultException and FaultContracts

4.63/5 (55 votes)
29 Jan 2016CPOL6 min read 304.4K   2K  
WCF - Exception Handling, Global Exception Handling, FaultExceptions and FaultContracts
In this article, you will find a detailed explanation with sample code for exception handling, global exception handling, fault exceptions and faultcontracts in WCF.

Table of Contents

Introduction

Whenever we write a program, we should expect the unexpected and add appropriate code to handle those exceptions. When we write a web or Windows based application, we use try/catch blocks in our methods to catch exceptions, and also we show a user friendly message in UI.

But when we work with WCF Service, It is not that straightforward, so:

  • How do we handle exceptions in WCF Service?
  • How to propagate a user-friendly error message to the client, who consumes the WCF Service?
  • How to send business rule validation messages to WCF client?

WCF consumers can be using any technology, due to which sending CLR exception will not make any sense and to avoid this situation, we need to have a better exception handling in WCF Service.

To help WCF service, .NET got SoapFaults, FaultException<T>, where T can be any type which can be serialized. Using SoapFaults, we can avoid sending CLR Exception object to client.

In this article, we will try to learn all the above mentioned points with the use of a sample WCF service implementation.

Creating DemoProject(s)

To demonstrate exception handling, I have created a simple WCF Service Application (named "DemoService") which is having a single OperationContract "Divide".

WCF Sample Project for Demonstrating Exception Handling

Then I have refactored all Service and named default files to DemoService.

The modified IDemoService.cs file text is given below:

C#
namespace DemoService
{    
    [ServiceContract]
    public interface IDemoService
    {
        [OperationContract]
        int Divide(int n1, int n2);
    }

    //TODO: Add data contract Fault Exception
}

and the modified DemoService.svc.cs file content is given below:

C#
namespace DemoService
{    
    // NOTE: In order to launch WCF Test Client for testing this service, 
    // please select Service1.svc or Service1.svc.cs at the Solution Explorer 
    // and start debugging.
    public class DemoService : IDemoService
    {
        public int Divide(int n1, int n2)
        {
            return n1 / n2;
        }
    }
}

To demonstrate the samples, I'm going to create a Console Application (named "DemoClient") which will consume the DemoService.

WCF Client Sample

To consume the WCF service, I'm going to add Service Reference to the DemoClient console application using the AddServiceReference menu (right click on Reference folder).

Add WCF Service Reference to a Client Application

Now we have got the ServiceReference added to our DemoClient console app. Program.cs is modified as below:

C#
namespace DemoClient
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("***Divide***\n\n");

            Console.Write("Enter Number 1: ");
            string line1 = Console.ReadLine(); // Read string from console

            Console.Write("Enter Number 2: ");
            string line2 = Console.ReadLine(); // Read string from console

            int num1;
            int num2;

            if (int.TryParse(line1, out num1) && 
                int.TryParse(line2, out num2)) // Try to parse the string as an integer
            {
                DemoServiceReference.DemoServiceClient client = 
                                     new DemoServiceReference.DemoServiceClient(); ;

                try
                {
                    client.Open();

                    Console.WriteLine("\nOutput: {0}", 
                    client.Divide(num1, num2)); // divide the numbers and display the result.

                    client.Close();

                }
                catch (Exception e)
                {
                    Console.WriteLine(e.ToString());
                    client.Abort();
                }
            }
            else
            {
                Console.WriteLine("Invalid inputs!.");
            }

            Console.ReadKey();
        }
    }

The above code block will show the console and accepts two numbers as inputs. These two numbers will be passed to the DemoService Divide method and the result will be shown in the console.

Let's consider this as our base code for the demonstration, now the Solution Explorer will have the below structure:

Sample Solution Explorer WCF

.NET Exception Object

Okay. Now let us run our application.

In order to launch WCF Test Client for testing this service, please set DemoService as startup project and select Service1.svc or Service1.svc.cs at the Solution Explorer and start debugging. Using WCF Test Client, I got an output for the sample inputs as below:

WCF Test Client

As we have already created a console application to consume our demo service, I've set the DemoClient project as startup project.

Set as Startup Project

After running the application, I'm giving the input values as zero (0).

WCF Exception Handling

As we all know, dividing by zero throws an exception, the same happened here also, but what exception are we getting:

"The server was unable to process the request due to an internal error. 
For more information about the error, either turn on IncludeExceptionDetailInFaults 
(either from ServiceBehaviorAttribute or from the <serviceDebug> configuration behavior) 
on the server in order to send the exception information back to the client, 
or turn on tracing as per the Microsoft .NET Framework SDK documentation 
and inspect the server trace logs."

WCF FaultException Caught

By seeing the above error message, we won't get any clue of the actual error. This generic error message will make the client confused about the error raised by the service.

As the generic error message suggested, we can enable exception details in faults by setting a config key.

I have modified the DemoService's web.config to includeExceptionDetailInFaults="true", so that the exception details are included in faults:

XML
<?xml version="1.0"?>
<configuration>

  <system.web>
    <compilation debug="true" targetFramework="4.0" />
  </system.web>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <!-- To avoid disclosing metadata information, 
               set the value below to false before deployment -->
          <serviceMetadata httpGetEnabled="true"/>
          <!-- To receive exception details in faults for debugging purposes, 
               set the value below to true. Set to false before deployment 
               to avoid disclosing exception information -->
          <serviceDebug includeExceptionDetailInFaults="true"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
  </system.serviceModel>
 <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
    <!--
        To browse web app root directory during debugging, set the value below to true.
        Set to false before deployment to avoid disclosing web app folder information.
      -->
    <directoryBrowse enabled="true"/>
  </system.webServer>

</configuration>

Before enabling includeExceptionDetailInFaults="true", we have got the below exception details:

WCF Exception Handling

But after enabling, we have got some more details in the exception details as below:

WCF Exception Handling

An exception is a CLR concept and it does not make any sense to expose this outside of the CLR and also we should not be doing this practice outside development environment (debugging purposes) as this exception contains potentially dangerous information like service stack trace.

We should use strongly typed FaultException for sending exceptions to the clients, who consume our WCF service.

FaultExceptions

As we saw in the previous section, WCF already throws a FaultException whenever it encounters an unhandled exception (untyped fault).

I have updated the DemoService.svc.cs to throw an untyped FaultException in case of DivideByZeroException.

C#
public int Divide(int n1, int n2)
{
    try
    {
        return n1 / n2;
    }
    catch (DivideByZeroException e)
    {
        throw new FaultException(e.Message);
    }
}

and also I have updated the DemoClient's program.cs to catch FaultException like below:

C#
static void Main(string[] args)
{
    Console.WriteLine("***Divide***\n\n");

    Console.Write("Enter Number 1: ");
    string line1 = Console.ReadLine(); // Read string from console

    Console.Write("Enter Number 2: ");
    string line2 = Console.ReadLine(); // Read string from console

    int num1;
    int num2;

    if (int.TryParse(line1, out num1) && 
        int.TryParse(line2, out num2)) // Try to parse the string as an integer
    {
        DemoServiceReference.DemoServiceClient client = 
                             new DemoServiceReference.DemoServiceClient(); ;

        try
        {
            client.Open();

            Console.WriteLine("\nOutput: {0}", 
               client.Divide(num1, num2)); // divide the numbers and display the result.

            client.Close();
        }
        catch (FaultException e)
        {
            Console.WriteLine(e.Message);
            client.Abort();
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
            client.Abort();
        }
    }
    else
    {
        Console.WriteLine("Invalid inputs!.");
    }

    Console.ReadKey();
}

When we try to call the client application again with the zero inputs, we get the proper exception using the FaultException.

Image 11

FaultException<TDetail>

In the previous section, we saw how to use untyped FaultException to propogate the exception details to client. In this section, we will learn how to use strongly typed FaultException, to propogate exception details to WCF consumers.

This helps to send typed fault data, here TDetail will carry the fault information. Here, the OperationContract Divide is decorated by [FaultContract(typeof(DivideByZeroFault))] is to let the end client know that this operation contract may return a DivideByZeroFault fault. A fault can be of any type that can be serialized. In this case, the DivideByZeroFault is a data contract, as follows (in IDemoService.cs).

C#
[DataContract]
public class DivideByZeroFault
{
    [DataMember]
    public bool Result { get; set; }
    [DataMember]
    public string Message { get; set; }
    [DataMember]
    public string Description { get; set; }
}

and I have decorated Divide operation contract with the FaultContractAttribute.

C#
[ServiceContract]
public interface IDemoService
{
    [OperationContract]
    [FaultContract(typeof(DivideByZeroFault))]
    int Divide(int n1, int n2);
}

In the same way, I have modified the DemoService.svc.cs to send strongly typed FaultException.

C#
public int Divide(int n1, int n2)
{
    try
    {
        return n1 / n2;
    }
    catch (DivideByZeroException e)
    {
        DivideByZeroFault fault = new DivideByZeroFault();

        fault.Result = false;
        fault.Message = e.Message;
        fault.Description = "Cannot divide by zero.";

        throw new FaultException<DivideByZeroFault>(fault);     
    }
}

Once you are done with the service side changes, make sure to update the service reference.

Update WCF Service Reference

Now run our DemoClient with input as zero to test the FaultContract.

Strongly Typed Fault Exception WCF

We got the above message because we haven't added the code to catch FaultException<DemoServiceReference.DivideByZeroFault>. To handle this, I have added the below code in Program.cs file's try-catch.

C#
catch (FaultException<DemoServiceReference.DivideByZeroFault> e)
{
    Console.WriteLine("Message: {0}, Description: {1}", 
                       e.Detail.Message, e.Detail.Description);
    client.Abort();
}

Now if you run the application with input as zero, then you will get the stongly typed faultexception as below:

Image 14

Now I have added another DataContract for Validation purpose as below:

C#
[DataContract]
public class ValidationFault
{
    [DataMember]
    public bool Result { get; set; }
    [DataMember]
    public string Message { get; set; }
    [DataMember]
    public string Description { get; set; }
}

and decorated OperationContract Divide with the above ValidationFault FaultContract.

C#
[OperationContract]
[FaultContract(typeof(DivideByZeroFault))]
[FaultContract(typeof(ValidationFault))]
int Divide(int n1, int n2);

Now the Divide method implemention in DemoService.svc.cs looks like below:

C#
public int Divide(int n1, int n2)
{
    try
    {
        if (n1 == 0 && n2 == 0)
        {
            ValidationFault fault = new ValidationFault();

            fault.Result = false;
            fault.Message = "Numbers cannot be zero";
            fault.Description = "Invalid numbers";

            throw new FaultException<ValidationFault>(fault);
        }

        return n1 / n2;
    }
    catch (DivideByZeroException e)
    {
        DivideByZeroFault fault = new DivideByZeroFault();

        fault.Result = false;
        fault.Message = e.Message;
        fault.Description = "Cannot divide by zero.";

        throw new FaultException<DivideByZeroFault>(fault);
    }
}

To catch this validation fault, I have added the below code block to try/catch section in Program.cs.

C#
catch (FaultException<DemoServiceReference.ValidationFault> e)
{
    Console.WriteLine("Message: {0}, Description: {1}", 
                       e.Detail.Message, e.Detail.Description);
    client.Abort();
}

Output:

Image 15

Rewind

FaultException and Exception

Exception and FaultException are not the same. If we throw a normal exception from a WCF service, then the client who consumes that service will be receiving a generic error which will not give any details regarding the issue. Instead, we should use FaultException which will give a formatted exception to the client as it is serialized.

Exception

C#
throw new Exception("Divide by zero");

By raising the above exception, the client will be receiving the exception as below:

"The server was unable to process the request due to an internal error. 
For more information about the error, either turn on IncludeExceptionDetailInFaults 
(either from ServiceBehaviorAttribute or from the <serviceDebug> configuration behavior) 
on the server in order to send the exception information back to the client, 
or turn on tracing as per the Microsoft .NET Framework SDK documentation 
and inspect the server trace logs."

FaultException

C#
​throw new FaultException("Divide by zero");

In the above FaultException case, the client will receive the exception message as below:

Divide by zero

It is always advised to send strongly-typed SOAP faults instead of managed exception objects. FaultException can be used just for debugging purposes or service contract doesn't have a fault contract defined.

FaultContract and FaultException

We might think, when we can send un-typed FaultException to the client, then why do we need to make use of strongly-typed FaultException by adding FaultContractAttribute to the OpertionContract and raising FaultException<TDetail>.

It is always better to send strongly-typed SOAP faults that a client is expected to receive and also make sure to include minimal data which end client is supposed to know. Un-typed faults can be useful for debugging purposes.

Note: FaultContractAttribute is not supported in One-way operations.

Code

For your reference, I have attached the full source code with this article. For quick reference, I have presented some of the files below.

IDemoService.cs (DemoService)

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;

namespace DemoService
{
    [ServiceContract]
    public interface IDemoService
    {
        [OperationContract]
        [FaultContract(typeof(DivideByZeroFault))]
        [FaultContract(typeof(ValidationFault))]
        int Divide(int n1, int n2);
    }

    [DataContract]
    public class DivideByZeroFault
    {
        [DataMember]
        public bool Result { get; set; }
        [DataMember]
        public string Message { get; set; }
        [DataMember]
        public string Description { get; set; }
    }

    [DataContract]
    public class ValidationFault
    {
        [DataMember]
        public bool Result { get; set; }
        [DataMember]
        public string Message { get; set; }
        [DataMember]
        public string Description { get; set; }
    }
}

DemoService.svc.cs (DemoService)

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;

namespace DemoService
{
    // NOTE: In order to launch WCF Test Client for testing this service, 
    // please select Service1.svc or Service1.svc.cs at the Solution Explorer 
    // and start debugging.
    public class DemoService : IDemoService
    {
        public int Divide(int n1, int n2)
        {
            try
            {
                if (n1 == 0 && n2 == 0)
                {
                    ValidationFault fault = new ValidationFault();

                    fault.Result = false;
                    fault.Message = "Numbers cannot be zero";
                    fault.Description = "Invalid numbers";

                    throw new FaultException<ValidationFault>(fault);
                }

                return n1 / n2;
            }
            catch (DivideByZeroException e)
            {
                DivideByZeroFault fault = new DivideByZeroFault();

                fault.Result = false;
                fault.Message = e.Message;
                fault.Description = "Cannot divide by zero.";

                throw new FaultException<DivideByZeroFault>(fault);
            }
        }
    }
}

Program.cs (DemoClient)

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.Text;

namespace DemoClient
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("***Divide***\n\n");

            Console.Write("Enter Number 1: ");
            string line1 = Console.ReadLine(); // Read string from console

            Console.Write("Enter Number 2: ");
            string line2 = Console.ReadLine(); // Read string from console

            int num1;
            int num2;

            if (int.TryParse(line1, out num1) && 
                int.TryParse(line2, out num2)) // Try to parse the string as an integer
            {
                DemoServiceReference.DemoServiceClient client = 
                                     new DemoServiceReference.DemoServiceClient(); ;

                try
                {
                    client.Open();

                    Console.WriteLine("\nOutput: {0}", 
                    client.Divide(num1, num2)); // divide the numbers and display the result.

                    client.Close();

                }
                catch (TimeoutException e)
                {
                    Console.WriteLine("The service operation timed out. " + e.Message);
                    client.Abort();
                }
                // Catch the contractually specified SOAP fault raised here as an exception. 
                catch (FaultException<DemoServiceReference.ValidationFault> e)
                {
                    Console.WriteLine("Message: {0}, Description: {1}", 
                                       e.Detail.Message, e.Detail.Description);
                    client.Abort();
                }
                // Catch the contractually specified SOAP fault raised here as an exception. 
                catch (FaultException<DemoServiceReference.DivideByZeroFault> e)
                {
                    Console.WriteLine("Message: {0}, Description: {1}", 
                                       e.Detail.Message, e.Detail.Description);
                    client.Abort();
                }
                // Catch unrecognized faults. This handler receives exceptions thrown by WCF 
                // services when ServiceDebugBehavior.IncludeExceptionDetailInFaults  
                // is set to true or when un-typed FaultExceptions raised.
                catch (FaultException e)
                {
                    Console.WriteLine(e.Message);
                    client.Abort();
                }
                // Standard communication fault handler. 
                catch (CommunicationException e)
                {
                    Console.WriteLine("There was a communication problem. " + 
                                       e.Message + e.StackTrace);
                    client.Abort();
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.Message);
                    client.Abort();
                }
            }
            else
            {
                Console.WriteLine("Invalid inputs!.");
            }

            Console.ReadKey();
        }
    }
}

Summary

In this article, I have explained WCF - FaultException and FaultContract with a sample WCF service and console application. I hope you have enjoyed this article and got some value addition to your knowledge.

I have put my time and efforts on all of my articles, Please don't forget to mark your votes, suggestions and feedback to improve the quality of this and upcoming articles.

History

  • 29th January, 2016: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)