You know that any
IDisposable
object must be disposed using using. So, you have been using using to wrap WCF service’s
ChannelFactory
and Clients like this:
using(var client = new SomeClient()) {
.
.
.
}
Or, if you are doing it the hard and slow way (without really knowing why), then:
using(var factory = new ChannelFactory<ISomeService>()) {
var channel= factory.CreateChannel();
.
.
.
}
That’s what we have all learnt in school right? We have learnt it wrong!
When there’s a network related error or the connection is broken, or the call is timed out before
Dispose
is called by the using keyword, then it results in the following exception when the using keyword tries to dispose the channel:
failed: System.ServiceModel.CommunicationObjectFaultedException :
The communication object, System.ServiceModel.Channels.ServiceChannel,
cannot be used for communication because it is in the Faulted state.
Server stack trace:
at System.ServiceModel.Channels.CommunicationObject.Close(TimeSpan timeout)
Exception rethrown at [0]:
at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
at System.ServiceModel.ICommunicationObject.Close(TimeSpan timeout)
at System.ServiceModel.ClientBase`1.System.ServiceModel.ICommunicationObject.Close(TimeSpan timeout)
at System.ServiceModel.ClientBase`1.Close()
at System.ServiceModel.ClientBase`1.System.IDisposable.Dispose()
There are various reasons for which the underlying connection can be at broken state before the using block is completed and the
.Dispose()
is called. Common problems like network connection dropping, IIS doing an app pool recycle at that moment, some proxy sitting between you and the service dropping the connection for various reasons and so on. The point is, it might seem like a corner case, but it’s a likely corner case. If you are building a highly available client, you need to treat this properly before you go-live.
So, do NOT use
using
on WCF
Channel/Client/ChannelFactory
. Instead you need to use an alternative. Here’s what you can do:
First create an extension method.
public static class WcfExtensions
{
public static void Using<T>(this T client, Action<T> work)
where T : ICommunicationObject
{
try
{
work(client);
client.Close();
}
catch (CommunicationException e)
{
client.Abort();
}
catch (TimeoutException e)
{
client.Abort();
}
catch (Exception e)
{
client.Abort();
throw;
}
}
}
Then use this instead of the using keyword:
new SomeClient().Using(channel => {
channel.Login(username, password);
});
Or if you are using
ChannelFactory
then:
new ChannelFactory<ISomeService>().Using(channel => {
channel.Login(username, password);
});
Enjoy!