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; }
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 }
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); } } }
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); }
No comments:
Post a Comment