web-dev-qa-db-fra.com

création de WCF ChannelFactory <T>

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 le monde WCF, cela serait similaire à la création d'un service par appel et à l'utilisation de ChannelFactory<T> pour créer un proxy. J'ai du mal à créer correctement ChannelFactory<T> pour un client ASP.NET. 

Pour des raisons de performances, je souhaite mettre en cache les objets ChannelFactory<T> et créer un canal à chaque fois que j'appelle le service. Dans les jours distants .NET, il existait une méthode RemotingConfiguration.GetRegisteredWellknownClientTypes() pour obtenir une collection d'objets client que je pouvais ensuite mettre en cache. Il semble que, dans le monde WCF, cela n'existe pas, même si j'ai pu obtenir une collection de points de terminaison à partir d'un fichier de configuration.

Maintenant, voici ce que je pense qui fonctionnera. Je peux créer quelque chose comme ça:

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 crains un peu que plusieurs threads essaient d'ajouter de nouveaux objets ChannelFactory<T> s'ils ne figurent pas dans la recherche. Depuis que j'utilise .NET 4.0, je pensais à utiliser ConcurrentDictionary et à utiliser la méthode GetOrAdd() ou à utiliser la méthode TryGetValue() pour vérifier si ChannelFactory<T> existe et s'il n'existe pas, puis utilise la méthode GetOrAdd(). Pas sûr des performances des méthodes ConcurrentDictionary.TryGetValue() et ConcurrentDictionary.GetOrAdd().

Une autre question mineure est de savoir si je dois appeler la méthode ChannelFactory.Close() sur les objets de fabrique de canaux après la fin de l’application ASP.NET ou puis-je laisser le .NET Framework disposer lui-même des objets de fabrique de canaux. Le canal proxy sera toujours fermé après l'appel de la méthode de service à l'aide de la méthode ((IChannel)proxy).Close().

44
Eric

Oui, si vous voulez créer quelque chose comme ceci - une classe statique pour contenir toutes ces instances ChannelFactory<T> -, vous devez absolument vous assurer que cette classe est thread-safe à 100% et ne peut pas trébucher en cas d'accès simultané. Je n'ai pas encore beaucoup utilisé les fonctionnalités de .NET 4, je ne peux donc pas en parler en particulier, mais je recommanderais certainement de le rendre aussi sûr que possible.

En ce qui concerne votre deuxième question (mineure): ChannelFactory est une classe statique - vous ne pouvez donc pas appeler une méthode .Close() sur celle-ci. Si vous vouliez demander si vous souhaitez ou non appeler la méthode .Close() sur la variable IChannel réelle, cochez à nouveau: oui, faites de votre mieux pour être un bon citoyen et fermez ces canaux si vous le pouvez. Si vous en manquez un, .NET s'en occupera - mais ne jetez pas simplement vos canaux inutilisés sur le sol et continuez - nettoyez après vous! :-) 

13
marc_s

Voici une classe d'assistance que j'utilise pour gérer les fabriques de canaux:

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

Ensuite, je définis un invocateur de service:

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

et une implémentation:

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 devez appeler un service 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 point de terminaison client pour le contrat de service IMyServiceContract dans le fichier de configuration:

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

Je n'ai pas aimé la construction de l'appelant:

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

De plus, vous ne pouvez pas utiliser deux fois le même canal.

J'ai créé cette solution:

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

Vous pouvez maintenant réutiliser le même canal jusqu'à ce que l'instruction using appelle la disposition.

La méthode GetChannel est essentiellement une ChannelFactory.CreateChannel () avec quelques config supplémentaires que j'utilise.

Vous pouvez créer une mise en cache pour ChannelFactory comme le font les autres solutions.

Code pour la classe de connexion:

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

@ NelsonRothermel, oui j'ai emprunté la voie de N'utilisant pas de catch try dans le gestionnaire d'événements ChannelFactoryManager ChannelFaulted . Ainsi, ChannelFaulted deviendrait

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

Semble autoriser la remontée de l'exception d'origine . Nous avons également choisi de ne pas utiliser channel.close car elle semble émettre une exceptionsi le canal est déjà dans un état défaillant . Le gestionnaire d'événements FactoryFaulted peut avoir le même état issues . Btw @Darin, bon morceau de code ...

0
PhilMc