When you use WCF to consume web-services, you typically add a service reference to the web-service, which creates a client class that you use to work with the web-service. The web-service client class is created by deriving from the ClientBase class. If you take a look at ClientBase you will find that it implements IDisposable.
Now if you are familiar with IDisposable, you would think that you should be using the “using” block to ensure that the WCF client gets correctly disposed. (MSDN:
using statement)
As a rule, when you use an IDisposable object, you should declare and instantiate it in a using statement. The using statement calls the Dispose method on the object in the correct way, and (when you use it as shown earlier) it also causes the object itself to go out of scope as soon as Dispose is called. Within the using block, the object is read-only and cannot be modified or reassigned. – extracted 08-18-2010.
But unfortunately, that is
not true when working with WCF. (MSDN:
Avoiding Problems with the Using Statement). The problem is that when dispose is called on a WCF client it can throw an exception, and that might leave the connection open. (One scenario where this can manifest itself is with a WS connection – wsHttpBinding -, where the client needs to connect to the server when it is trying to close the connection. In this case, an error can occur upon the call to Close and one might have to call Abort to ensure that the connection is closed).
According to the MSDN article, this is how one should close a WCF client connection.
try
{
...
client.Close();
}
catch (CommunicationException e)
{
...
client.Abort();
}
catch (TimeoutException e)
{
...
client.Abort();
}
catch (Exception e)
{
...
client.Abort();
throw;
}
The only
problem with the above code is that if you have to do this every-time you need to call a web-service operation, it will lead to really ugly code. Further more,
I like the using statement, because I can
group a set of web-service operation calls and call them all together within the using statement, thereby having to open the connection and close it only once – making for more
efficient code. (This can be important if you typically need to make many calls to the web-service, where if you had to open and close the connection each time – you would notice a performance degradation). And it centralizes the code for consuming web-services correctly.
So here is my implementation. It’s a class the implements IDisposable and correctly implements the Dispose method – so that it does not throw an exception at the end of the using block and it closes the connection even if an exception is thrown on a Close call (through the Abort call).
Here is the code:
using System;
using System.ServiceModel;
public class ServiceClient<TI, TClient> : IDisposable
where TI : class
where TClient : ClientBase<TI>, TI, new()
{
private TClient _client;
private bool _objectHasBeenDisposed = false;
/// <summary>
///
/// </summary>
/// <param name="tclient">WCF Proxy Client</param>
public ServiceClient(TClient tclient)
{
_client = tclient;
}
/// <summary>
///Provides access to the underlying connection object
/// </summary>
protected TI Client
{
get
{
return _client;
}
}
/// <summary>
///
/// </summary>
/// <typeparam name="TIn"></typeparam>
/// <param name="action"></param>
/// <param name="argument"></param>
protected void Invoke<TIn>(Action<TIn> action, TIn argument)
{
bool callWasSuccessful = false;
try
{
action(argument);
callWasSuccessful = true;
}
catch (CommunicationException)
{
CloseConnection(true);
}
catch (TimeoutException)
{
CloseConnection(true);
}
finally
{
//shutdown the connection in case of an error.
//also by disposing it - subsequent calls on this object will also fails
if (!callWasSuccessful)
CloseConnection(true);
}
}
/// <summary>
///
/// </summary>
/// <typeparam name="TIn"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <param name="func"></param>
/// <param name="argument"></param>
/// <returns></returns>
protected TResult Invoke<TIn, TResult>(Func<TIn, TResult> func, TIn argument)
{
TResult returnVal = default(TResult);
bool callWasSuccessful = false;
try
{
returnVal = func(argument);
callWasSuccessful = true;
}
catch (CommunicationException)
{
}
catch (TimeoutException)
{
}
finally
{
//shutdown the connection in case of an error.
//also by disposing it - subsequent calls on this object will also fails
if (!callWasSuccessful)
CloseConnection(true);
}
return returnVal;
}
/// <summary>
///
/// </summary>
/// <typeparam name="TIn1"></typeparam>
/// <typeparam name="TIn2"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <param name="func"></param>
/// <param name="argument1"></param>
/// <param name="argument2"></param>
/// <returns></returns>
protected TResult Invoke<TIn1, TIn2, TResult>(Func<TIn1, TIn2, TResult> func, TIn1 argument1, TIn2 argument2)
{
TResult returnVal = default(TResult);
bool callWasSuccessful = false;
try
{
returnVal = func(argument1, argument2);
callWasSuccessful = true;
}
catch (CommunicationException)
{
}
catch (TimeoutException)
{
}
finally
{
//shutdown the connection in case of an error.
//also by disposing it - subsequent calls on this object will also fails
if (!callWasSuccessful)
CloseConnection(true);
}
return returnVal;
}
/// <summary>
///
/// </summary>
public void Close()
{
CloseConnection(false);
}
#region private members
protected void CloseConnection(bool disposeObject)
{
if (_objectHasBeenDisposed) return;
if (disposeObject)
_objectHasBeenDisposed = true;
try
{
if (_client != null)
{
if (_client.State != CommunicationState.Faulted)
_client.Close();
else
_client.Abort();
}
}
catch (CommunicationException)
{
}
catch (TimeoutException)
{
}
catch (Exception)
{
throw;
}
finally
{
if (_client != null)
_client.Abort();
}
_client = null;
}
#endregion
#region IDisposable Members
/// <summary>
///
/// </summary>
public void Dispose()
{
try
{
CloseConnection(true);
_objectHasBeenDisposed = true;
}
catch //Dispose should swallow all errors.
{
}
}
#endregion
}
And here is how you use the class. (Assuming the web-service client implementation class is called “CalculatorClient” and the interface class that it implements is called “ICalculator”)
public class myClient : ServiceClient<ICalculator, CalculatorClient>
{
public static void Demonstrate()
{
using (myClient client = new myClient())
{
// Demonstrate a successful client call.
Console.WriteLine("Calling client.Add(4.0, 5.0);");
double addValue = client.Invoke<double, double, double>(client.Client.Add, 4.0, 5.0);
Console.WriteLine("client.Add(4.0, 5.0); returned {0}", addValue);
}
}
}
Here is what you need to observe:
1. The Invoke method is overloaded to call different types of methods with different number of arguments (0,1 and 2). If you need more arguments – then overload it with other Func types.
2. The invoke method uses the correct pattern to call the WCF operation and hence makes sure that the connection is cleaned up correctly even if an exception occurs.
3. To test the above code here is what you need to do:
- Create a wcf web-service with an interface called ICalculator with a method “double Add(double d1, double d2)”.
- make the wcf web-service use a wsHttpBinding. (connection problems are not easy to recreate with basicHttpBinding).
- in the Add method call the following method: “OperationContext.Current.Channel.Abort();” – this will cause a fault when the Add method is called from the client.
- inside the using block shown above, throw an exception right before the end of the using block. a try catch block should catch the exception outside the using block. (code is shown below).
try
{
// Create a new client.
using (myClient client = new myClient())
{
Console.WriteLine("Calling client.Add(3.0, 2.0);");
double d = client.Invoke<double, double, double>(client.Client.Add, 3.0, 2.0);
Console.WriteLine(d);
throw new OperationCanceledException("Normally if there was an exception when you call Add, this exception will be swallowed by the end of the using statement");
}
}
catch (OperationCanceledException operationCanceledException)
{
Console.WriteLine(operationCanceledException);
Console.WriteLine(operationCanceledException.Message);
}