tags:

views:

1844

answers:

7

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 failure, as if the server won't respond. It would appear that bit of work happens somewhere in the resulting code from ChannelFactory, but I'm not sure. General code review is welcome also. :)

Bottom line, surrounding the creation of the channel, or the begin or async result delegate in try/catch doesn't trap a failed connection. I'd like to have that catch run the ServiceCallError event.

public class ServiceCaller : IDisposable
{
    private IFeedService _channel;

    public ServiceCaller()
    {
        var elements = new List<BindingElement>();
        elements.Add(new BinaryMessageEncodingBindingElement());
        elements.Add(new HttpTransportBindingElement());
        var binding = new CustomBinding(elements);
        var endpointAddress = new EndpointAddress(App.GetRootUrl() + "Feed.svc");
        _channel = new ChannelFactory<IFeedService>(binding, endpointAddress).CreateChannel();
    }

    public void MakeCall(DateTime lastTime, Dictionary<string, string> context)
    {
        AsyncCallback asyncCallBack = delegate(IAsyncResult result)
        {
            var items = ((IFeedService)result.AsyncState).EndGet(result);
            if (ItemsRetrieved != null)
                ItemsRetrieved(this, new ServiceCallerEventArgs(items));
        };
        _channel.BeginGet(lastTime, context, asyncCallBack, _channel);
    }

    public event ItemsRetrievedEventHandler ItemsRetrieved;
    public event ServiceCallErrorHandler ServiceCallError;

    public delegate void ItemsRetrievedEventHandler(object sender, ServiceCallerEventArgs e);

    public delegate void ServiceCallErrorHandler(object sender, ServiceCallErrorEventArgs e);

    public void Dispose()
    {
        _channel.Close();
        _channel.Dispose();
    }
}

Here's the stack trace, for those who are curious:

 An AsyncCallback threw an exception.
at System.ServiceModel.AsyncResult.Complete(Boolean completedSynchronously)
   at System.ServiceModel.AsyncResult.Complete(Boolean completedSynchronously, Exception exception)
   at System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpChannelAsyncRequest.OnGetResponse(IAsyncResult result)
   at System.Net.Browser.BrowserHttpWebRequest.<>c__DisplayClassd.<InvokeGetResponseCallback>b__b(Object state2)
   at System.Threading._ThreadPoolWaitCallback.WaitCallback_Context(Object state)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallbackInternal(_ThreadPoolWaitCallback tpWaitCallBack)
   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback(Object state)

To make this happen, I fire up the app in the browser, then I kill the Web server process from Visual Studio. In a test environment, I get the same thing by killing the network connection for the client system.

Here's the FULL exception's ToString():

System.Exception: An AsyncCallback threw an exception. ---> System.Exception: An AsyncCallback threw an exception. ---> System.ServiceModel.CommunicationException: The remote server returned an error: NotFound. ---> System.Net.WebException: The remote server returned an error: NotFound. ---> System.Net.WebException: The remote server returned an error: NotFound.
   at System.Net.Browser.BrowserHttpWebRequest.InternalEndGetResponse(IAsyncResult asyncResult)
   at System.Net.Browser.BrowserHttpWebRequest.<>c__DisplayClass5.<EndGetResponse>b__4(Object sendState)
   at System.Net.Browser.AsyncHelper.<>c__DisplayClass2.<BeginOnUI>b__0(Object sendState)
   --- End of inner exception stack trace ---
   at System.Net.Browser.AsyncHelper.BeginOnUI(SendOrPostCallback beginMethod, Object state)
   at System.Net.Browser.BrowserHttpWebRequest.EndGetResponse(IAsyncResult asyncResult)
   at System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpChannelAsyncRequest.CompleteGetResponse(IAsyncResult result)
   --- End of inner exception stack trace ---
   at System.ServiceModel.Channels.Remoting.RealProxy.Invoke(Object[] args)
   at proxy_2.EndGet(IAsyncResult )
   at CoasterBuzz.Feed.Client.ServiceCaller.<MakeCall>b__0(IAsyncResult result)
   at System.ServiceModel.AsyncResult.Complete(Boolean completedSynchronously)
   --- End of inner exception stack trace ---
   at System.ServiceModel.AsyncResult.Complete(Boolean completedSynchronously)
   at System.ServiceModel.AsyncResult.Complete(Boolean completedSynchronously, Exception exception)
   at System.ServiceModel.Channels.ServiceChannel.SendAsyncResult.CallComplete(Boolean completedSynchronously, Exception exception)
   at System.ServiceModel.Channels.ServiceChannel.SendAsyncResult.FinishSend(IAsyncResult result, Boolean completedSynchronously)
   at System.ServiceModel.Channels.ServiceChannel.SendAsyncResult.SendCallback(IAsyncResult result)
   at System.ServiceModel.AsyncResult.Complete(Boolean completedSynchronously)
   --- End of inner exception stack trace ---
   at System.ServiceModel.AsyncResult.Complete(Boolean completedSynchronously)
   at System.ServiceModel.AsyncResult.Complete(Boolean completedSynchronously, Exception exception)
   at System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpChannelAsyncRequest.OnGetResponse(IAsyncResult result)
   at System.Net.Browser.BrowserHttpWebRequest.<>c__DisplayClassd.<InvokeGetResponseCallback>b__b(Object state2)
   at System.Threading._ThreadPoolWaitCallback.WaitCallback_Context(Object state)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallbackInternal(_ThreadPoolWaitCallback tpWaitCallBack)
   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback(Object state)

By the way, the only way I can catch this at all is to use app level UnhandledException event in the SL client.

A: 

To catch communications errors with the server, I'd suggest putting the call to .CreateChannel() into a try...catch and handle things like CommunicationException and TimeoutExceptions there.

Also, one nuggest of general advice: creating the ChannelFactory is quite an expensive operation, so you might want to do this separately and store that reference to the ChannelFactory object somewhere.

If you need to create and possibly re-create the channel from the ChannelFactory, you can then use that stored / cached instance of the ChannelFactory over and over again without having to create that all the time.

But other than that, I would think you're doing quite fine here!

Marc

marc_s
As I mentioned, that doesn't work. When I cut off the server, the client responds with "an AsyncCallback threw an exception," and the stack trace doesn't include any part of my code.
Jeff Putz
What if you put your code in the async callback method (I see you're using an anonymous delegate for that) into a try....catch block?? Usually, on an async operation, if the server threw an exception, you'll get this at the client when you call the `.EndXXXXX()` method.
marc_s
Nope... still won't catch the exception. Weird, eh?
Jeff Putz
+5  A: 

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<BindingElement>();
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<IFeedService>("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<string, string> 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

Jerry Bullard
Nope, as I mentioned, I tried to trap the error in all of the touch points, the creation of the channel, the begin call and the end call. It makes sense that the trapping wouldn't work, because the begin and end happen in their own thread, right? That code, which is awfully black-box in appearance to me within the framework, is where it happens.
Jeff Putz
The stack trace indicates the exception was thrown by an async callback. This is likely being thrown inside your declared delegate at the EndGet() method call.Please try again to place a try/catch block around the contents of your AsyncCallback (inside, not around the outside where it is declared). Perhaps you have already tried this, but please post that code to confirm.If you don't get an exception at the EndGet() call, please set a breakpoint on the line starting with "var items". Then add this watch:((System.ServiceModel.AsyncResult)(result)).exceptionThis should not be null.
Jerry Bullard
Tried it before this. As I said originally, I've tried to trap this in every single point in this class, and none of them catch the exception. The only place I've been able to get it is in SL's app-level UnhandledException event.
Jeff Putz
Please post the code with the additional try/catch I suggested and please set a breakpoint on this line of code:var items = ((IFeedService)result.AsyncState).EndGet(result);Then before this line executes, please let us know the value of this:((System.ServiceModel.AsyncResult)(result)).exception
Jerry Bullard
A: 

Your ChannelFactory instance is going to be in the "Aborted" state when this timeout occurs. You'll find that the exception is being thrown not when the exception happens in the call, but when you call _channel.Dispose().

You'll need to guard against this exception somehow. The simplest way would be to put a try/catch around the contents of your dispose method, but it would be better if you checked the state of the _channel instance before trying to close/dispose.

This is why you are still getting the exception - it's not being handled here.

Update:

Based on your stack trace, it looks like the framework is trying to execute your callback delegate and is getting an exception there. You responded elsewhere that try/catching the contents of your delegate doesn't solve the issue... what about inner exceptions? What is the full output of TheException.ToString()?

I trolled through the .NET Framework and it is indeed allowed to travel all the way back to the threadpool (at least in the stacktrace you have here) which would cause an unhandled exception and your thread to die. If worse comes to worse, you can always take over the threading... spin up your own thread and inside call the service synchronously, rather than using the builtin async pattern (unless this is a WCF Duplex type of communication, which I don't think it is).

I still think the full exception information (if there is anything more) might be useful.

Anderson Imes
Nope... no joy. Like I said, nothing in the stack trace pertains to my code at all.
Jeff Putz
It wouldn't show anything from your stack trace. Try putting a try/catch around your _channel.Dispose() and _channel.Close() and see what happens.
Anderson Imes
Also, post that stack trace, eh? Could have a good clue.
Anderson Imes
As I said (no joy), adding the try/catch there doesn't work. I edited the original above to show the trace.
Jeff Putz
Nevermind, I see that you tried that already... I've updated my answer with a request of the full output of TheException.ToString() (shows all inner exceptions as well).
Anderson Imes
Inner exception added to main body of this post.
Jeff Putz
A: 

Jeff, please show us your code when you try to put a try/catch block around the EndGet call. It's hard to believe that doesn't catch an exception, and seeing it will help me believe.

Also, as part of this experiment, get rid of the UnhandledException event handler. I think you're catching this exception as soon as the BrowserHttpRequest realizes there's a 404. I think that without the UnhandledException event, a try/catch around EndGet will catch the exception, if any. I think you're picking up an intermediate state of the exception handling process.

I'd also like to see you put a System.Debugger.Break or something immediately after the EndGet call.

John Saunders
A: 

Am I right in thinking that you have designed your ServiceCaller class so that you can call it in a using block?

If yes, then that's the problem. The WCF channel classes have been designed in such a way as to make it extremely difficult to write completely general disposing code that caters for all possible error conditions. If it were simple, then the channel classes themselves would be safe to dispose and you wouldn't need a wrapper class.

The only safe way I have found to date is to encapsulate not the dispose logic, but the entire call sequence, thus:

public static void Invoke<TContract>(ChannelFactory<TContract> factory, Action<TContract> action) where TContract : class {

    var proxy = (IClientChannel) factory.CreateChannel();
    bool success = false;
    try {
        action((TContract) proxy);
        proxy.Close();
        success = true;
    } finally {
        if(!success) {
            proxy.Abort();
        }
    }
}

I know that's a synchronous call, but the principle is the same. Instead of you trying to deduce whether you should call Close or Abort, you determine it in advance based on how far through the call you managed to get before it fell over.

Note - only the actual channel needs this special treatment. You can dispose of the factory any way you like AFAIK.

Christian Hayter
Making synchronous calls in a desktop UI environment is not a good idea.
Jeff Putz
That decision is up to you, I was only trying to illustrate how you should track the exact method of disposing the channel. For your asynch class, you could add a class-level state field containing states like Initialized, Opened, Closed, etc. Your Dispose method would then call either Abort, Close or nothing at all depending on the value of your state field.
Christian Hayter
I agree with your statement about synchronous calls in a desktop UI environment is a bad idea, but you certainly don't have to use the async pattern provided by SVCUTIL. You could just as easily make a syncronous call from a background thread (using BackgroundWorker or similar approach). This would avoid tying up the UI thread and still get you what you want - the ability to catch these exceptions gracefully.
Anderson Imes
+2  A: 

I've encountered a similar problem before. The main problem is with the way IDisposable is implemented on instances of your proxy/channel. The way I've solved it is shown in the code below, where IDirector is my service contract:

public class ProxyWrapper : IDisposable
{
 private IDirector proxy;
 private ChannelFactory<IDirector> factory;
 int callCount = 0;

 public ProxyWrapper()
 {
  factory = new ChannelFactory<IDirector>();

  proxy = factory.CreateChannel();
 }

 public IDirector Proxy
 {
  get
  {
   if (callCount > 0)
    throw new InvalidOperationException("The proxy can only be accessed once for every ProxyWrapper instance. You must create a new ProxyWrapper instance for each service request.");
   // only allow the proxy/channel to be used for one call.

   callCount++;
   return proxy;
  }
 }

 public void Dispose()
 {
  IClientChannel channel = (IClientChannel)proxy;

  try
  {
   if (channel.State != CommunicationState.Faulted)
   {
    channel.Close();
   }
   else
   {
    channel.Abort();
   }
  }
  catch (CommunicationException)
  {
   channel.Abort();
  }
  catch (TimeoutException)
  {
   channel.Abort();
  }
  catch (Exception)
  {
   channel.Abort();
   throw;
  }
  finally
  {
   channel = null;
   proxy = null;
  }
 }
}

The way to use the above class is as follows:

    public static void Login(string userName, string password)
    {
  using (ProxyWrapper wrapper = new ProxyWrapper())
  {
   currentSession = wrapper.Proxy.Login(userName, password);
  }
    }

Because the ProxyWrapper class implements IDisposable, if we use an instance of the ProxyWrapper class inside a using block, the Dispose() method is guaranteed to be called, even if an exception is thrown. The code in the Dispose() method will handle all cases and states of the proxy/channel. You can then add your error-handling / logging / event delegate code in this method.

Read the following blog entry for more info, and for a more generic version of the code above: http://bloggingabout.net/blogs/erwyn/archive/2006/12/09/WCF-Service-Proxy-Helper.aspx

Saajid Ismail
A: 

I think that the problem is in the way you have designed service caller.

The channel is opened when service caller is created. This means that the channel can timeout and there is nothing in your code that can recover from the timeount.

I would move the creation and closing of the channel to the make call method.

You can do a try catch around the begin get and around the contents of the callback.

Shiraz Bhaiji