11

I'm consuming a clunky WCF server that occasionally throws various exceptions, and additionally returns some of its errors as string. I have no access to the server code at all.

I want to override the inner WCF-client request invocation method and handle all inner exceptions and hard-coded errors returned by the server and raise the Fault event if an error occurs, pseudo:

class MyClient : MyServiceSoapClient
{
    protected override OnInvoke()
    {
        object result;
        try
        {
            result = base.OnInvoke();
            if(result == "Error")
            {
                //raise fault event
            }
        catch
        {
            //raise fault event
        }
    }        
}

So that when I call myClient.GetHelloWorld(), it goes thru my overridden method.

How can this be achieved?
I know I don't have to use the generated client, but I don't want to re-implement all the contracts again, and I want to use the generated ClientBase subclass or at least its channel.
What I need is control over the inner request call method.

Update

I read this answer, and looks it's partially what I'm looking for, but I'm wondering if there is a way to attach an IErrorHandler to the consumer (client) code only, I want to add it to the ClientBase<TChannel> instance somehow.

Update

This article also looks very promising but it doesn't work. The applied attribute doesn't seem to take effect. I can't find a way to add IServiceBehavior to the client side.

Update

I tried attaching an IErrorHandler via IEndpointBehavior.ApplyClientBehavior calling:

public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
  clientRuntime.CallbackDispatchRuntime.ChannelDispatcher.ErrorHandlers
           .Add(new ErrorHandler());
}

(clientRuntime is a parameter), but exceptions are still thrown directly skipping MyErrorHandler.
ApplyDispatchBehavior isn't called at all.

Conclusion

