Click here to Skip to main content
11,629,535 members (67,382 online)
Click here to Skip to main content

WCF - Exception Handling, FaultException and FaultContracts

, 25 Aug 2014 CPOL 149.4K 742 37
Rate this:
Please Sign up or sign in to vote.
WCF - Exception Handling, Global Exception Handling, FaultExceptions and FaultContracts- Detailed explantion with Sample Codes.

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 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 much straight forward, 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?

Handling exceptions in WCF Service is different than usual exceptions in .NET. As the WCF clients may not be based on .NET Technologies we cannot utilize the .NET Exception object to exchange exception details over the wire. For this purpose we should have a generic way to pass exception details to all type of clients, who consumes WCF Service.

For this reason we have SOAP faults, SOAP faults are the way to propagate the exceptions from the service to the client application. .Net has FaultException<T> generic class which can raise SoapFault exception. Service should throw FaultException<T> instead of usual CLR Exception object. T can be any type which can be serialized.

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,

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,

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,

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();

        }
    }

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

Lets consider this as our base code for the demonstration, now the Solution Explorer will have 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 thorws an exception, the same happened here also, but what exception we are 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 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 below exception details

WCF Exception Handling

but after enabling we have got a 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 informations like service stack trace.

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

FaultExceptions

As we saw in the previous section, WCF already throws a FaultException whenever it encounters an unhandled exception.  A FaultException is used to send untyped fault data to the client.

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

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,

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

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 generic version is used to send typed fault data to the client. TDetail represents the type parameter for the fault information. The FaultContractAttribute attribute indicates that the Divide operation may return a fault of type DivideByZeroFault. 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)

[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

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

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

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 have 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.

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 the input as zero then you will get the stongly typed faultexception as below,

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

[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

[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,

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 below code block to try/catch section in Program.cs

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

Output:

Rewind

FaultException and Exception

Exception and FaultException are not 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:

throw new Exception("Divide by zero");

By raising the above exception, 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:

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

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

Divide by zero

In general, it is strongly recommended that you use the FaultContractAttribute to design your services to return strongly-typed SOAP faults (and not managed exception objects) for all fault cases in which you decide the client requires fault information. However, use the FaultException in the following situations:

  • To send SOAP faults from a service for debugging purposes.
  • To catch SOAP faults on a client when the faults are not part of the service contract.

Note:  FaultException extends CommunicationException, remember to catch any FaultException objects prior to catching CommunicationException objects if you want to catch them separately.

FaultContract and FaultException

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

Use the FaultException class to create an untyped fault to return to the client for debugging purposes. It is recommended that service operations use the FaultContractAttribute to formally specify all SOAP faults that a client can expect to receive in the normal course of an operation. It is also recommended that only those information a client must know is returned in a SOAP fault to minimize information disclosure.

Note: One-way operations do not support SOAP faults and therefore do not support FaultContractAttribute.

Code

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

IDemoService.cs (DemoService)

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)

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)

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();

        }
    }
}

References

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. 

License

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

Share

About the Author

Shemeer NS
Software Developer (Senior)
India India
Technology Specialist | CodeProject MVP | Visual Studio Gallery Contributor | Author | Geek | Netizen | Husband | ChessPlayer

Most of my articles are listed on top 5 of the respective 'Best articles of the month' and some of my articles are published on ASP.NET WebSite's Article of the Day section.

Check my contributions in Visual Studio Gallery and Code Project

Technical Blog: http://www.shemeerns.com
Facebook: http://facebook.com/shemeernsblog
Twitter : http://twitter.com/shemeerns
Google+ : http://google.com/+Shemeernsblog

You may also be interested in...

Comments and Discussions

 
QuestionAsynchronous Pin
Ross Brigoli24-Jun-15 3:10
memberRoss Brigoli24-Jun-15 3:10 
QuestionWrapper for handling exception on client Pin
Evgeny Ivanov16-Jun-15 23:41
memberEvgeny Ivanov16-Jun-15 23:41 
GeneralMy vote of 4 Pin
Suvendu Shekhar Giri29-Apr-15 3:36
professionalSuvendu Shekhar Giri29-Apr-15 3:36 
QuestionNice explanation in usefult and simple way. Pin
Prabhu Yadav16-Apr-15 20:33
memberPrabhu Yadav16-Apr-15 20:33 
QuestionGreat explanation Pin
jgnavarron25-Mar-15 0:31
memberjgnavarron25-Mar-15 0:31 
General4 star Pin
varma_vizag20-Feb-15 1:41
membervarma_vizag20-Feb-15 1:41 
QuestionVery well written - My vote of 5 Pin
AndySydneyAU2-Sep-14 3:06
memberAndySydneyAU2-Sep-14 3:06 
GeneralMy vote of 5 Pin
Mihai MOGA13-Aug-14 3:12
professionalMihai MOGA13-Aug-14 3:12 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.150723.1 | Last Updated 26 Aug 2014
Article Copyright 2014 by Shemeer NS
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid