Создание WCF ChannelFactory <T>
Я пытаюсь преобразовать существующее приложение .NET Remoting в WCF. Оба сервера и клиента имеют общий интерфейс, а все объекты - объекты, активированные сервером.
В мире WCF это будет похоже на создание службы для каждого вызова и использование ChannelFactory<T>
для создания прокси. Я немного борюсь с тем, как правильно создать ChannelFactory<T>
для клиента ASP.NET.
По соображениям производительности я хочу кэшировать объекты ChannelFactory<T>
и просто создавать канал каждый раз, когда я звоню в службу. В .NET remoting days использовался метод 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()
на каналах factory после завершения приложения ASP.NET или я могу просто позволить платформе .NET самостоятельно размещать объекты канала factory. Прокси-канал всегда будет закрыт после вызова метода службы с помощью метода ((IChannel)proxy).Close()
.
Ответы
Ответ 1
Да, если вы хотите создать что-то вроде этого - статический класс для хранения всех этих экземпляров ChannelFactory<T>
, вам обязательно нужно убедиться, что этот класс на 100% потокобезопасен и не может споткнуться при одновременном доступе. Я еще не использовал возможности .NET 4, поэтому я не могу комментировать их конкретно, но я определенно рекомендую сделать это как можно безопаснее.
Что касается второго (второстепенного) вопроса: сам ChannelFactory является статическим классом, поэтому вы не можете называть его .Close()
. Если вы хотите спросить, следует ли вызывать метод .Close()
для фактического IChannel
, а затем еще раз: да, старайтесь быть хорошим гражданином и закрывать эти каналы, если сможете. Если вы пропустите один,.NET позаботится об этом - но не просто бросайте неиспользуемые каналы на пол и продолжайте - очиститесь после себя!:-)
Ответ 2
Вот класс-помощник, который я использую для обработки фабрик каналов:
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>
Ответ 3
Мне не понравилась конструкция вызова:
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();
}
}
}
}
}
Ответ 4
@NelsonRothermel, да, я пошел по дороге
не используя попытку catch в обработчике событий ChannelFactoryManager ChannelFaulted.
Таким образом ChannelFaulted станет
private void ChannelFaulted(object sender, EventArgs e)
{
IClientChannel channel = (IClientChannel)sender;
channel.Abort();
}
Похоже, что исходное исключение пузырится.
Также выбрал не использовать channel.close, поскольку он, похоже, генерирует исключение
поскольку канал уже находится в неисправном состоянии.
Обработчик события FactoryFaulted может иметь схожие проблемы.
Btw @Darin, хороший бит кода...