I need to achieve two aspects:

  1. Wrap all exceptions that might occur during the lifetime of a BaseClient<TChannel> and decide whether to handle them or throw them on. This should take care of all operation (the service I'm consuming exposes few dozens)
  2. Parse all server-replies and throw exceptions for some of them, so they're forwarded as in statement 1.
Community
  • 1
  • 1
Shimmy Weitzhandler
  • 101,809
  • 122
  • 424
  • 632
  • Do you have any access to the app.config or web.config for the service? – David P Nov 17 '15 at 13:44
  • @DavidP I have no access to the server code whatsoever. Besides, I prefer to set up my configuration programmatically rather than using app settings or app config. – Shimmy Weitzhandler Nov 17 '15 at 13:47
  • Ok - I think I understand what you are trying to do now. Is the service returning an exception, or is returning successfully with a string message? – David P Nov 17 '15 at 16:04
  • @DavidP both. I need to handle both situations, and additionally I also want to handle server-down or other general exceptions, all in the same place. – Shimmy Weitzhandler Nov 18 '15 at 08:55
  • The IOperationInvoker article is the right way to solve your problem. You can use it on the client side. Don't add it as an IServiceBehavior on the client side. Add it as an IOperationBehavior or IEndpointBehavior. – ErnieL Nov 25 '15 at 02:27
  • @ErnieL `IOperationInvoker` can only be attached via `ApplyDispatchBehavior`, it's not a client-side facility. – Shimmy Weitzhandler Nov 25 '15 at 03:49

3 Answers3

6

You could use and modify the Exception Handling WCF Proxy Generator, more specifically, the base class that it uses. It's basic idea (check this description too) is to provide connection resilience by catching connection faults, and retrying the failed operation. As you can imagine, for this purpose it needs to be able to catch thrown exceptions, and also, it can inspect the result of calls.

The main functionality is given by the ExceptionHandlingProxyBase<T> base class, which you use instead of the ClientBase<T>. This base class has an Invoke method as follows, you'd need to modify that.

Simplified Invoke:

protected TResult Invoke<TResult>(string operationName, params object[] parameters)                              
{                                                        
  this.Open();                              
  MethodInfo methodInfo = GetMethod(operationName);                              
  TResult result = default(TResult);                              
  try                              
  {                              
    this.m_proxyRecreationLock.WaitOne(this.m_proxyRecreationLockWait); 
    result = (TResult)methodInfo.Invoke(m_channel, parameters);                              
  }                              
  catch (TargetInvocationException targetEx) // Invoke() always throws this type                              
  {                              
    CommunicationException commEx = targetEx.InnerException as CommunicationException;                              
    if (commEx == null)                              
    {                              
      throw targetEx.InnerException; // not a communication exception, throw it                              
    }                              
    FaultException faultEx = commEx as FaultException;                              
    if (faultEx != null)                              
    {                              
      throw targetEx.InnerException; // the service threw a fault, throw it                              
    }                              

    //... Retry logic

  }
  return result;
}  

You'll need to modify the throw targetEx.InnerException; part to handle the exceptions as you need, and obviously the resturn value shoudl also be inspected for your needs. Other then that you can leave the retry logic or throw it away if you don't expect connection problems. There is another variant of the Invoke for void return methods.

Oh, and by the way, it works with duplex channels as well, there is another base class for those.

If you don't want to use the generator (it might not even work in newer versions of VS), then you could just take the base class for example from here, and generate the actual implementation class with T4 from your service interface.

Tamas
  • 6,260
  • 19
  • 30
  • Thanks for your effort. `BaseClient.Invoke` is not virtual. Should have been and my question wouldn't be here. – Shimmy Weitzhandler Nov 24 '15 at 13:09
  • I know it's not `virtual`, that's why I'm suggesting using `ExceptionHandlingProxyBase` instead of `ClientBase` as your client proxy base class. And your actual implementation class can be generated by the generator, or you can generate it yourself.) – Tamas Nov 24 '15 at 13:15
  • oh I didn't realize it's an external tool. Have you **personally** used this tool? I'm tight with time and can't afford to install new software just to get disappointed after spending many hours testing it. Besides, I'm using VS 2015, looks like that tool is from 2009. Will this even work? – Shimmy Weitzhandler Nov 24 '15 at 15:15
  • I used it but only a couple of years ago. I have doubts that it will work in VS2015, that's why I included a [link](https://github.com/ImaginaryDevelopment/MvcOdata/blob/master/Webby/ExceptionHandlingProxyBase.cs) to the `ExceptionHandlingProxyBase`. So you could reuse it if you decided to do the proxy generation yourself. – Tamas Nov 24 '15 at 16:09
  • The `ExceptionHandlingProxyBase` itself will work, WCF is not new. And it's just implementing the same interfaces as the [`ClientBase`](http://referencesource.microsoft.com/#System.ServiceModel/System/ServiceModel/ClientBase.cs,b6f9da84473940d1). – Tamas Nov 24 '15 at 16:11
  • Can the `Open` method throw an exception itself? – Shimmy Weitzhandler Nov 27 '15 at 03:53
  • 1
    I think it can. For example in case of a timeout. – Tamas Nov 27 '15 at 05:42
1

If the service isn't returning a true exception, but just a message, you probably want to add a ClientMessageInspector as a new client behavior. Please see: https://msdn.microsoft.com/en-us/library/ms733786.aspx

David P
  • 2,027
  • 3
  • 15
  • 27
  • The server is returning error messages, but regular exceptions. I need a centralized way to handle ALL exception that can occur while requesting the service, be it an unhandled server exception, or server is down or anything else. I need a place to replace them all the `FaultException` so they're tunneled up to the `Fault` event of the channel. – Shimmy Weitzhandler Nov 18 '15 at 08:54
  • I've added a bounty to my question. I need both things, intercept messages, and handle all types of `Exception`s. – Shimmy Weitzhandler Nov 18 '15 at 12:18
1

I've ended up using something based on the answers in this question.

It sticks to the generated client code, and allows invocation of the operations generically.

The code is incomplete, feel free to fork and edit it. Please notify me if you found any bugs or made any updates.

It's pretty bulky so I'll just share the usage code:

using (var proxy = new ClientProxy<MyServiceSoapClientChannel, MyServiceSoapChannel>())
{
  client.Exception += (sender, eventArgs) =>
  {
    //All the exceptions will get here, can be customized by overriding ClientProxy.
    Console.WriteLine($@"A '{eventArgs.Exception.GetType()}' occurred 
      during operation '{eventArgs.Operation.Method.Name}'.");
    eventArgs.Handled = true;
  };
  client.Invoke(client.Client.MyOperation, "arg1", "arg2");
}
Community
  • 1
  • 1
Shimmy Weitzhandler
  • 101,809
  • 122
  • 424
  • 632