création de WCF ChannelFactory
j'essaie de convertir une application .net Remoting existante en WCF. Le serveur et le client partagent une interface commune et tous les objets sont des objets activés par le serveur.
dans WCF world, cela serait similaire à créer un service par appel et utiliser ChannelFactory<T>
créer un proxy. Je me bats un peu avec la façon de créer correctement ChannelFactory<T>
pour un ASP.NET client.
pour des raisons de performance, je veux mettre en cache ChannelFactory<T>
objets et créer canal chaque fois que j'appelle la service. En. net remoting days, il y avait RemotingConfiguration.GetRegisteredWellknownClientTypes()
méthode pour obtenir une collection d'objets client que je pourrais ensuite mettre en cache. Il semble, dans le monde de WCF il n'y a pas une telle chose, bien que j'ai pu obtenir une collection de points terminaux à partir du fichier de configuration.
voici ce qui devrait marcher. Je peux créer quelque chose comme ceci:
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;
}
}
je pense que le code ci-dessus fonctionnera, mais je suis un peu inquiet de plusieurs threads essayant d'ajouter de nouveaux ChannelFactory<T>
objets si ce n'est pas dans la Lookup. Puisque J'utilise .NET 4.0, je pensais utiliser ConcurrentDictionary
et utiliser GetOrAdd()
méthode ou de l'utilisation TryGetValue()
d'abord la méthode pour vérifier si ChannelFactory<T>
existe et il n'existe pas, alors utilisez GetOrAdd()
méthode. Pas sûr de la performance cependant de ConcurrentDictionary.TryGetValue()
et ConcurrentDictionary.GetOrAdd()
méthode.
une autre question mineure est de savoir si je dois appeler ChannelFactory.Close()
méthode sur les objets d'usine de canal après ASP.NET l'application se termine ou Puis-je simplement laisser .net framework disposer les objets channel factory de son propre chef. Le proxy canal sera toujours fermé après l'appel de service, en utilisant la méthode ((IChannel)proxy).Close()
méthode.
4 réponses
Oui, si vous voulez créer quelque chose comme ça - une classe statique pour contenir tous ceux ChannelFactory<T>
instances-vous devez certainement vous assurer que cette classe est 100% thread-safe et ne peut pas trébucher lorsqu'elle est consultée simultanément. Je n'ai pas encore beaucoup utilisé les fonctionnalités de .NET 4, donc je ne peux pas les commenter spécifiquement - mais je recommande certainement de rendre cela aussi sûr que possible.
en ce qui concerne votre deuxième question (mineure): le ChannelFactory lui - même est une classe statique- donc vous ne pouvez pas vraiment appeler un .Close()
méthode sur elle. Si vous avez voulu demander si oui ou non appeler le .Close()
méthode sur le réel
Voici une classe helper que j'utilise pour gérer les usines de chaînes:
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();
}
}
}
}
Puis-je définir un service d'invocateur:
public interface IServiceInvoker
{
R InvokeService<T, R>(Func<T, R> invokeHandler) where T: class;
}
et une mise en œuvre:
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;
}
}
maintenant chaque fois que vous avez besoin d'appeler un service de la WCF, vous pouvez utiliser ceci:
WCFServiceInvoker invoker = new WCFServiceInvoker();
SomeReturnType result = invoker.InvokeService<IMyServiceContract, SomeReturnType>(
proxy => proxy.SomeMethod()
);
cela suppose que vous avez défini un paramètre client pour le IMyServiceContract
contrat de service dans le fichier de configuration:
<client>
<endpoint
name="myservice"
address="http://example.com/"
binding="basicHttpBinding"
contract="IMyServiceContract" />
</client>
je n'aimais pas à l'appel de la construction:
WCFServiceInvoker invoker = new WCFServiceInvoker();
var result = invoker.InvokeService<IClaimsService, ICollection<string>>(proxy => proxy.GetStringClaims());
vous ne pouvez pas utiliser le même canal deux fois.
j'ai créé cette solution:
using(var i = Connection<IClaimsService>.Instance)
{
var result = i.Channel.GetStringClaims();
}
Maintenant vous pouvez réutiliser le même canal jusqu'à ce que la déclaration d'utilisation appelle la disposition.
la méthode GetChannel est basicly un canal Factory.CreateChannel() avec un certain config que j'utilise.
vous pouvez construire une mise en cache pour les interfaces de canaux comme les autres solutions faire.
Code pour la classe Connection:
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, Oui je suis allé sur la route de ne pas utiliser une capture d'essai dans le Gestionnaire D'événements par défaut de ChannelFactoryManager. Ainsi Channelfouted deviendrait
private void ChannelFaulted(object sender, EventArgs e)
{
IClientChannel channel = (IClientChannel)sender;
channel.Abort();
}
semble permettre à l'exception originale de faire des bulles. Aussi choisi de ne pas utiliser le canal.proche comme il semble jeter une exception comme le canal est déjà dans un état défectueux. Le gestionnaire d'événements par défaut de factor peut avoir des problèmes similaires. Btw @Darin, bon morceau de code...