创建WCF ChannelFactory<T>

47

我正在尝试将现有的.NET Remoting应用程序转换为WCF。服务器和客户端共享公共接口,所有对象都是由服务器激活的对象。

在WCF世界中,这类似于创建每个调用服务并使用ChannelFactory<T>创建代理。但我在如何为ASP.NET客户端正确创建ChannelFactory<T>方面遇到了一些困难。

出于性能原因,我想要缓存ChannelFactory<T>对象,并且每次调用服务时只需创建通道。在.NET remoting的时代,曾经有可以获取客户端对象集合的RemotingConfiguration.GetRegisteredWellknownClientTypes()方法,我可以将其缓存。虽然我已经能够从配置文件中获取端点集合,但在WCF中似乎没有这样的方法。

现在,这是我认为会起作用的方法:

public static ProxyHelper
{
    static Dictionary<Type, object> lookup = new Dictionary<string, object>();  

    static public T GetChannel<T>()
    {
        Type type = typeof(T);
        ChannelFactory<T> factory;

        if (!lookup.ContainsKey(type))
        {
            factory = new ChannelFactory<T>();
            lookup.Add(type, factory);
        }
        else
        {
            factory = (ChannelFactory<T>)lookup[type];
        }

        T proxy = factory.CreateChannel();   
        ((IClientChannel)proxy).Open();

        return proxy;
    }    
}

我认为上面的代码能够正常工作,但我有点担心多个线程尝试添加新的ChannelFactory<T>对象时,如果它不在查找中,会发生什么。由于我使用的是.NET 4.0,所以考虑使用ConcurrentDictionary,并使用GetOrAdd()方法或先使用TryGetValue()方法检查ChannelFactory<T>是否存在,如果不存在,则使用GetOrAdd()方法。不太确定ConcurrentDictionary.TryGetValue()ConcurrentDictionary.GetOrAdd()方法的性能如何。

另一个小问题是,在ASP.NET应用程序结束后,是否需要调用ChannelFactory.Close()方法来关闭通道工厂对象,还是可以让.NET框架自行处理通道工厂对象的释放。代理通道总是会在调用服务方法后使用((IChannel)proxy).Close()方法关闭。

4个回答

69
这是我用来处理通道工厂的辅助类:
public class ChannelFactoryManager : IDisposable
{
    private static Dictionary<Type, ChannelFactory> _factories = new Dictionary<Type,ChannelFactory>();
    private static readonly object _syncRoot = new object();

    public virtual T CreateChannel<T>() where T : class
    {
        return CreateChannel<T>("*", null);
    }

    public virtual T CreateChannel<T>(string endpointConfigurationName) where T : class
    {
        return CreateChannel<T>(endpointConfigurationName, null);
    }

    public virtual T CreateChannel<T>(string endpointConfigurationName, string endpointAddress) where T : class
    {
        T local = GetFactory<T>(endpointConfigurationName, endpointAddress).CreateChannel();
        ((IClientChannel)local).Faulted += ChannelFaulted;
        return local;
    }

    protected virtual ChannelFactory<T> GetFactory<T>(string endpointConfigurationName, string endpointAddress) where T : class
    {
        lock (_syncRoot)
        {
            ChannelFactory factory;
            if (!_factories.TryGetValue(typeof(T), out factory))
            {
                factory = CreateFactoryInstance<T>(endpointConfigurationName, endpointAddress);
                _factories.Add(typeof(T), factory);
            }
            return (factory as ChannelFactory<T>);
        }
    }

    private ChannelFactory CreateFactoryInstance<T>(string endpointConfigurationName, string endpointAddress)
    {
        ChannelFactory factory = null;
        if (!string.IsNullOrEmpty(endpointAddress))
        {
            factory = new ChannelFactory<T>(endpointConfigurationName, new EndpointAddress(endpointAddress));
        }
        else
        {
            factory = new ChannelFactory<T>(endpointConfigurationName);
        }
        factory.Faulted += FactoryFaulted;
        factory.Open();
        return factory;
    }

    private void ChannelFaulted(object sender, EventArgs e)
    {
        IClientChannel channel = (IClientChannel)sender;
        try
        {
            channel.Close();
        }
        catch
        {
            channel.Abort();
        }
        throw new ApplicationException("Exc_ChannelFailure");
    }

    private void FactoryFaulted(object sender, EventArgs args)
    {
        ChannelFactory factory = (ChannelFactory)sender;
        try
        {
            factory.Close();
        }
        catch
        {
            factory.Abort();
        }
        Type[] genericArguments = factory.GetType().GetGenericArguments();
        if ((genericArguments != null) && (genericArguments.Length == 1))
        {
            Type key = genericArguments[0];
            if (_factories.ContainsKey(key))
            {
                _factories.Remove(key);
            }
        }
        throw new ApplicationException("Exc_ChannelFactoryFailure");
    }

    public void Dispose()
    {
        Dispose(true);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            lock (_syncRoot)
            {
                foreach (Type type in _factories.Keys)
                {
                    ChannelFactory factory = _factories[type];
                    try
                    {
                        factory.Close();
                        continue;
                    }
                    catch
                    {
                        factory.Abort();
                        continue;
                    }
                }
                _factories.Clear();
            }
        }
    }
}

接着我定义了一个服务调用器:

public interface IServiceInvoker
{
    R InvokeService<T, R>(Func<T, R> invokeHandler) where T: class;
}

以及一个实现:

public class WCFServiceInvoker : IServiceInvoker
{
    private static ChannelFactoryManager _factoryManager = new ChannelFactoryManager();
    private static ClientSection _clientSection = ConfigurationManager.GetSection("system.serviceModel/client") as ClientSection;

    public R InvokeService<T, R>(Func<T, R> invokeHandler) where T : class
    {
        var endpointNameAddressPair = GetEndpointNameAddressPair(typeof(T));
        T arg = _factoryManager.CreateChannel<T>(endpointNameAddressPair.Key, endpointNameAddressPair.Value);
        ICommunicationObject obj2 = (ICommunicationObject)arg;
        try
        {
            return invokeHandler(arg);
        }
        finally
        {
            try
            {
                if (obj2.State != CommunicationState.Faulted)
                {
                    obj2.Close();
                }
            }
            catch
            {
                obj2.Abort();
            }
        }
    }

    private KeyValuePair<string, string> GetEndpointNameAddressPair(Type serviceContractType)
    {
        var configException = new ConfigurationErrorsException(string.Format("No client endpoint found for type {0}. Please add the section <client><endpoint name=\"myservice\" address=\"http://address/\" binding=\"basicHttpBinding\" contract=\"{0}\"/></client> in the config file.", serviceContractType));
        if (((_clientSection == null) || (_clientSection.Endpoints == null)) || (_clientSection.Endpoints.Count < 1))
        {
            throw configException;
        }
        foreach (ChannelEndpointElement element in _clientSection.Endpoints)
        {
            if (element.Contract == serviceContractType.ToString())
            {
                return new KeyValuePair<string, string>(element.Name, element.Address.AbsoluteUri);
            }
        }
        throw configException;
    }

}

现在,每当您需要调用WCF服务时,您可以使用以下内容:
WCFServiceInvoker invoker = new WCFServiceInvoker();
SomeReturnType result = invoker.InvokeService<IMyServiceContract, SomeReturnType>(
    proxy => proxy.SomeMethod()
);

假设您已经在配置文件中定义了IMyServiceContract服务契约的客户端终端点:

<client>
    <endpoint 
        name="myservice" 
        address="http://example.com/" 
        binding="basicHttpBinding" 
        contract="IMyServiceContract" />
</client>

