WCF 客户端错误处理

WCF client-side error-handling

我正在使用一个笨重的 WCF 服务器,它偶尔会抛出各种异常,另外 returns 它的一些错误 string。我根本无法访问服务器代码。

我想覆盖内部 WCF 客户端请求调用方法并处理服务器返回的所有内部异常和硬编码错误,并在发生错误时引发 Fault 事件,伪:

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

所以当我调用 myClient.GetHelloWorld() 时,它会通过我重写的方法。

如何实现?
我知道我不必使用生成的客户端,但我不想再次重新实现所有合同,我想使用生成的 ClientBase 子类或至少它的通道。
我需要的是控制内部请求调用方法。

更新

我读了这个 answer,看起来它是我正在寻找的部分内容,但我想知道是否有办法将 IErrorHandler 附加到消费者(客户端)代码只是,我想以某种方式将它添加到 ClientBase<TChannel> 实例。

更新

This文章看起来也很有前途,但行不通。 applied 属性似乎没有生效。 我找不到将 IServiceBehavior 添加到客户端的方法。

更新

我尝试通过 IEndpointBehavior.ApplyClientBehavior 调用附加 IErrorHandler

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

clientRuntime是一个参数),但是还是直接跳过MyErrorHandler.
抛出异常 ApplyDispatchBehavior 根本没有被调用。

结论

我需要实现两个方面:

  1. 包装一个BaseClient<TChannel>生命周期中可能发生的所有异常,并决定是处理它们还是继续抛出它们。这应该负责所有操作(我正在使用的服务公开了几十个)
  2. 解析所有服务器回复并为其中一些抛出异常,因此它们将按照语句 1 进行转发。

如果服务没有返回真正的异常,而只是一条消息,您可能想要添加 ClientMessageInspector 作为新的客户端行为。请参阅:https://msdn.microsoft.com/en-us/library/ms733786.aspx

您也可以使用和修改 Exception Handling WCF Proxy Generator, more specifically, the base class that it uses. It's basic idea (check this description)是通过捕获连接故障并重试失败的操作来提供连接弹性。可以想象,为此目的它需要能够捕获抛出的异常,并且还可以检查调用的结果。

主要功能由 ExceptionHandlingProxyBase<T> 基础 class 提供,您可以使用它代替 ClientBase<T>。这个基础 class 有一个 Invoke 方法如下,你需要修改它。

简化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;
}  

您需要修改 throw targetEx.InnerException; 部分以根据需要处理异常,显然还应该根据您的需要检查返回值。除此之外,如果您不希望出现连接问题,您可以保留重试逻辑或将其丢弃。 Invoke void return 方法还有另一种变体。

哦,顺便说一下,它也适用于双工通道,还有另一个基础 class。

如果你不想使用生成器(它甚至可能无法在较新版本的 VS 中工作),那么你可以只从 here 中获取基数 class,然后使用服务接口中的 T4 生成实际实现 class。

我最终使用了基于 this 问题中的答案的东西。

它坚持生成的客户端代码,并允许一般地调用操作。

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");
}