I have spent a considerable amount of time on development in a service-oriented architecture. On the Microsoft platform, the technology of choice is WCF (Windows Communication Foundation). Generally, it is a great technology, but it does have its nuisances. One of them is the way certain error conditions are handled when consuming web services.
The problem has been blogged about fairly extensively already, so if you know what I'm talking about, you'll probably want to skip to the Solutions bit.
The problem
Consuming a service, in it's simplest form, is incredibly easy in WCF. You add a service reference to your Visual Studio project, the tooling generates a number of proxy classes and off you go. All the operations the service offers are exposed as methods on the proxy class, there for you to use without worrying about complicated communication protocols. But wait, in the background there is a lot of plumbing being done. Being a smart developer, you have probably realised that there are most likely some resources involved, resources that need to be cleaned up using the IDisposable interface. You also realise that there are probably a number of things that can go wrong while communicating with the service, so let's add some exception handling as well. In WCF, all exceptions that might occur during communication derive from the aptly named CommunicationException.
try
{
using (ServiceClient client = new ServiceClient())
{
client.SomeOperation();
client.Close();
}
}
catch (CommunicationException ex)
{
Console.WriteLine("Error calling the service:\r\n" + ex);
}
Looks good, right? Not quite. Follwing the happy path, this example will function perfectly. When an exception does occur, it will appear to be caught as you might expect. Let's break communication with the service by breaking the endpoint configuration and see what happens:
Error calling the service:
System.ServiceModel.CommunicationObjectFaultedException: The communication object, System.ServiceModel.Channels.ServiceChannel, cannot be used for communication because it is in the Faulted state.
This exception text is not nearly as informative as you would have hoped. What's going on?
Analysis
If you run the above example in the debugger, you will notice there are actually two exceptions occurring, not one.
A first chance exception of type 'System.ServiceModel.EndpointNotFoundException' occurred in mscorlib.dll
A first chance exception of type 'System.ServiceModel.CommunicationObjectFaultedException' occurred in mscorlib.dll
It sounds like we'd be more interested in this first exception that's occurring, and not the one we have just logged to the screen. Apparently, an exception is being thrown as a result of us handling the initial exception.
To understand the problem, you need to be familiar with a WCF concept called channel faulting; WCF tries to enforce best practise exception handling this way. When an exception occurs1, our proxy will be put in a faulted state. This faulted states prevents the proxy instance from being used further, which would allow us to catch any exceptions and continue as if nothing bad had ever happened. Any attempt to use the proxy after the initial exception will result in a CommunicationObjectFaultedException.
You may be wondering how this is related to our problem, because we're only making one call to our proxy. When our EndpointNotFoundException occurs, the runtime will jump to the appropriate exception handler. To do so, it will leave the scope of the using statement, which will cause the Dispose method to be called. Dispose is responsible for cleaning up resources, like closing the communications channel. It will call the Close method on our generated proxy, but unfortunately that is not allowed if the channel is in a faulted state. In such a case, you're supposed to call the Abort method instead.
Okay, so after wondering what the WCF team was smoking when they implemented IDisposable like this, we cut our losses. We'll just close the channel without using the IDisposable interface. Let's see how that will look if you add some more detailed exception handling:
ServiceClient client;
try
{
client = new ServiceClient();
client.SomeOperation();
client.Close();
}
catch (EndpointNotFoundException ex)
{
// ...
client.Abort();
}
catch (CommunicationException ex)
{
// ...
client.Abort();
}
catch (Exception ex)
{
client.Abort();
throw;
}
Our code just got a whole lot uglier and more prone to coding errors, such as forgetting to free up the resources altogether. There are a few other ways of going about this (like try-finally), but most of them are still pretty darned ugly.
Solutions
Several solutions have been proposed, for example on iServiceOriented.com. This particular solution uses a delegate in the form of a lambda expression, to emulate the behavior of IDisposable in the way it should have worked for WCF:
Service<IOrderService>.Use(orderService =>
{
orderService.PlaceOrder(request);
});
This is a pretty decent solution, but I'm not entirely satisfied with the required syntax. Indeed, the original article is actually missing the ); bit in the end. Also, this approach might confuse people that aren't fully versed in the concept of closures.
Another solution is to use a partial class to fix our ServiceClient implementation, by providing it with a custom Dispose method. This works really well and fully transparent, but only if you're actually using a service reference. If you're using a ChannelFactory to construct your proxy, you can't use this approach. A partial solution is really no solution at all in this case, so let's move on.
My favorite solution looks something like this:
// Channel is a custom written class; code follows
using (Channel.AsDisposable(client))
{
client.SomeOperation();
}
The advantage of this approach is that the using statement is back. While this Channel.AsDisposable business may not be immediately apparent to someone browsing my code, they will probably guess it has something to do with cleaning up resources properly. We don't need any fancy stuff like lambda expressions to make it work, and we aren't going to forget about that nasty ); bit at the end of our screen sized code blocks.
On the off chance that you were planning on using a yield return with your service call, you can even do that. yield doesn't work with delegates, which makes sense if you think about it. I'll admit it's a far fetched reasoning for using this technique, but I hope you get my point: why try to be clever, when there is a perfectly good simple solution?
Here's the full implementation of the custom Channel class:
public class Channel : IDisposable
{
private ICommunicationObject _channel;
private Channel(ICommunicationObject channel)
{
_channel = channel;
}
public static IDisposable AsDisposable(object client)
{
return new Channel((ICommunicationObject)client);
}
public void Dispose()
{
bool success = false;
try
{
if (_channel.State != CommunicationState.Faulted)
{
_channel.Close();
success = true;
}
}
finally
{
if (!success)
_channel.Abort();
}
}
}
The AsDisposable method takes a parameter of the type object, to allow interface types to be used without any ugly casting operations. Here's a full example using a ChannelFactory:
var factory = new ChannelFactory<IService>("EndpointName");
try
{
IService client = factory.CreateChannel();
using (Channel.AsDisposable(client))
{
client.SomeOperation();
}
}
catch (CommunicationException ex)
{
Console.WriteLine("Error calling the service:\r\n" + ex);
}
And the printed exception text:
Error calling the service:
System.ServiceModel.EndpointNotFoundException: There was no endpoint listening at http://localhost:8731/ServiceLibrary/Service1/ that could accept the message. This is often caused by an incorrect address or SOAP action. See InnerException, if present, for more details. ---> System.Net.WebException: The remote server returned an error: (404) Not Found.
That's going to look a lot more useful in your log file.
Notes
- In case you were wondering: in terms of overhead, my wrapped ICommunicationObject approach is about twice as fast compared to using a delegate. That wasn't my primary concern however; we're talking about 30 microseconds per service call on my machine.
- Interesting read on Stack Overflow: What is the best workaround for the WCF client `using` block issue?
1: FaultException does not cause the channel to be put into a faulted state.
Files: