I\'m trying to write a class that encapsulates WCF calls (the client is Silverlight, if it matters). It all works swimmingly, but I\'m not sure how to trap a connection fail
Jeff, I'm not entirely certain from your question how you are simulating a connection failure. I am assuming that "as if the server won't respond" means you are looking for some kind of timeout error. You are probably simulating a non-responsive server by forcing a slow response from your service call using Thread.Sleep() on the server side. Right? Close enough?
I had a similar project, so I tested this out and was able to trap a timeout and other exceptions successfully. I think perhaps you weren't seeing these exceptions because your custom binding had a very long default timeout and you may not have waited long enough.
To explain, WCF timeouts are controlled by the binding configuration. Looking at your code, you are creating your own custom binding like this:
var elements = new List();
elements.Add(new BinaryMessageEncodingBindingElement());
elements.Add(new HttpTransportBindingElement());
var binding = new CustomBinding(elements);
If you set a breakpoint there and inspect the custom binding you created, you will see it has a one minute SendTimeout by default. I suspect you were either not waiting long enough or not setting your service's simulated timeout long enough, so you were not able to catch a timeout exception.
Here are some code changes to demonstrate. First, to set the timeout on the binding:
var binding = new CustomBinding(elements);
//Set the timeout to something reasonable for your service
//This will fail very quickly
binding.SendTimeout = TimeSpan.FromSeconds(1);
Finally, to catch the exceptions and raise the correct events, you can update your AsyncCallback delegate as follows. The exception is thrown when EndGet() is called.
AsyncCallback asyncCallBack = delegate(IAsyncResult result)
{
IFeedService service = ((IFeedService)result.AsyncState);
try
{
var items = service.EndGet(result);
ItemsRetrieved(this, EventArgs.Empty);
}
catch (System.TimeoutException ex)
{
//Handles timeout
ServiceCallError(this, EventArgs.Empty);
}
catch (System.ServiceModel.CommunicationException ex)
{
//Handles a number of failures:
// Lack of cross-domain policy on target site
// Exception thrown by a service
ServiceCallError(this, EventArgs.Empty);
}
catch (System.Exception ex)
{
//Handles any other errors here
ServiceCallError(this, EventArgs.Empty);
}
};
As far as a general code review, I would recommend that you avoid hard-coding your binding configuration. You can instead declare your endpoint configuration (including reasonable timeouts) in your ServiceReferences.ClientConfig file and new up your channel factory with the endpoint name like this:
new ChannelFactory("feedServiceEndpoint");
That should make the app more maintainable over time.
I hope this helps.
Jerry
Updated:
Jeff,
Please take a look at this code and the comments within for more explanation of what is happening here. The MakeCall() method is creating a function (delegate) that is being passed to the BeginGet() method. That function is later executed when the service responds.
Please make the changes suggested and set breakpoints at the lines starting with "AsyncCallback asyncCallback" and "var items". You will see the first pass through the debugger merely declares the code (function/delegate) to execute when the service responds and the second pass is the actual processing of that response, starting at the inside of your delegate declaration. This means the outer try/catch will not be in scope when the response of the service call is processed.
public void MakeCall(DateTime lastTime, Dictionary context)
{
try
{
AsyncCallback asyncCallBack = delegate(IAsyncResult result)
{
try
{
var items = ((IFeedService)result.AsyncState).EndGet(result);
if (ItemsRetrieved != null)
ItemsRetrieved(this, new ServiceCallerEventArgs(items));
}
catch (Exception ex)
{
//This will catch errors from the service call
}
};
_channel.BeginGet(lastTime, context, asyncCallBack, _channel);
}
catch(Exception ex)
{
//This will not catch an error coming back from the service. It will
//catch only errors in the act of calling the service asynchronously.
//The communication with the service and response is handled on a different
//thread, so this try/catch will be out of scope when that executes.
//So, at this point in the execution, you have declared a delegate
//(actually an anonymous delegate the compiler will turn into a hidden class)
//which describes some code that will be executed when the service responds.
//You can see this in action by setting a breakpoint just inside the delegate
//at the line starting with "var items". It will not be hit until the service
// responds (or doesn't respond in a timeout situation).
}
}
Jerry