создание WCF ChannelFactory <T> - PullRequest
       63

создание WCF ChannelFactory <T>

44 голосов
/ 08 июля 2010

Я пытаюсь преобразовать существующее приложение .NET Remoting в WCF.И сервер, и клиент имеют общий интерфейс, и все объекты являются объектами, активируемыми сервером.

В мире WCF это будет похоже на создание службы для каждого вызова и использование ChannelFactory<T> для создания прокси.Я немного борюсь с тем, как правильно создать ChannelFactory<T> для клиента ASP.NET.

По соображениям производительности я хочу кэшировать ChannelFactory<T> объекты и просто создавать канал каждый раз, когда я вызываю службу.В дни удаленного взаимодействия .NET существовал метод 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().

Еще один небольшой вопрос - нужно ли мне вызывать метод ChannelFactory.Close() для объектов фабрики каналов после завершения приложения ASP.NET или я могу просто позволить.NET Framework распоряжается объектами фабрики каналов самостоятельно.Прокси-канал всегда будет закрыт после вызова метода обслуживания с использованием метода ((IChannel)proxy).Close().

Ответы [ 4 ]

64 голосов
/ 08 июля 2010

Вот вспомогательный класс, который я использую для обработки фабрик каналов:

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>
13 голосов
/ 08 июля 2010

Да, если вы хотите создать что-то вроде этого - статический класс для хранения всех этих ChannelFactory<T> экземпляров - вам определенно нужно убедиться, что этот класс на 100% поточнобезопасен и не может запнуться при одновременном доступе. Я еще не очень часто использовал функции .NET 4, поэтому я не могу комментировать их конкретно, но я бы определенно рекомендовал сделать это как можно более безопасным.

Что касается вашего второго (второстепенного) вопроса: сам ChannelFactory является статическим классом - поэтому вы не можете действительно вызвать метод .Close() для него. Если вы хотели спросить, вызывать или нет метод .Close() для фактического IChannel, то еще раз: да, постарайтесь быть хорошим гражданином и закройте эти каналы, если сможете. Если вы пропустите один из них, .NET позаботится об этом - но не просто бросьте свои неиспользуемые каналы на пол и продолжайте - убирайте за собой! :-)

2 голосов
/ 29 апреля 2011

Мне не понравилась конструкция вызова:

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

Теперь вы можете повторно использовать тот же канал, пока оператор using не вызовет dispose.

Метод GetChannel - это, в основном, ChannelFactory.CreateChannel () с некоторыми дополнительными настройками, которые я использую.

Выможет создать некоторое кэширование для ChannelFactory, как это делают другие решения.

Код для класса 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();
               }
            }
         }
      }
}
0 голосов
/ 30 мая 2014

@ NelsonRothermel, да, я пошел по пути, когда не использовал функцию try в обработчике событий ChannelFactoryManager ChannelFaults.Таким образом, ChannelFaults станет

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

Кажется, чтобы исходное исключение всплыло.Также выбрал не использовать channel.close, так как кажется, что он генерирует исключение, так как канал уже находится в неисправном состоянии.Обработчик события FactoryFaults может иметь похожие проблемы.Кстати @ Darin, хороший кусок кода ...

...