简体   繁体   English

创建WCF ChannelFactory <T>

[英]creating WCF ChannelFactory<T>

I'm trying to convert an existing .NET Remoting application to WCF. 我正在尝试将现有的.NET Remoting应用程序转换为WCF。 Both server and client share common interface and all objects are server-activated objects. 服务器和客户端共享公共接口,所有对象都是服务器激活的对象。

In WCF world, this would be similar to creating per-call service and using ChannelFactory<T> to create a proxy. 在WCF世界中,这类似于创建每个呼叫服务并使用ChannelFactory<T>来创建代理。 I'm struggling a bit with how to properly create ChannelFactory<T> for an ASP.NET client. 我正在努力解决如何为ASP.NET客户端正确创建ChannelFactory<T>的问题。

For performance reasons, I want to cache ChannelFactory<T> objects and just create channel every time I call the service. 出于性能原因,我想缓存ChannelFactory<T>对象,并在每次调用服务时创建通道。 In .NET remoting days, there used to be RemotingConfiguration.GetRegisteredWellknownClientTypes() method to get a collection of client objects that I could then cache. 在.NET远程处理时代,曾经有过RemotingConfiguration.GetRegisteredWellknownClientTypes()方法来获取我可以缓存的客户端对象的集合。 It appears, in WCF world there is no such thing, although I was able to get a collection of endpoints from config file. 看来,在WCF世界中没有这样的东西,虽然我能够从配置文件中获取端点集合。

Now here is what I think will work. 现在我认为这将是有效的。 I can create something like this: 我可以创建这样的东西:

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

I think the above code will work, but I'm a bit worried about multiple threads trying to add new ChannelFactory<T> objects if it's not in the lookup. 我认为上面的代码可以工作,但我有点担心多个线程试图添加新的ChannelFactory<T>对象,如果它不在查找中。 Since I'm using .NET 4.0, I was thinking about using ConcurrentDictionary and use GetOrAdd() method or use TryGetValue() method first to check if ChannelFactory<T> exists and it does not exist, then use GetOrAdd() method. 由于我使用的是.NET 4.0,我考虑使用ConcurrentDictionary并使用GetOrAdd()方法或首先使用TryGetValue()方法来检查ChannelFactory<T>存在且它不存在,然后使用GetOrAdd()方法。 Not sure about performance though of ConcurrentDictionary.TryGetValue() and ConcurrentDictionary.GetOrAdd() method. 不确定ConcurrentDictionary.TryGetValue()ConcurrentDictionary.GetOrAdd()方法的性能。

Another minor question is whether I need to call ChannelFactory.Close() method on channel factory objects after ASP.NET application ends or can I just let .NET framework dispose the channel factory objects on its own. 另一个小问题是我是否需要在ASP.NET应用程序结束后在通道工厂对象上调用ChannelFactory.Close()方法,或者我可以让.NET框架自己配置通道工厂对象。 The proxy channel will always be closed after calling service method by using ((IChannel)proxy).Close() method. 通过使用((IChannel)proxy).Close()方法调用服务方法后,代理通道将始终关闭。

Here's a helper class that I use to handle channel factories: 这是我用来处理通道工厂的辅助类:

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

Then I define a service invoker: 然后我定义了一个服务调用者:

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

and an implementation: 和实施:

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

}

Now every time you need to call a WCF service you could use this: 现在每次需要调用WCF服务时都可以使用:

WCFServiceInvoker invoker = new WCFServiceInvoker();
SomeReturnType result = invoker.InvokeService<IMyServiceContract, SomeReturnType>(
    proxy => proxy.SomeMethod()
);

This assumes that you've defined a client endpoint for the IMyServiceContract service contract in the config file: 这假设您已在配置文件中为IMyServiceContract服务合同定义了客户端端点:

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

Yes, if you want to create something like this - a static class to hold all those ChannelFactory<T> instances - you definitely have to make sure this class is 100% thread-safe and cannot stumble when accessed concurrently. 是的,如果你想创建这样的东西 - 一个静态类来保存所有那些ChannelFactory<T>实例 - 你必须确保这个类是100%线程安全的,并且在并发访问时不会绊倒。 I haven't used .NET 4's features much yet, so I cannot comment on those specifically - but I would definitely recommend to make this as safe as possible. 我还没有使用.NET 4的功能,所以我不能对这些功能发表评论 - 但我肯定会建议尽可能安全。

As for your second (minor) question: the ChannelFactory itself is a static class - so you cannot really call a .Close() method on it. 至于你的第二个(次要)问题:ChannelFactory本身是一个静态类 - 所以你不能真正调用它上面的.Close()方法。 If you meant to ask whether or not to call the .Close() method on the actual IChannel , then again: yes, try your best to be a good citizen and close those channels if you ever can. 如果您打算询问是否在实际的IChannel上调用.Close()方法,那么请再次:是的,尽量做一个好公民并尽可能关闭这些渠道。 If you miss one, .NET will take care of it - but don't just toss your unused channels on the floor and go on - clean up after yourself! 如果你错过了一个,.NET会照顾它 - 但不要只是把你未使用的频道丢在地板上继续 - 自己清理干净! :-) :-)

I didn't like the calling construction: 我不喜欢呼叫结构:

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

Also you cannot use the same channel twice. 此外,您不能两次使用相同的频道。

I've created this solution: 我创建了这个解决方案:

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

Now you can reuse the same channel until the using statement calls the dispose. 现在,您可以重用相同的通道,直到using语句调用dispose。

The GetChannel method is basicly a ChannelFactory.CreateChannel() with some extra config's I'm using. GetChannel方法基本上是一个ChannelFactory.CreateChannel(),我正在使用一些额外的配置。

You could build some caching for the ChannelFactory's as the other solutions does. 您可以像其他解决方案那样为ChannelFactory构建一些缓存。

Code for the Connnection class: Connnection类的代码:

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

@NelsonRothermel, yes I went down the road of not using a try catch in the ChannelFactoryManager ChannelFaulted event handler. @NelsonRothermel,是的,我在ChannelFactoryManager ChannelFaulted事件处理程序中没有使用try catch。 So ChannelFaulted would become 所以ChannelFaulted会成为

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

Seems to allow the original exception to bubble up. 似乎允许原始异常冒泡。 Also chose not to use channel.close as it seems to throw an exception as the channel is in a faulted state already. 还选择不使用channel.close,因为它似乎已抛出异常,因为通道已经处于故障状态。 FactoryFaulted event handler may have similar issues. FactoryFaulted事件处理程序可能有类似的问题。 Btw @Darin, good bit of code... Btw @Darin,代码很好......

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM