WCF 客户端错误处理

11
我正在使用一个笨重的WCF服务器,偶尔会抛出各种异常,并且还将其中一些错误作为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>实例中。
更新

这篇文章看起来很有前途,但它不起作用。应用的属性似乎没有生效。 我找不到在客户端添加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 中那样转发。

你是否可以访问服务的app.config或web.config文件? - David P
@DavidP 我完全没有访问服务器代码的权限。此外,我更喜欢通过编程设置我的配置,而不是使用应用程序设置或应用程序配置。 - Shimmy Weitzhandler
好的 - 我现在明白你想做什么了。服务是返回异常还是成功返回一个字符串消息? - David P
@DavidP 都需要处理。此外,我还想在同一个地方处理服务器宕机或其他一般异常情况。 - Shimmy Weitzhandler
IOperationInvoker文章是解决您问题的正确方法。您可以在客户端上使用它。不要将其添加为客户端上的IServiceBehavior。将其添加为IOperationBehavior或IEndpointBehavior。 - ErnieL
@ErnieL IOperationInvoker 只能通过 ApplyDispatchBehavior 进行附加,它不是客户端的设施。 - Shimmy Weitzhandler
3个回答

6
您可以使用并修改异常处理WCF代理生成器,更具体地说,使用它所使用的基类。它的基本思想(也可以查看这个描述)是通过捕获连接故障并重试失败的操作来提供连接弹性。正如您所想象的那样,为此,它需要能够捕获抛出的异常,并且还可以检查调用的结果。
主要功能由ExceptionHandlingProxyBase<T>基类提供,您使用该基类而不是ClientBase<T>。该基类具有一个以下方式的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;部分以根据您的需求处理异常,显然还应检查返回值是否符合您的需求。 除此之外,如果您不希望遇到连接问题,则可以保留重试逻辑或将其丢弃。 还有另一种用于void返回方法的Invoke变体。
顺便说一下,它也适用于双工通道,对于那些还有另一个基类。
如果您不想使用生成器(在更新的VS版本中可能无法工作),则可以从这里获取示例的基类,并从服务接口使用T4生成实际的实现类。

谢谢您的努力。BaseClient<TChannel>.Invoke 不是虚函数。如果是虚函数,我就不会有这个问题了。 - Shimmy Weitzhandler
我知道它不是“virtual”,所以我建议您使用ExceptionHandlingProxyBase<T>而不是ClientBase<T>作为客户端代理基类。您的实际实现类可以由生成器生成,也可以自己生成。 - Tamas
哦,我没意识到这是一个外部工具。你个人使用过这个工具吗?我时间很紧,不能安装新软件,然后花费很多时间测试后感到失望。此外,我正在使用VS 2015,看起来那个工具是从2009年的。这会有效吗? - Shimmy Weitzhandler
我曾经使用过它,但是只是几年前的事了。我怀疑它是否能在VS2015中工作,这就是为什么我包含了一个链接ExceptionHandlingProxyBase<T>。所以如果你决定自己生成代理,你可以重用它。 - Tamas
1
我认为可以。例如,在超时的情况下。 - Tamas
显示剩余2条评论

1

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

它遵循生成的客户端代码,并允许泛型调用操作。

代码是不完整的,随意分叉并编辑它。如果您发现任何错误或进行任何更新,请通知我。

它相当笨重,所以我只会分享使用代码:

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

1

服务器正在返回错误消息,但是普通异常。我需要一种集中处理请求服务时可能发生的所有异常的方法,无论是未处理的服务器异常、服务器宕机还是其他任何问题。我需要一个地方来替换所有“FaultException”,以便将它们隧道传输到通道的“Fault”事件。 - Shimmy Weitzhandler
我已经为我的问题添加了赏金。我需要拦截消息并处理所有类型的“异常”。 - Shimmy Weitzhandler

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接