c# - creando WCF ChannelFactory<T>
.net c#-4.0 (4)
Estoy tratando de convertir una aplicación .NET Remoting existente a WCF. Tanto el servidor como el cliente comparten una interfaz común y todos los objetos son objetos activados por el servidor.
En el mundo de WCF, esto sería similar a crear un servicio por llamada y usar ChannelFactory<T>
para crear un proxy. Estoy luchando un poco sobre cómo crear correctamente ChannelFactory<T>
para un cliente ASP.NET.
Por motivos de rendimiento, quiero almacenar en caché objetos ChannelFactory<T>
y simplemente crear un canal cada vez que llamo al servicio. En días remotos de .NET, solía haber el método RemotingConfiguration.GetRegisteredWellknownClientTypes()
para obtener una colección de objetos de cliente que luego podía almacenar en caché. Parece que en el mundo de WCF no existe tal cosa, aunque pude obtener una colección de puntos finales del archivo de configuración.
Ahora aquí está lo que creo que funcionará. Puedo crear algo como esto:
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;
}
}
Creo que el código anterior funcionará, pero estoy un poco preocupado por los hilos múltiples que intentan agregar nuevos objetos de ChannelFactory<T>
si no están en la búsqueda. Como estoy usando .NET 4.0, estaba pensando en usar ConcurrentDictionary
y usar el método GetOrAdd()
o usar el método TryGetValue()
primero para verificar si ChannelFactory<T>
existe y no existe, luego use el método GetOrAdd()
. No estoy seguro del rendimiento del método ConcurrentDictionary.TryGetValue()
y ConcurrentDictionary.GetOrAdd()
.
Otra cuestión menor es si necesito llamar al método ChannelFactory.Close()
en los objetos de fábrica del canal después de que finalice la aplicación ASP.NET o simplemente dejar que .NET framework elimine los objetos de fábrica del canal por sí mismo. El canal proxy siempre se cerrará después de llamar al método de servicio mediante el método ((IChannel)proxy).Close()
.
@NelsonRothermel, sí, me fui por el camino de no usar una captura de prueba en el controlador de eventos ChannelFactoryManager ChannelFaulted. Así que ChannelFaulted se convertiría
private void ChannelFaulted(object sender, EventArgs e)
{
IClientChannel channel = (IClientChannel)sender;
channel.Abort();
}
Parece permitir que la excepción original se dispare. También eligió no usar channel.close ya que parece lanzar una excepción ya que el canal ya está en estado de falla. El controlador de eventos FactoryFaulted puede tener problemas similares. Btw @Darin, buen pedazo de código ...
Aquí hay una clase de ayuda que utilizo para manejar fábricas de canales:
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();
}
}
}
}
Luego defino un invocador de servicio:
public interface IServiceInvoker
{
R InvokeService<T, R>(Func<T, R> invokeHandler) where T: class;
}
y una implementación:
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;
}
}
Ahora, cada vez que necesite llamar a un servicio de WCF, puede usar esto:
WCFServiceInvoker invoker = new WCFServiceInvoker();
SomeReturnType result = invoker.InvokeService<IMyServiceContract, SomeReturnType>(
proxy => proxy.SomeMethod()
);
Esto supone que ha definido un punto final del cliente para el contrato de servicio IMyServiceContract
en el archivo de configuración:
<client>
<endpoint
name="myservice"
address="http://example.com/"
binding="basicHttpBinding"
contract="IMyServiceContract" />
</client>
No me gustó la construcción de llamadas:
WCFServiceInvoker invoker = new WCFServiceInvoker();
var result = invoker.InvokeService<IClaimsService, ICollection<string>>(proxy => proxy.GetStringClaims());
Además, no puedes usar el mismo canal dos veces.
Creé esta solución:
using(var i = Connection<IClaimsService>.Instance)
{
var result = i.Channel.GetStringClaims();
}
Ahora puede reutilizar el mismo canal hasta que la instrucción using llame a disponer.
El método GetChannel es básicamente ChannelFactory.CreateChannel () con algunas configuraciones adicionales que estoy usando.
Podría construir algo de almacenamiento en caché para ChannelFactory como lo hacen las otras soluciones.
Código para la clase 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();
}
}
}
}
}
Sí, si quieres crear algo como esto - una clase estática para contener todas las instancias de ChannelFactory<T>
- definitivamente debes asegurarte de que esta clase sea 100% segura para subprocesos y no pueda tropezar cuando se acceda simultáneamente. Todavía no he usado las características de .NET 4, así que no puedo comentar sobre ellas específicamente, pero definitivamente recomendaría hacer esto lo más seguro posible.
En cuanto a su segunda pregunta (menor): ChannelFactory en sí es una clase estática, por lo que no puede realmente llamar a un método .Close()
. Si quería preguntar si llama o no al método .Close()
en el IChannel
real, entonces nuevamente: sí, IChannel
todo lo posible para ser un buen ciudadano y cierre esos canales si puede. Si se olvida una, .NET se encargará de eso, pero no se limite a tirar los canales que no se usan en el piso y continuar, ¡límpielos! :-)