我喜欢在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块再次出现,而不会有隐藏故障状态异常的危险。

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


当前回答

我在这篇文章中提到了一些答案,并根据我的需要定制了它。

我想在使用WCF客户端之前做一些事情,所以使用DoSomethingWithClient()方法。

public interface IServiceClientFactory<T>
{
    T DoSomethingWithClient();
}
public partial class ServiceClient : IServiceClientFactory<ServiceClient>
{
    public ServiceClient DoSomethingWithClient()
    {
        var client = this;
        // do somthing here as set client credentials, etc.
        //client.ClientCredentials = ... ;
        return client;
    }
}

下面是helper类:

public static class Service<TClient>
    where TClient : class, ICommunicationObject, IServiceClientFactory<TClient>, new()
{
    public static TReturn Use<TReturn>(Func<TClient, TReturn> codeBlock)
    {
        TClient client = default(TClient);
        bool success = false;
        try
        {
            client = new TClient().DoSomethingWithClient();
            TReturn result = codeBlock(client);
            client.Close();
            success = true;
            return result;
        }
        finally
        {
            if (!success && client != null)
            {
                client.Abort();
            }
        }
    }
}

我可以把它用作:

string data = Service<ServiceClient>.Use(x => x.GetData(7));

其他回答

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

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

像这样的包装器可以工作:

public class ServiceClientWrapper<ServiceType> : IDisposable
{
    private ServiceType _channel;
    public ServiceType Channel
    {
        get { return _channel; }
    }

    private static ChannelFactory<ServiceType> _channelFactory;

    public ServiceClientWrapper()
    {
        if(_channelFactory == null)
             // Given that the endpoint name is the same as FullName of contract.
            _channelFactory = new ChannelFactory<ServiceType>(typeof(T).FullName);
        _channel = _channelFactory.CreateChannel();
        ((IChannel)_channel).Open();
    }

    public void Dispose()
    {
        try
        {
            ((IChannel)_channel).Close();
        }
        catch (Exception e)
        {
            ((IChannel)_channel).Abort();
            // TODO: Insert logging
        }
    }
}

这应该使您能够编写如下代码:

ResponseType response = null;
using(var clientWrapper = new ServiceClientWrapper<IService>())
{
    var request = ...
    response = clientWrapper.Channel.MyServiceCall(request);
}
// Use your response object.

如果需要的话,包装器当然可以捕获更多的异常,但原理是一样的。

下面是问题来源的增强版本,并扩展到缓存多个通道工厂,并尝试通过契约名称在配置文件中查找端点。

它使用。net 4(具体来说:逆变,LINQ, var):

/// <summary>
/// Delegate type of the service method to perform.
/// </summary>
/// <param name="proxy">The service proxy.</param>
/// <typeparam name="T">The type of service to use.</typeparam>
internal delegate void UseServiceDelegate<in T>(T proxy);

/// <summary>
/// Wraps using a WCF service.
/// </summary>
/// <typeparam name="T">The type of service to use.</typeparam>
internal static class Service<T>
{
    /// <summary>
    /// A dictionary to hold looked-up endpoint names.
    /// </summary>
    private static readonly IDictionary<Type, string> cachedEndpointNames = new Dictionary<Type, string>();

    /// <summary>
    /// A dictionary to hold created channel factories.
    /// </summary>
    private static readonly IDictionary<string, ChannelFactory<T>> cachedFactories =
        new Dictionary<string, ChannelFactory<T>>();

    /// <summary>
    /// Uses the specified code block.
    /// </summary>
    /// <param name="codeBlock">The code block.</param>
    internal static void Use(UseServiceDelegate<T> codeBlock)
    {
        var factory = GetChannelFactory();
        var proxy = (IClientChannel)factory.CreateChannel();
        var success = false;

        try
        {
            using (proxy)
            {
                codeBlock((T)proxy);
            }

            success = true;
        }
        finally
        {
            if (!success)
            {
                proxy.Abort();
            }
        }
    }

    /// <summary>
    /// Gets the channel factory.
    /// </summary>
    /// <returns>The channel factory.</returns>
    private static ChannelFactory<T> GetChannelFactory()
    {
        lock (cachedFactories)
        {
            var endpointName = GetEndpointName();

            if (cachedFactories.ContainsKey(endpointName))
            {
                return cachedFactories[endpointName];
            }

            var factory = new ChannelFactory<T>(endpointName);

            cachedFactories.Add(endpointName, factory);
            return factory;
        }
    }

    /// <summary>
    /// Gets the name of the endpoint.
    /// </summary>
    /// <returns>The name of the endpoint.</returns>
    private static string GetEndpointName()
    {
        var type = typeof(T);
        var fullName = type.FullName;

        lock (cachedFactories)
        {
            if (cachedEndpointNames.ContainsKey(type))
            {
                return cachedEndpointNames[type];
            }

            var serviceModel = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None).SectionGroups["system.serviceModel"] as ServiceModelSectionGroup;

            if ((serviceModel != null) && !string.IsNullOrEmpty(fullName))
            {
                foreach (var endpointName in serviceModel.Client.Endpoints.Cast<ChannelEndpointElement>().Where(endpoint => fullName.EndsWith(endpoint.Contract)).Select(endpoint => endpoint.Name))
                {
                    cachedEndpointNames.Add(type, endpointName);
                    return endpointName;
                }
            }
        }

        throw new InvalidOperationException("Could not find endpoint element for type '" + fullName + "' in the ServiceModel client configuration section. This might be because no configuration file was found for your application, or because no endpoint element matching this name could be found in the client element.");
    }
}

我终于找到了一些明确解决这个问题的坚实步骤。

This custom tool extends WCFProxyGenerator to provide an exception handling proxy. It generates an additional proxy called ExceptionHandlingProxy<T> which inherits ExceptionHandlingProxyBase<T> - the latter of which implements the meat of the proxy's functionality. The result is that you can choose to use the default proxy that inherits ClientBase<T> or ExceptionHandlingProxy<T> which encapsulates managing the lifetime of the channel factory and channel. ExceptionHandlingProxy respects your selections in the Add Service Reference dialog with respect to asynchronous methods and collection types.

Codeplex有一个名为异常处理WCF代理生成器的项目。它基本上是在Visual Studio 2008中安装一个新的自定义工具,然后使用该工具生成新的服务代理(添加服务引用)。它有一些很好的功能来处理故障通道、超时和安全处理。这里有一个名为ExceptionHandlingProxyWrapper的优秀视频,详细解释了这是如何工作的。

您可以安全地再次使用Using语句,如果通道在任何请求(TimeoutException或CommunicationException)上发生故障,Wrapper将重新初始化故障通道并重试查询。如果失败,它将调用Abort()命令并处理代理并重新抛出异常。如果服务抛出FaultException代码,它将停止执行,代理将按照预期安全抛出正确的异常。

我使用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动态代理