Indisposable: WCF Gotcha #1
How the WCF team threw IDisposable out the window and what you can do about it
So you're ready to start building WCF services. You design your interface, which looks a little something like this:
public interface IOrderServiceService
{
void PlaceOrder(PlaceOrderRequest request);
}
When you have the code behind the service ready to go, you deploy it to the server of your choice and are ready to call the service. At this point, you will most likely do one of two things to consume the service: Create a ChannelFactory
and reuse your interface, or use the "Add Service Reference..." option inside Visual Studio. Either way you go, you will almost certainly get the next part wrong... even if you know your shit when it comes to the rest of the .NET
framework.
Using ChannelFactory you write:
using(IClientChannel clientChannel = (IClientChannel)channelFactory.CreateChannel())
{
IOrderService proxy = (IOrderService)clientChannel;
proxy.PlaceOrder(request);
}
using the "add service reference" method:
using(OrderServiceClient proxy = new OrderServiceClient())
{
proxy.PlaceOrder(request);
}
Hopefully, you were clued in enough to know you need to dispose these objects... but the problem is that both of the above code blocks will cause major problems in a production environment. The choice the WCF developers made here is
questionable at best and goes against everything you thought you knew about the IDisposable interface. Everywhere else in the .NET framework, you can count on Dispose cleaning up your object... not so in this case. The problem is that
Dispose actually calls Close, and if you are using something like the WsHttpBinding, Close doesn't just clean up resources managed by the object. Close actually needs to make a call to the server and tell the server to clean up your session before it shuts
down. When that call back to the server can't be made for any reason--which can happen quite often--Close fires an exception and the object is not disposed. There is another method, Abort, which you need to call to clean up the object's
state. The way you end up having to handle every call is quite messy (and yes, I do mean "have to"... unless you don't mind your service doing bad things... like refusing incoming connections):
OrderServiceClient proxy = null;
bool success = false;
try
{
proxy = new OrderServiceClient();
proxy.PlaceOrder(request);
proxy.Close();
success = true;
}
finally
{
if(!success)
{
proxy.Abort();
}
}
Wow... that's a lot of code to do something that you'd expect to happen automatically. If you are making a lot of calls, this can really muddy up your code. We really need to isolate this logic into something reusable. Using lambda
expressions and generics, we can make our code look nearly as clean as the first example. How about something like this:
Service<IOrderService>.Use(orderService=>
{
orderService.PlaceOrder(request);
}
And here's the class that does the magic:
public delegate void UseServiceDelegate<T>(T proxy);
public static class Service<T>
{
public static ChannelFactory<T> _channelFactory = new ChannelFactory<T>("");
public static void Use(UseServiceDelegate<T> codeBlock)
{
IClientChannel proxy = (IClientChannel)_channelFactory.CreateChannel();
bool success = false;
try
{
codeBlock((T)proxy);
proxy.Close();
success = true;
}
finally
{
if (!success)
{
proxy.Abort();
}
}
}
}