我喜欢在using块中实例化我的WCF服务客户端,因为它几乎是使用实现IDisposable的资源的标准方式:

using (var client = new SomeWCFServiceClient()) 
{
    //Do something with the client 
}

但是,正如这篇MSDN文章中提到的,将WCF客户端包装在using块中可能会掩盖导致客户端处于故障状态(如超时或通信问题)的任何错误。长话短说,当调用Dispose()时,客户端的Close()方法会触发,但会抛出一个错误,因为它处于故障状态。然后,原始异常被第二个异常掩盖。不好的。

在MSDN文章中建议的解决方法是完全避免使用using块,而是实例化你的客户端,并像这样使用它们:

try
{
    ...
    client.Close();
}
catch (CommunicationException e)
{
    ...
    client.Abort();
}
catch (TimeoutException e)
{
    ...
    client.Abort();
}
catch (Exception e)
{
    ...
    client.Abort();
    throw;
}

与using块相比,我认为它很难看。每次需要客户端时都要写大量代码。

幸运的是,我找到了一些其他的解决方法,比如IServiceOriented博客上的这个(现在已经不存在了)。你可以这样开始:

public delegate void UseServiceDelegate<T>(T proxy); 

public static class Service<T> 
{ 
    public static ChannelFactory<T> _channelFactory = new ChannelFactory<T>(""); 
    
    public static void Use(UseServiceDelegate<T> codeBlock) 
    { 
        IClientChannel proxy = (IClientChannel)_channelFactory.CreateChannel(); 
        bool success = false; 
        try 
        { 
            codeBlock((T)proxy); 
            proxy.Close(); 
            success = true; 
        } 
        finally 
        { 
            if (!success) 
            { 
                proxy.Abort(); 
            } 
        } 
     } 
} 

这就允许:

Service<IOrderService>.Use(orderService => 
{ 
    orderService.PlaceOrder(request); 
}); 

这还不错,但我不认为它像using块那样具有表现力和易于理解。

我目前正在尝试使用的解决方法是我第一次在blog.davidbarret.net上读到的。基本上,无论在哪里使用客户机的Dispose()方法,都要重写它。喜欢的东西:

public partial class SomeWCFServiceClient : IDisposable
{
    void IDisposable.Dispose() 
    {
        if (this.State == CommunicationState.Faulted) 
        {
            this.Abort();
        } 
        else 
        {
            this.Close();
        }
    }
}

这似乎能够允许using块再次出现,而不会有隐藏故障状态异常的危险。

那么,使用这些变通方法还有其他需要注意的问题吗?有没有人想出更好的办法?


当前回答

实际上,尽管我写了博客(见Luke的回答),我认为这比我的IDisposable包装更好。典型的代码:

Service<IOrderService>.Use(orderService=>
{
  orderService.PlaceOrder(request);
}); 

(按评论编辑)

由于Use返回void,处理返回值的最简单方法是通过一个捕获的变量:

int newOrderId = 0; // need a value for definite assignment
Service<IOrderService>.Use(orderService=>
  {
    newOrderId = orderService.PlaceOrder(request);
  });
Console.WriteLine(newOrderId); // should be updated

其他回答

实际上,尽管我写了博客(见Luke的回答),我认为这比我的IDisposable包装更好。典型的代码:

Service<IOrderService>.Use(orderService=>
{
  orderService.PlaceOrder(request);
}); 

(按评论编辑)

由于Use返回void,处理返回值的最简单方法是通过一个捕获的变量:

int newOrderId = 0; // need a value for definite assignment
Service<IOrderService>.Use(orderService=>
  {
    newOrderId = orderService.PlaceOrder(request);
  });
Console.WriteLine(newOrderId); // should be updated

我写了一个简单的基类来处理这个问题。它可以作为NuGet包使用,而且非常容易使用。

//MemberServiceClient is the class generated by SvcUtil
public class MemberServiceManager : ServiceClientBase<MemberServiceClient>
{
    public User GetUser(int userId)
    {
        return PerformServiceOperation(client => client.GetUser(userId));
    }

    //you can also check if any error occured if you can't throw exceptions       
    public bool TryGetUser(int userId, out User user)
    {
        return TryPerformServiceOperation(c => c.GetUser(userId), out user);
    }
}

我有我自己的包装器的通道,实现Dispose如下:

public void Dispose()
{
        try
        {
            if (channel.State == CommunicationState.Faulted)
            {
                channel.Abort();
            }
            else
            {
                channel.Close();
            }
        }
        catch (CommunicationException)
        {
            channel.Abort();
        }
        catch (TimeoutException)
        {
            channel.Abort();
        }
        catch (Exception)
        {
            channel.Abort();
            throw;
        }
}

这似乎工作得很好,并允许使用using块。

我使用Castle动态代理来解决Dispose()问题,并在通道处于不可用状态时实现自动刷新。要使用此功能,必须创建一个继承服务契约和IDisposable的新接口。动态代理实现了这个接口,并封装了一个WCF通道:

Func<object> createChannel = () =>
    ChannelFactory<IHelloWorldService>
        .CreateChannel(new NetTcpBinding(), new EndpointAddress(uri));
var factory = new WcfProxyFactory();
var proxy = factory.Create<IDisposableHelloWorldService>(createChannel);
proxy.HelloWorld();

我喜欢这样做,因为您可以注入WCF服务,而使用者无需担心WCF的任何细节。它不像其他解那样有额外的麻烦。

看一下代码,其实很简单: WCF动态代理

您还可以使用DynamicProxy来扩展Dispose()方法。你可以这样做:

using (var wrapperdProxy = new Proxy<yourProxy>())
{
   // Do whatever and dispose of Proxy<yourProxy> will be called and work properly.
}