Working with Error/Exception(s) in WCF

By default, if no exception handling has been implemented in the service, and an error condition occurs , the client gets a generic standard error message (which is good for security) and the communication channel between the service and its client gets faulted/broken and can not be re-used. However during development, in debug mode, if “includeExceptionDetailInFaults” is set to true, which can be done either in config file or in code — then the client can see all exception details. This obviously is not applicable in live environment. By default,”includeExceptionDetailInFaults” is false in config file. In WCF, exceptions travel as SOAP faults and not .net exception, since a client can be any non .net application. There are 2 important classes for this –> “FaultException” and “FaultException<TDetail>”. Here’s a minimal hierarchy.
SystemException –> CommunicationException –> FaultException –> FaultException<TDetail>. Notice that FaultException<T> is derived from FaultException. The FaultException<T> is recommended, since we can define our own custom class, to
send whatever amount of error-information we may want, and client is aware during the build/design phase what class (structure/format) the exception is expected. In contrary, FaultException has definite pre-defined fields and client checks for FaultReason and FaultCode.
FaultException — Constructors
public FaultException(String);
public FaultException(String,FaultCode);
public FaultException(FaultReason);
public FaultException(FaultReason,FaultCode,String);

FaultException<T> — Constructors
public FaultException(TDetail);
public FaultException(TDetail, String);
public FaultException(TDetail, FaultReason, FaultCode);
public FaultException(TDetail, String, FaultCode);

I kinda extended this, on a msdn example. Here’s the Service-side & client-side code and we will see some scenarios here —

//The Service

namespace WcfErrorHandling
{
// NOTE: You can use the "Rename" command on the "Refactor" menu to change the interface name "IErrorHandlingService" in both code and config file together.
[ServiceContract]
public interface IErrorHandlingService
{
    [OperationContract]
    [FaultContract(typeof(MathFault))]
    [FaultContract(typeof(GenericDBFaultException))]
    int Divide(int n1, int n2);
}
}

 

public class ErrorHandlingService : IErrorHandlingService
{
public int Divide(int a, int b)
{
//throw new ApplicationException("zzzzzzzzz");
try
{
    //DivideByZero exception
    int c = a / b;
    //Run some DB related stuff to generate a SQL Exception
    SqlConnection conn = new SqlConnection();
    conn.ConnectionString = "Your Connection string";
    SqlCommand cmd = new SqlCommand();
    cmd.CommandText = "SELECT City, StateProvinceID FROM [AdventureWorks].[Person].[Address] ";
    cmd.Connection = conn;
    StringBuilder sb = new StringBuilder();
    conn.Open();
    SqlDataReader reader = cmd.ExecuteReader();
    conn.Close();
    return a / b;
}
catch (DivideByZeroException ex1)
{
    //Log the actual exception and Stack Trace in some 
    //persistent medium like event log or Database, send email and 
    //finally let client have a short message
    //informing something went wrong

    MathFault _MathFault = new MathFault();
    _MathFault.ProblemType = "Can not divivde by zero";
    _MathFault.Operation = "DIVIDE_ERROR_CODE";
    throw new FaultException<MathFault>(_MathFault);
}
catch (SqlException ex2)
{
    //Log the actual exception and Stack Trace in some 
    //persistent medium like event log or Database, send email 
    //and finally let client have a short message
    //informing something went wrong

    GenericDBFaultException _GenericDBFaultException = new GenericDBFaultException();
    _GenericDBFaultException._ErrorCode = ErrorCode.ERROR_NUMBER;
    _GenericDBFaultException.message = "Some database exception occured.";
    throw new FaultException<GenericDBFaultException>(_GenericDBFaultException);
}       
}
}

 

//The Service Class file

namespace WcfErrorHandling
{
// Define a math fault data contract
[DataContract]
public class MathFault
{
    private string operation;
    private string problemType;

    [DataMember]
    public string Operation
    {
        get { return operation; }
        set { operation = value; }
    }

    [DataMember]
    public string ProblemType
    {
        get { return problemType; }
        set { problemType = value; }
    }
}

//Define another generic DB related Fault contract
[DataContract]
public class GenericDBFaultException
{
    [DataMember]
    public ErrorCode _ErrorCode;

    [DataMember]
    public string message;
}

[DataContract]
public enum ErrorCode
{
    [EnumMember]
    ERROR_NUMBER = 1001,

    [EnumMember]
    ERROR_VALUE = 23
}
}

//The Client

protected void Button1_Click(object sender, EventArgs e)
{
try
{
    ServiceReference1.ErrorHandlingServiceClient MyErrorService = new ServiceReference1.ErrorHandlingServiceClient();
    MyErrorService.Divide(2, 0);
}
catch (TimeoutException timeProblem)
{
    //operation exceeds the specified timeout period
    Label1.Text = "<br>The service operation timed out. " + timeProblem.Message;
    //Take any appropriate action here
}
catch (FaultException<MathFault> MathFault)
{
    Label1.Text = "<br>Detailed MathFault message " + MathFault.Detail.ProblemType;
    //Take any appropriate action here
}
catch (FaultException<GenericDBFaultException> GenericDBFaultException)
{
    Label1.Text = "<br>Detailed GenericDBFaultException message " + GenericDBFaultException.Detail._ErrorCode;
    //Take any appropriate action here
}
catch (FaultException unknownFault)
{
    Label1.Text = "<br>An unknown exception was received. " + unknownFault.Message;
    ///Take any appropriate action here
}
catch (CommunicationException commProblem)
{
    Label1.Text = "<br>There was a communication problem. " + commProblem.Message + commProblem.StackTrace;
    //Take any appropriate action here
}
}

In the service, the Divide operation has 2 FaultContracts — MathFault and GenericDBFaultException.
Scenario 1:
If DivideByZero exception occurs, then at client end — catch (FaultException<MathFault> MathFault) will capture that. Similarly for any SQLException raised in the service, — at client end, catch (FaultException<GenericFaultException> GenericFaultException) will get invoked.

Scenario 2:-
Let’s say, even though I have 2 FaultContracts on the method Divide, I throw a .net exception instead of a FaultException ie — something like, throw new ApplicationException(“zzzzzzzzz”);
In the client end , it gets caught under —  catch (FaultException unknownFault). FaultException exceptions are thrown when a listener receives a fault that is not expected or specified in the operation contract. In other words, if you
have a FaultContract defined on the method, and you throw a .Net exception — at the client end , it can be caught under FaultException catch with a generic message like this.
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 3.0 SDK documentation and inspect the server trace logs.

Scenario 3:-
If in the service interface there is no FaultContract defined, and the method raises a FaultException<T>, then the client won’t have any idea about the custom class, since the wsdl is not updated.Hence the client won’t even build.Quote
from MSDN —
SOAP faults are publicly described messages that carry fault information for a particular operation. Because they are described along with other operation messages in WSDL, clients know and, therefore, expect to handle such faults when
invoking an operation.

The keyword “using” can’t be used in the service, hence try/catch everywhere.
In the WCF service, if you want to catch all possible exceptions, at a centralized place , something similar to global.asax, IErrorHandler interface needs to be implemented.
More on “IErrorHandler”, some other time. 

http://msdn.microsoft.com/en-us/library/ms732013.aspx

That’s all about it. Thanks for reading.

This entry was posted in WCF. Bookmark the permalink.

Leave a comment