45 votes

création de WCF ChannelFactory <T>

Je suis en train de convertir un .NET Remoting application de la WCF. À la fois de serveur et de client commun de l'interface et tous les objets sont activé par le serveur d'objets. Dans WCF monde, ce serait semblable à la création par appel de service et à l'aide de ChannelFactory créer un proxy. Je suis un peu en difficulté avec la façon de créer correctement ChannelFactory pour ASP .NET client. Pour des raisons de performances, je veux cache ChannelFactory objets et créer canal à chaque fois que j'appel le service. Dans .NET remoting jours, il RemotingConfiguration.GetRegisteredWellknownClientTypes méthode pour obtenir une collection d'objets client que j'ai pu ensuite en cache. Il apparaît, dans WCF monde il n'y a pas une telle chose, bien que j'ai été en mesure d'obtenir une collection de points de terminaison du fichier de configuration.

Maintenant, voici ce que je pense de travail. 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 fonctionne, mais je suis un peu inquiet au sujet de plusieurs threads tentent d'ajouter de nouveaux ChannelFactory objet, si ce n'est pas dans la recherche. Depuis que j'utilise .NET 4.0, je pensais à l'aide de ConcurrentDictionary de la classe et de l'utilisation GetOrAdd() la méthode ou l'utilisation TryGetValue() la méthode d'abord pour vérifier si ChannelFactory existe et il n'existe pas, alors utilisez GetOrAdd() la méthode. Pas sûr au sujet de la performance mais de ConcurrentDictionary.TryGetValue() et ConcurrentDictionary.GetOrAdd() la méthode.

Un autre petit question est de savoir si j'ai besoin d'appeler ChannelFactory.La méthode Close() sur le canal de l'usine objets après l'ASP .NET application se termine, ou puis-je juste laisser .NET framework disposer le canal de l'usine d'objets sur son propre. Le proxy canal sera toujours fermé après l'appel de service, en utilisant la méthode ((IChannel)proxy).Close() méthode.

64voto

Darin Dimitrov Points 528142

Voici une classe d'aide que j'utilise pour gérer les usines 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>
 

13voto

marc_s Points 321990

Oui, si vous voulez créer quelque chose comme ça - une classe statique pour contenir tous ceux - ChannelFactory<T> des cas, vous devez certainement assurez-vous que cette classe est de 100% de thread-safe et ne peut pas trébucher lors de l'accès en même temps. Je n'ai pas utilisé .NET 4 dispose de beaucoup, de sorte que je ne peut pas commenter sur ceux spécifiquement, mais je vous recommande vraiment de faire ce aussi sûr que possible.

Quant à votre deuxième (mineur) question: la ChannelFactory lui-même est une classe statique - de sorte que vous ne peut pas vraiment appeler une .Close() méthode. Si vous demandez de savoir si ou de ne pas appeler l' .Close() méthode sur l' IChannel, puis de nouveau: oui, essayez de votre mieux pour être un bon citoyen et à proximité de ces voies si jamais vous pouvez. Si vous manquez un .NET va prendre soin d'elle mais ne pas simplement jeter vos canaux non utilisés sur le sol et se nettoyer après vous! :-)

2voto

Job Vermeulen Points 369

Je n'aimais pas à l'appel de la construction:

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

Aussi, vous ne pouvez pas utiliser le même canal à deux reprises.

J'ai créé cette solution:

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

Maintenant, vous pouvez réutiliser la même voie jusqu'à ce que l'instruction à l'aide d'appels de l'éliminer.

Le GetChannel méthode est d'abord un ChannelFactory.CreateChannel() avec un certain config que j'utilise.

Vous pourriez construire certaines de mise en cache pour le ChannelFactory est que les autres solutions.

Code pour la Connexion de la classe:

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

0voto

PhilMc Points 1

@NelsonRothermel, oui, j'ai décidé de ne pas utiliser de capture d'essai dans le gestionnaire d'événements ChannelFactoryManager ChannelFaulted. Donc ChannelFaulted deviendrait

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

Semble permettre à l'exception d'origine de bouillonner. A également choisi de ne pas utiliser channel.close car il semble lever une exception car le canal est déjà dans un état défectueux. Le gestionnaire d'événements FactoryFaulted peut avoir des problèmes similaires. Btw @Darin, bon bout de code ...

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X