16 March, 2011

How to: WCF and Custom Exception Handling (Custom Exceptions), part 2

This is an extension to another post about custom exception handling in WCF.
Part 1: http://blog.clauskonrad.net/2008/06/wcf-and-custom-exceptions.html

If you desire to communicate your own custom exceptions and at the same time include custom data in that exception, you are better off by using the built-in DataContractSerializer. This stands in contrast to the previous way (see above post) of declaring exceptions; but the ease and “ready-to-use” condition of the DataContractSerializer makes it very difficult to argue for rolling your own serialization mechanism.

WCF uses by default the DataContractSerializer to serialize/deserialize data on the wire between the Server and Client, hence this is already in operation out-of-the-box.

How to create this? In this scenario, I have full control of both Client and Server side – hence I can easily share a “contract” between the two. In the below image, you see the 3 projects in effect.

Shared Contracts "Contracts"

Shared Contract
The contract will declare my custom exception called CalculationFaultContract. Note, that this is decorated with [DataContract] attributes as well as [DataMember] attributes. This will make the DataContractSerializer able to serialize this object between Server –> Client just fine.

/// <summary>
///
A custom type to convey error data to the Client
/// </summary>
[DataContract]
public class CalculationFaultContract
{
/// <summary>
///
Gets or sets the argument A.
/// </summary>
/// <value>
///
The arg A.
/// </value>
[DataMember]
public int ArgA { get; set; }

/// <summary>
///
Gets or sets the argument B.
/// </summary>
/// <value>
///
The arg B.
/// </value>
[DataMember]
public int ArgB { get; set; }

}

Server Side
On the server side, you still need to declare what exceptions your methods will throw. This is done using the [FaultContract]-attribute like below. This will inform WCF, that your method will throw this kind of exception to the Client in case of an error has occurred.

/// <summary>
///
/// </summary>
[ServiceContract]
public interface ICalculator
{
/// <summary>
///
Adds the specified arguments.
/// </summary>
/// <param name="a">
A.</param>
/// <param name="b">
The b.</param>
/// <returns></returns>
[OperationContract]
[FaultContract(typeof(CalculationFaultContract))]
int Add(int a, int b);

/// <summary>
///
Subtracts the specified arguments.
/// </summary>
/// <param name="a">
A.</param>
/// <param name="b">
The b.</param>
/// <returns></returns>
[OperationContract]
[FaultContract(typeof(CalculationFaultContract))]
int Subtract(int a, int b);
}

The ICalculator-implementation will, in case of an error, throw the fault like below. Note, that when programming with services, we are principally dealing with “Faults” over “Exceptions”. An exception is a platform specific entity, that can not be carried from Server –> Client. Remember, the Client could potentially be running on a Java-platform, so how would you communicate a .NET exception to this platform?

/// <summary>
///
Calculation implementation.
/// </summary>
public class CalculatorService : ICalculator
{
/// <summary>
///
Adds the specified arguments.
/// </summary>
/// <param name="a">
A.</param>
/// <param name="b">
The b.</param>
/// <returns></returns>
public int Add(int a, int b)
{
Console.WriteLine("Add called...");
//check not negative (to show exception)
if(a < 0)
throw new FaultException<CalculationFaultContract>(
new CalculationFaultContract(){ArgA = a, ArgB = b}, "Argument is negative");

//normal operation
return a + b;
}

Also note, that the message set in the fault (“Argument is negative”) is a so-called FaultReason. Again this is an abstraction that makes the WCF/WS-paradigm operational cross-platform. It is not tied to a specific platform.


Client Side
Finally, on the Client side you surround the proxy-call to the CalculatorService in a try/catch structure of the proper kind. You need to anticipate FaultException<T> here, to be able to receive the proper values from the Server side.

class Program
{
static void Main(string[] args)
{
Console.WriteLine("Starting CLIENT...");
Console.WriteLine("Press key to call..");
Console.Read();

var fac = new ChannelFactory<ICalculator>(
new NetTcpBinding(),
"net.tcp://localhost:9000/CalculatorService");

var proxy = fac.CreateChannel();
using (proxy as IDisposable)
{
try
{
//this SHOULD throw errors as 'a' < 0
proxy.Add(-3, 5);

}
catch (FaultException<CalculationFaultContract> ex)
{
var msg = string.Format("Caught the RIGHT exception. ArgA={0}, ArgB={1}, Error={2}",
ex.Detail.ArgA,
ex.Detail.ArgB,
ex.Reason);

Console.WriteLine(msg);
}

catch (CommunicationException ex)
{
Console.WriteLine("Caught the WRONG exception.");
}
}

Console.Write("Done");
Console.Read();
}
}

This is basically all there is to communicating your custom data to the calling Client from the server side. On the side note, you maybe can see that this allows you do declare a complete structure of appropriate exception types in your system to very precisely communicate the reason of error from the Server to the calling Client.


It is actually not all that difficult ;-)



Technorati Tags:

No comments:

InRiver: Not loading your extensions?

(You really need to in the loop to appreciate the issue this post addresses). Man, I've been fighting this problem for hours before I ...