2
谢谢!这非常有趣,我需要更仔细地分析一下。我有几个问题:您正在订阅ChannelFactory<T>.Faulted事件。这是否有必要?该事件何时触发,订阅Channel.Faulted是否足以检测到故障状态? - Eric
1
嗨,很棒的解决方案!我注意到一个有趣的事情,与“return new KeyValuePair<string, string>(element.Name, element.Address.Host);”这一段相关,实际上应该是“return new KeyValuePair<string, string>(element.Name, element.Address.AbsoluteUri);”。 - Tri Q Tran
1
@Tri Q,你说得完全正确。我错误地复制粘贴了一些其他的代码,并忘记替换 :-) 我已经更新了我的帖子。感谢你指出这个问题。 - Darin Dimitrov
1
这个怎么样:https://dev59.com/TFbTa4cB1Zd3GeqP7yK2?请问,您能否修改您的类以加载客户端不同的文件? - Kiquenet
2
当您订阅此事件 ((IClientChannel)local).Faulted += ChannelFaulted 时,默认情况下 return invokeHandler(arg); 行不会抛出异常。我猜这就是为什么添加了 throw new ApplicationException("Exc_ChannelFailure"); 的原因。有人找到了获取原始异常的方法吗? - Nelson Rothermel
显示剩余9条评论

14

如果你想创建像这样的东西 - 一个静态类来保存所有那些ChannelFactory<T>实例 - 你一定要确保这个类在并发访问时是100%线程安全的,并且不会出现问题。我还没有很多使用.NET 4的特性,所以我无法对那些具体进行评论 - 但我肯定建议尽可能使其安全。

至于您的第二个(次要)问题:ChannelFactory本身是一个静态类 - 因此您不能真正调用.Close()方法。 如果您想问的是是否要在实际的IChannel上调用.Close()方法,那么答案是肯定的 - 尽力做好自己的事情,如果您能够关闭这些通道,请关闭。 如果您错过了某个通道,.NET会为您处理 - 但不要仅仅把未使用的通道扔在地上然后离开 - 在完成后清理! :-)


抱歉,我指的是关闭ChannelFactory<T>类的实例。字典将包含所有ChannelFactory<T>的实例,但是当应用程序不再需要它们时,我想在某个时候关闭每个工厂。我尝试了AppDomain.CurrentDomain.DomainUnload事件,这似乎适用于我的情况。我能够订阅DomainUnload事件并在ASP .NET应用程序域重新启动时关闭每个通道工厂。然而,我不确定是否在.NET 4中使用ConcurrentDictionary还是像Darrin下面那样使用对字典的访问进行同步。 - Eric

2

我不喜欢这种调用方式:

WCFServiceInvoker invoker = new WCFServiceInvoker();
var result = invoker.InvokeService<IClaimsService, ICollection<string>>(proxy => proxy.GetStringClaims());

同时,您不能重复使用同一频道。

我已经创建了这个解决方案:

using(var i = Connection<IClaimsService>.Instance)
{           
   var result = i.Channel.GetStringClaims();
}

现在你可以重复使用同一个通道,直到使用语句调用dispose。

GetChannel方法基本上是使用一些额外的配置的ChannelFactory.CreateChannel()。

你可以为ChannelFactory构建一些缓存,就像其他解决方案一样。

连接类的代码:

public static class Connection<T>
   {
      public static ChannelHolder Instance
      {
         get
         {
            return new ChannelHolder();
         }
      }

      public class ChannelHolder : IDisposable
      {
         public T Channel { get; set; }

         public ChannelHolder()
         {
            this.Channel = GetChannel();
         }

         public void Dispose()
         {
            IChannel connection = null;
            try
            {
               connection = (IChannel)Channel;
               connection.Close();
            }
            catch (Exception)
            {
               if (connection != null)
               {
                  connection.Abort();
               }
            }
         }
      }
}

如果你想添加中央逻辑,比如自动重试请求,我认为这种方法行不通。你可以订阅ICommunicationObject.Faulted事件,但原始异常将不会抛出。 - Nelson Rothermel

0

@NelsonRothermel,是的,我选择了不在ChannelFactoryManager ChannelFaulted事件处理程序中使用try catch。因此,ChannelFaulted将变为

 private void ChannelFaulted(object sender, EventArgs e)
    {
        IClientChannel channel = (IClientChannel)sender;            
        channel.Abort();
    }

似乎允许原始异常冒泡。 同时选择不使用channel.close,因为它似乎会抛出异常, 因为通道已经处于故障状态。 FactoryFaulted事件处理程序可能存在类似的问题。 顺便说一句@Darin,好代码...


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