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
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 SoapFault
s, 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.
To demonstrate exception handling, I have created a simple WCF Service Application (named "DemoService") which is having a single OperationContract
"Divide
".
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);
}
}
and the modified DemoService.svc.cs file content is given below:
namespace DemoService
{
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
.
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).
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();
Console.Write("Enter Number 2: ");
string line2 = Console.ReadLine();
int num1;
int num2;
if (int.TryParse(line1, out num1) &&
int.TryParse(line2, out num2))
{
DemoServiceReference.DemoServiceClient client =
new DemoServiceReference.DemoServiceClient(); ;
try
{
client.Open();
Console.WriteLine("\nOutput: {0}",
client.Divide(num1, num2));
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:
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:
As we have already created a console application to consume our demo service, I've set the DemoClient
project as startup project.
After running the application, I'm giving the input values as zero (0).
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."
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:
="1.0"
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
<directoryBrowse enabled="true"/>
</system.webServer>
</configuration>
Before enabling includeExceptionDetailInFaults="true"
, we have got the below exception details:
But after enabling, we have got some more details in the exception details as below:
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.
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
.
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();
Console.Write("Enter Number 2: ");
string line2 = Console.ReadLine();
int num1;
int num2;
if (int.TryParse(line1, out num1) &&
int.TryParse(line2, out num2))
{
DemoServiceReference.DemoServiceClient client =
new DemoServiceReference.DemoServiceClient(); ;
try
{
client.Open();
Console.WriteLine("\nOutput: {0}",
client.Divide(num1, num2));
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
.
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).
[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);
}
In 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 are done with the service side changes, make sure to update the service reference.
Now run our DemoClient
with input as zero to test the FaultContract
.
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 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 the 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:
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
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
​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.
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.
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
)
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
{
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();
Console.Write("Enter Number 2: ");
string line2 = Console.ReadLine();
int num1;
int num2;
if (int.TryParse(line1, out num1) &&
int.TryParse(line2, out num2))
{
DemoServiceReference.DemoServiceClient client =
new DemoServiceReference.DemoServiceClient(); ;
try
{
client.Open();
Console.WriteLine("\nOutput: {0}",
client.Divide(num1, num2));
client.Close();
}
catch (TimeoutException e)
{
Console.WriteLine("The service operation timed out. " + e.Message);
client.Abort();
}
catch (FaultException<DemoServiceReference.ValidationFault> e)
{
Console.WriteLine("Message: {0}, Description: {1}",
e.Detail.Message, e.Detail.Description);
client.Abort();
}
catch (FaultException<DemoServiceReference.DivideByZeroFault> e)
{
Console.WriteLine("Message: {0}, Description: {1}",
e.Detail.Message, e.Detail.Description);
client.Abort();
}
catch (FaultException e)
{
Console.WriteLine(e.Message);
client.Abort();
}
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();
}
}
}
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.
- 29th January, 2016: Initial version