Question

J'aime instanciation mes clients de service WCF dans un bloc de using comme il est à peu près la manière standard d'utiliser les ressources qui mettent en œuvre IDisposable:

using (var client = new SomeWCFServiceClient()) 
{
    //Do something with the client 
}

Mais, comme il est indiqué dans cet article MSDN , enroulant un WCF client dans un bloc de using pourrait masquer des erreurs qui se traduisent par le client étant laissé dans un état de défaut (comme une temporisation ou d'un problème de communication). Longue histoire courte, lorsque les feux de méthode Dispose () est appelée, la proximité du client (), mais renvoie une erreur, car il est dans un état de défaut. L'exception d'origine est alors masquée par la seconde exception. Pas bon.

La solution proposée dans l'article MSDN est d'éviter complètement l'aide d'un bloc using, et à la place instancier vos clients et de les utiliser quelque chose comme ceci:

try
{
    ...
    client.Close();
}
catch (CommunicationException e)
{
    ...
    client.Abort();
}
catch (TimeoutException e)
{
    ...
    client.Abort();
}
catch (Exception e)
{
    ...
    client.Abort();
    throw;
}

Par rapport au bloc using, je pense que c'est laid. Et beaucoup de code à écrire chaque fois que vous avez besoin d'un client.

Heureusement, j'ai trouvé quelques autres solutions de contournement, comme celui-ci sur IServiceOriented. Vous commencez avec:

public delegate void UseServiceDelegate<T>(T proxy); 

public static class Service<T> 
{ 
    public static ChannelFactory<T> _channelFactory = new ChannelFactory<T>(""); 

    public static void Use(UseServiceDelegate<T> codeBlock) 
    { 
        IClientChannel proxy = (IClientChannel)_channelFactory.CreateChannel(); 
        bool success = false; 
        try 
        { 
            codeBlock((T)proxy); 
            proxy.Close(); 
            success = true; 
        } 
        finally 
        { 
            if (!success) 
            { 
                proxy.Abort(); 
            } 
        } 
     } 
} 

Ce qui permet alors:

Service<IOrderService>.Use(orderService => 
{ 
    orderService.PlaceOrder(request); 
}); 

Ce n'est pas mal, mais je ne pense pas qu'il est aussi expressif et facilement compréhensible que le bloc using.

La solution que je suis en train d'essayer d'utiliser j'ai lu sur blog.davidbarret. net. Fondamentalement, vous substituez la méthode de Dispose() du client où vous l'utilisez. Quelque chose comme:

public partial class SomeWCFServiceClient : IDisposable
{
    void IDisposable.Dispose() 
    {
        if (this.State == CommunicationState.Faulted) 
        {
            this.Abort();
        } 
        else 
        {
            this.Close();
        }
    }
}

Cela semble être en mesure de permettre au bloc de using à nouveau sans risque de masquer une exception de l'Etat en défaut.

Alors, y at-il d'autres gotchas je dois regarder l'utilisation de ces solutions de contournement? Quelqu'un at-il arriver à quelque chose de mieux?

Était-ce utile?

La solution

En fait, bien que je blogué (voir Luke's répondre ), je penser cette est meilleur que mon emballage IDisposable. Code typique:

Service<IOrderService>.Use(orderService=>
{
  orderService.PlaceOrder(request);
}); 

(modifier par les commentaires)

Depuis Use retourne vide, la meilleure façon de gérer les valeurs de retour se fait par une variable capturée:

int newOrderId = 0; // need a value for definite assignment
Service<IOrderService>.Use(orderService=>
  {
    newOrderId = orderService.PlaceOrder(request);
  });
Console.WriteLine(newOrderId); // should be updated

Autres conseils

Étant donné le choix entre la solution préconisée par IServiceOriented.com et la solution préconisée par David Barret de de blog, je préfère la simplicité offerte en remplaçant la méthode Dispose () du client. Cela me permet de continuer à utiliser l'instruction en utilisant () comme on peut s'y attendre avec un objet jetable. Cependant, comme @ Brian a fait remarquer, cette solution contient une condition de course que l'Etat pourrait ne pas être prise en défaut quand il est vérifié, mais pourrait être le temps Close () est appelée, dans ce cas, le CommunicationException se produit encore.

Alors, pour se déplacer, j'ai employé une solution qui mélange le meilleur des deux mondes.

void IDisposable.Dispose()
{
    bool success = false;
    try 
    {
        if (State != CommunicationState.Faulted) 
        {
            Close();
            success = true;
        }
    } 
    finally 
    {
        if (!success) 
            Abort();
    }
}

J'ai écrit un

Ceci est la méthode recommandée de Microsoft pour gérer les appels des clients WCF:

Pour plus de détails, voir: Exceptions prévues

try
{
    ...
    double result = client.Add(value1, value2);
    ...
    client.Close();
}
catch (TimeoutException exception)
{
    Console.WriteLine("Got {0}", exception.GetType());
    client.Abort();
}
catch (CommunicationException exception)
{
    Console.WriteLine("Got {0}", exception.GetType());
    client.Abort();
}

Informations complémentaires Beaucoup de gens semblent poser cette question sur WCF que Microsoft a même créé un échantillon dédié pour montrer comment gérer les exceptions:

c: \ WF_WCF_Samples \ WCF \ base \ Client \ ExpectedExceptions \ CS \ client

Télécharger l'échantillon: C # ou VB

Considérant qu'il ya tant de problèmes impliquant l'instruction à l'aide , (chauffée?) Les discussions internes et cette réponse , et ne voient pas de « hacks » dans ce code qui peuvent causer des problèmes sur la route.

Je l'ai finalement trouvé quelques pas solides vers une solution propre à ce problème.

  

Cet outil personnalisé étend WCFProxyGenerator pour fournir un proxy de gestion des exceptions. Il génère un proxy supplémentaire appelé ExceptionHandlingProxy<T> qui hérite ExceptionHandlingProxyBase<T> - ce dernier qui met en œuvre la viande de la fonctionnalité du proxy. Le résultat est que vous pouvez choisir d'utiliser le proxy par défaut qui hérite ClientBase<T> ou ExceptionHandlingProxy<T> qui encapsule la gestion de la durée de vie de l'usine de canal et le canal. ExceptionHandlingProxy respecte vos sélections dans la boîte de dialogue Service de référence Ajouter en ce qui concerne les méthodes et les types de collecte asynchrones.

Codeplex a un projet appelé Gestion des exceptions WCF Proxy Generator . Il installe en fait un nouvel outil personnalisé pour Visual Studio 2008, puis utilisez cet outil pour générer le nouveau proxy de service (Ajouter une référence de service) . Il a une fonctionnalité agréable de traiter avec les chaînes, les délais d'attente et faillées élimination en toute sécurité. Il y a une excellente vidéo ici appelé ExceptionHandlingProxyWrapper expliquant comment exactement cela fonctionne.

Vous pouvez utiliser en toute sécurité le nouveau relevé de Using, et si le canal est en défaut sur toute demande (ou TimeoutException CommunicationException), le Wrapper réinitialisera le canal faillée et essayez de nouveau la requête. Si cela échoue, alors il appellera la commande Abort() et de disposer de la procuration et réémettre l'exception. Si le service lance un code de FaultException il arrêtera l'exécution et le proxy sera avortée jeter en toute sécurité l'exception correcte comme prévu.

Sur la base des réponses de Marc Gravell, MichaelGG et Matt Davis, nos développeurs sont venus avec les éléments suivants:

public static class UsingServiceClient
{
    public static void Do<TClient>(TClient client, Action<TClient> execute)
        where TClient : class, ICommunicationObject
    {
        try
        {
            execute(client);
        }
        finally
        {
            client.DisposeSafely();
        }
    }

    public static void DisposeSafely(this ICommunicationObject client)
    {
        if (client == null)
        {
            return;
        }

        bool success = false;

        try
        {
            if (client.State != CommunicationState.Faulted)
            {
                client.Close();
                success = true;
            }
        }
        finally
        {
            if (!success)
            {
                client.Abort();
            }
        }
    }
}

Exemple d'utilisation:

string result = string.Empty;

UsingServiceClient.Do(
    new MyServiceClient(),
    client =>
    result = client.GetServiceResult(parameters));

Il est aussi proche de la « utilisant » la syntaxe que possible, vous ne devez pas retourner une valeur factice lors de l'appel d'une méthode vide, et vous pouvez faire plusieurs appels au service (et retour plusieurs valeurs) sans avoir à utiliser tuples.

, vous pouvez également l'utiliser avec les descendants de ClientBase<T> au lieu de ChannelFactory si on le souhaite.

La méthode d'extension est exposée si un développeur veut disposer manuellement d'un proxy / canal à la place.

@Marc Gravell

Ne serait-il pas OK d'utiliser ceci:

public static TResult Using<T, TResult>(this T client, Func<T, TResult> work)
        where T : ICommunicationObject
{
    try
    {
        var result = work(client);

        client.Close();

        return result;
    }
    catch (Exception e)
    {
        client.Abort();

        throw;
    }
}

Ou, le même (Func<T, TResult>) chose en cas de Service<IOrderService>.Use

Ces rendraient les variables de retour plus facile.

Qu'est-ce que cela?

Ceci est la version CW de la réponse acceptée, mais avec (ce que je considère complet) La gestion des exceptions inclus.

Les références de réponse acceptée ce site qui n'est plus autour . Pour vous sauver du mal, je suis notamment les parties les plus pertinentes ici. De plus, je l'ai légèrement modifié pour inclure nouvelle tentative d'exception manipulation pour gérer les délais d'attente du réseau embêtants.

Simple client WCF Utilisation

Une fois que vous générez votre proxy côté client, cela est tout ce que vous devez le mettre en œuvre.

Service<IOrderService>.Use(orderService=>
{
  orderService.PlaceOrder(request);
});

ServiceDelegate.cs

Ajoutez ce fichier à votre solution. Aucun changement sont nécessaires pour ce fichier, sauf si vous souhaitez modifier le nombre de tentatives ou les exceptions que vous voulez gérer.

public delegate void UseServiceDelegate<T>(T proxy);

public static class Service<T>
{
    public static ChannelFactory<T> _channelFactory = new ChannelFactory<T>(""); 

    public static void Use(UseServiceDelegate<T> codeBlock)
    {
        IClientChannel proxy = (IClientChannel)_channelFactory.CreateChannel();
        bool success = false;


       Exception mostRecentEx = null;
       int millsecondsToSleep = 1000;

       for(int i=0; i<5; i++)  // Attempt a maximum of 5 times 
       {
           try
           {
               codeBlock((T)proxy);
               proxy.Close();
               success = true; 
               break;
           }

           // The following is typically thrown on the client when a channel is terminated due to the server closing the connection.
           catch (ChannelTerminatedException cte)
           {
              mostRecentEx = cte;
               proxy.Abort();
               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep  * (i + 1)); 
           }

           // The following is thrown when a remote endpoint could not be found or reached.  The endpoint may not be found or 
           // reachable because the remote endpoint is down, the remote endpoint is unreachable, or because the remote network is unreachable.
           catch (EndpointNotFoundException enfe)
           {
              mostRecentEx = enfe;
               proxy.Abort();
               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep * (i + 1)); 
           }

           // The following exception that is thrown when a server is too busy to accept a message.
           catch (ServerTooBusyException stbe)
           {
              mostRecentEx = stbe;
               proxy.Abort();

               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep * (i + 1)); 
           }
           catch (TimeoutException timeoutEx)
           {
               mostRecentEx = timeoutEx;
               proxy.Abort();

               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep * (i + 1)); 
           } 
           catch (CommunicationException comException)
           {
               mostRecentEx = comException;
               proxy.Abort();

               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep * (i + 1)); 
           }
           catch(Exception )
           {
                // rethrow any other exception not defined here
                // You may want to define a custom Exception class to pass information such as failure count, and failure type
                proxy.Abort();
                throw ;  
           }
       }
       if (success == false && mostRecentEx != null) 
       { 
           proxy.Abort();
           throw new Exception("WCF call failed after 5 retries.", mostRecentEx );
       }

    }
}

PS: Je l'ai fait ce poste un wiki communautaire. Je ne vais pas recueillir des « points » de cette réponse, mais préférez, vous upvote si vous êtes d'accord avec la mise en œuvre, ou le modifier pour le rendre meilleur.

est ci-dessous une version améliorée de la source de question et étendue à mettre en cache les usines de canaux multiples et tenter de rechercher le point final dans le fichier de configuration par le nom du contrat.

Il utilise Framework 4 (plus précisément: contravariance, LINQ, var):

/// <summary>
/// Delegate type of the service method to perform.
/// </summary>
/// <param name="proxy">The service proxy.</param>
/// <typeparam name="T">The type of service to use.</typeparam>
internal delegate void UseServiceDelegate<in T>(T proxy);

/// <summary>
/// Wraps using a WCF service.
/// </summary>
/// <typeparam name="T">The type of service to use.</typeparam>
internal static class Service<T>
{
    /// <summary>
    /// A dictionary to hold looked-up endpoint names.
    /// </summary>
    private static readonly IDictionary<Type, string> cachedEndpointNames = new Dictionary<Type, string>();

    /// <summary>
    /// A dictionary to hold created channel factories.
    /// </summary>
    private static readonly IDictionary<string, ChannelFactory<T>> cachedFactories =
        new Dictionary<string, ChannelFactory<T>>();

    /// <summary>
    /// Uses the specified code block.
    /// </summary>
    /// <param name="codeBlock">The code block.</param>
    internal static void Use(UseServiceDelegate<T> codeBlock)
    {
        var factory = GetChannelFactory();
        var proxy = (IClientChannel)factory.CreateChannel();
        var success = false;

        try
        {
            using (proxy)
            {
                codeBlock((T)proxy);
            }

            success = true;
        }
        finally
        {
            if (!success)
            {
                proxy.Abort();
            }
        }
    }

    /// <summary>
    /// Gets the channel factory.
    /// </summary>
    /// <returns>The channel factory.</returns>
    private static ChannelFactory<T> GetChannelFactory()
    {
        lock (cachedFactories)
        {
            var endpointName = GetEndpointName();

            if (cachedFactories.ContainsKey(endpointName))
            {
                return cachedFactories[endpointName];
            }

            var factory = new ChannelFactory<T>(endpointName);

            cachedFactories.Add(endpointName, factory);
            return factory;
        }
    }

    /// <summary>
    /// Gets the name of the endpoint.
    /// </summary>
    /// <returns>The name of the endpoint.</returns>
    private static string GetEndpointName()
    {
        var type = typeof(T);
        var fullName = type.FullName;

        lock (cachedFactories)
        {
            if (cachedEndpointNames.ContainsKey(type))
            {
                return cachedEndpointNames[type];
            }

            var serviceModel = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None).SectionGroups["system.serviceModel"] as ServiceModelSectionGroup;

            if ((serviceModel != null) && !string.IsNullOrEmpty(fullName))
            {
                foreach (var endpointName in serviceModel.Client.Endpoints.Cast<ChannelEndpointElement>().Where(endpoint => fullName.EndsWith(endpoint.Contract)).Select(endpoint => endpoint.Name))
                {
                    cachedEndpointNames.Add(type, endpointName);
                    return endpointName;
                }
            }
        }

        throw new InvalidOperationException("Could not find endpoint element for type '" + fullName + "' in the ServiceModel client configuration section. This might be because no configuration file was found for your application, or because no endpoint element matching this name could be found in the client element.");
    }
}

Une enveloppe comme cela fonctionnerait:

public class ServiceClientWrapper<ServiceType> : IDisposable
{
    private ServiceType _channel;
    public ServiceType Channel
    {
        get { return _channel; }
    }

    private static ChannelFactory<ServiceType> _channelFactory;

    public ServiceClientWrapper()
    {
        if(_channelFactory == null)
             // Given that the endpoint name is the same as FullName of contract.
            _channelFactory = new ChannelFactory<ServiceType>(typeof(T).FullName);
        _channel = _channelFactory.CreateChannel();
        ((IChannel)_channel).Open();
    }

    public void Dispose()
    {
        try
        {
            ((IChannel)_channel).Close();
        }
        catch (Exception e)
        {
            ((IChannel)_channel).Abort();
            // TODO: Insert logging
        }
    }
}

Cela devrait vous permettre d'écrire du code comme:

ResponseType response = null;
using(var clientWrapper = new ServiceClientWrapper<IService>())
{
    var request = ...
    response = clientWrapper.Channel.MyServiceCall(request);
}
// Use your response object.

L'enveloppe pourrait bien sûr prendre plus d'exceptions si cela est nécessaire, mais le principe reste le même.

je proxy dynamique Castle pour résoudre le problème Dispose (), et également mis en place l'actualisation automatique de canal quand il est dans un état inutilisable. Pour utiliser cela, vous devez créer une nouvelle interface qui hérite votre contrat de service et IDisposable. Le proxy dynamique implémente cette interface et enroule un canal WCF:

Func<object> createChannel = () =>
    ChannelFactory<IHelloWorldService>
        .CreateChannel(new NetTcpBinding(), new EndpointAddress(uri));
var factory = new WcfProxyFactory();
var proxy = factory.Create<IDisposableHelloWorldService>(createChannel);
proxy.HelloWorld();

J'aime cela car vous pouvez injecter des services WCF sans que les consommateurs ont besoin de se soucier des détails de WCF. Et il n'y a pas cruft ajoutée comme les autres solutions.

Regardez le code, il est en fait assez simple: WCF Dynamic Proxy

Si vous n'avez pas besoin IoC ou utilisez un client autogenerated (Service de référence), alors vous pouvez simplement utiliser un wrapper pour gérer la fermeture et laisser le GC prendre la ClientBase quand il est dans un état sûr qui ne jeter aucune exception. Le GC appellera dans serviceclient Jeter, et cela appellera Close. Comme il est fermé alread, il ne peut causer aucun dommage. J'utilise cela sans problèmes dans le code de production.

public class AutoCloseWcf : IDisposable
{

    private ICommunicationObject CommunicationObject;

    public AutoDisconnect(ICommunicationObject CommunicationObject)
    {
        this.CommunicationObject = CommunicationObject;
    }

    public void Dispose()
    {
        if (CommunicationObject == null)
            return;
        try {
            if (CommunicationObject.State != CommunicationState.Faulted) {
                CommunicationObject.Close();
            } else {
                CommunicationObject.Abort();
            }
        } catch (CommunicationException ce) {
            CommunicationObject.Abort();
        } catch (TimeoutException toe) {
            CommunicationObject.Abort();
        } catch (Exception e) {
            CommunicationObject.Abort();
            //Perhaps log this

        } finally {
            CommunicationObject = null;
        }
    }
}

Ensuite, lorsque vous accédez au serveur, vous créez le client et utilisez using dans le autodisconect:

var Ws = new ServiceClient("netTcpEndPointName");
using (new AutoCloseWcf(Ws)) {

    Ws.Open();

    Ws.Test();
}

utiliser une méthode d'extension:

public static class CommunicationObjectExtensions
{
    public static TResult MakeSafeServiceCall<TResult, TService>(this TService client, Func<TService, TResult> method) where TService : ICommunicationObject
    {
        TResult result;

        try
        {
            result = method(client);
        }
        finally
        {
            try
            {
                client.Close();
            }
            catch (CommunicationException)
            {
                client.Abort(); // Don't care about these exceptions. The call has completed anyway.
            }
            catch (TimeoutException)
            {
                client.Abort(); // Don't care about these exceptions. The call has completed anyway.
            }
            catch (Exception)
            {
                client.Abort();
                throw;
            }
        }

        return result;
    }
}

Résumé

En utilisant les techniques décrites dans cette réponse, on peut utiliser un service WCF dans un bloc en utilisant la syntaxe suivante:

var channelFactory = new ChannelFactory<IMyService>("");

var serviceHelper = new ServiceHelper<IMyService>(channelFactory);
var proxy = serviceHelper.CreateChannel();
using (proxy as IDisposable)
{
    proxy.DoWork();
}

Vous pouvez bien sûr adapter encore plus loin pour atteindre un modèle de programmation plus concis spécifiques à votre situation - mais le point est que nous pouvons créer une implémentation de IMyService reprenting le canal qui implémente correctement le modèle jetable

.

Détails

Toutes les réponses données ainsi répondre loin le problème de contourner le « bug » dans le canal implemention WCF de IDisposable. La réponse qui semble offrir le modèle de programmation la plus concise (vous permettant d'utiliser le bloc using de disposer des ressources non gérés) est celui-ci - où la procuration est modifed à mettre en œuvre IDisposable avec une mise en œuvre sans bug. Le problème avec cette approche est maintenabilité - nous devons ré-implémenter cette fonctionnalité pour toujours proxy que nous utilisons. Sur une variante de cette réponse, nous verrons comment nous pouvons utiliser composition plutôt que l'héritage à faire cette technique générique.

Première tentative

Il semble différentes implémentations pour la mise en œuvre de IDisposable, mais pour l'amour de l'argumentation, nous allons utiliser une adaptation de celui utilisé par le actuellement acceptée répondre .

[ServiceContract]
public interface IMyService
{
    [OperationContract]
    void DoWork();
}

public class ProxyDisposer : IDisposable
{
    private IClientChannel _clientChannel;


    public ProxyDisposer(IClientChannel clientChannel)
    {
        _clientChannel = clientChannel;
    }

    public void Dispose()
    {
        var success = false;
        try
        {
            _clientChannel.Close();
            success = true;
        }
        finally
        {
            if (!success)
                _clientChannel.Abort();
            _clientChannel = null;
        }
    }
}

public class ProxyWrapper : IMyService, IDisposable
{
    private IMyService _proxy;
    private IDisposable _proxyDisposer;

    public ProxyWrapper(IMyService proxy, IDisposable disposable)
    {
        _proxy = proxy;
        _proxyDisposer = disposable;
    }

    public void DoWork()
    {
        _proxy.DoWork();
    }

    public void Dispose()
    {
        _proxyDisposer.Dispose();
    }
}

Armés des classes ci-dessus, nous pouvons maintenant écrire

public class ServiceHelper
{
    private readonly ChannelFactory<IMyService> _channelFactory;

    public ServiceHelper(ChannelFactory<IMyService> channelFactory )
    {
        _channelFactory = channelFactory;
    }

    public IMyService CreateChannel()
    {
        var channel = _channelFactory.CreateChannel();
        var channelDisposer = new ProxyDisposer(channel as IClientChannel);
        return new ProxyWrapper(channel, channelDisposer);
    }
}

Cela nous permet de consommer notre service à l'aide du bloc using:

ServiceHelper serviceHelper = ...;
var proxy = serviceHelper.CreateChannel();
using (proxy as IDisposable)
{
    proxy.DoWork();
}

Faire ce générique

Tout ce que nous avons fait jusqu'à présent est de reformuler solution Tomas . Ce qui empêche ce code d'être générique est le fait que la classe ProxyWrapper doit être réimplémenté pour chaque contrat de service que nous voulons. Nous allons maintenant à une classe qui nous permet de créer ce type en utilisant dynamiquement IL:

public class ServiceHelper<T>
{
    private readonly ChannelFactory<T> _channelFactory;

    private static readonly Func<T, IDisposable, T> _channelCreator;

    static ServiceHelper()
    {
        /** 
         * Create a method that can be used generate the channel. 
         * This is effectively a compiled verion of new ProxyWrappper(channel, channelDisposer) for our proxy type
         * */
        var assemblyName = Guid.NewGuid().ToString();
        var an = new AssemblyName(assemblyName);
        var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
        var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName);

        var proxyType = CreateProxyType(moduleBuilder, typeof(T), typeof(IDisposable));

        var channelCreatorMethod = new DynamicMethod("ChannelFactory", typeof(T),
            new[] { typeof(T), typeof(IDisposable) });

        var ilGen = channelCreatorMethod.GetILGenerator();
        var proxyVariable = ilGen.DeclareLocal(typeof(T));
        var disposableVariable = ilGen.DeclareLocal(typeof(IDisposable));
        ilGen.Emit(OpCodes.Ldarg, proxyVariable);
        ilGen.Emit(OpCodes.Ldarg, disposableVariable);
        ilGen.Emit(OpCodes.Newobj, proxyType.GetConstructor(new[] { typeof(T), typeof(IDisposable) }));
        ilGen.Emit(OpCodes.Ret);

        _channelCreator =
            (Func<T, IDisposable, T>)channelCreatorMethod.CreateDelegate(typeof(Func<T, IDisposable, T>));

    }

    public ServiceHelper(ChannelFactory<T> channelFactory)
    {
        _channelFactory = channelFactory;
    }

    public T CreateChannel()
    {
        var channel = _channelFactory.CreateChannel();
        var channelDisposer = new ProxyDisposer(channel as IClientChannel);
        return _channelCreator(channel, channelDisposer);
    }

   /**
    * Creates a dynamic type analogous to ProxyWrapper, implementing T and IDisposable.
    * This method is actually more generic than this exact scenario.
    * */
    private static Type CreateProxyType(ModuleBuilder moduleBuilder, params Type[] interfacesToInjectAndImplement)
    {
        TypeBuilder tb = moduleBuilder.DefineType(Guid.NewGuid().ToString(),
            TypeAttributes.Public | TypeAttributes.Class);

        var typeFields = interfacesToInjectAndImplement.ToDictionary(tf => tf,
            tf => tb.DefineField("_" + tf.Name, tf, FieldAttributes.Private));

        #region Constructor

        var constructorBuilder = tb.DefineConstructor(
            MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName |
            MethodAttributes.RTSpecialName,
            CallingConventions.Standard,
            interfacesToInjectAndImplement);

        var il = constructorBuilder.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Call, typeof(object).GetConstructor(new Type[0]));

        for (var i = 1; i <= interfacesToInjectAndImplement.Length; i++)
        {
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldarg, i);
            il.Emit(OpCodes.Stfld, typeFields[interfacesToInjectAndImplement[i - 1]]);
        }
        il.Emit(OpCodes.Ret);

        #endregion

        #region Add Interface Implementations

        foreach (var type in interfacesToInjectAndImplement)
        {
            tb.AddInterfaceImplementation(type);
        }

        #endregion

        #region Implement Interfaces

        foreach (var type in interfacesToInjectAndImplement)
        {
            foreach (var method in type.GetMethods())
            {
                var methodBuilder = tb.DefineMethod(method.Name,
                    MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig |
                    MethodAttributes.Final | MethodAttributes.NewSlot,
                    method.ReturnType,
                    method.GetParameters().Select(p => p.ParameterType).ToArray());
                il = methodBuilder.GetILGenerator();

                if (method.ReturnType == typeof(void))
                {
                    il.Emit(OpCodes.Nop);
                    il.Emit(OpCodes.Ldarg_0);
                    il.Emit(OpCodes.Ldfld, typeFields[type]);
                    il.Emit(OpCodes.Callvirt, method);
                    il.Emit(OpCodes.Ret);
                }
                else
                {
                    il.DeclareLocal(method.ReturnType);

                    il.Emit(OpCodes.Nop);
                    il.Emit(OpCodes.Ldarg_0);
                    il.Emit(OpCodes.Ldfld, typeFields[type]);

                    var methodParameterInfos = method.GetParameters();
                    for (var i = 0; i < methodParameterInfos.Length; i++)
                        il.Emit(OpCodes.Ldarg, (i + 1));
                    il.Emit(OpCodes.Callvirt, method);

                    il.Emit(OpCodes.Stloc_0);
                    var defineLabel = il.DefineLabel();
                    il.Emit(OpCodes.Br_S, defineLabel);
                    il.MarkLabel(defineLabel);
                    il.Emit(OpCodes.Ldloc_0);
                    il.Emit(OpCodes.Ret);
                }

                tb.DefineMethodOverride(methodBuilder, method);
            }
        }

        #endregion

        return tb.CreateType();
    }
}

Avec notre nouvelle classe d'aide, nous pouvons maintenant écrire

var channelFactory = new ChannelFactory<IMyService>("");

var serviceHelper = new ServiceHelper<IMyService>(channelFactory);
var proxy = serviceHelper.CreateChannel();
using (proxy as IDisposable)
{
    proxy.DoWork();
}

Notez que vous pouvez également utiliser la même technique (avec de légères modifications) pour les clients générés automatiquement héritant pour ClientBase<> (au lieu d'utiliser ChannelFactory<>), ou si vous souhaitez utiliser une implémentation différente de IDisposable fermer votre canal.

J'aime cette façon de connexion de fermeture:

var client = new ProxyClient();
try
{
    ...
    client.Close();
}
finally
{
    if(client.State != CommunicationState.Closed)
        client.Abort();
}

Je l'ai écrit une simple classe de base qui gère cela. Il est disponible en tant que package NuGet et il est très facile à utiliser.

//MemberServiceClient is the class generated by SvcUtil
public class MemberServiceManager : ServiceClientBase<MemberServiceClient>
{
    public User GetUser(int userId)
    {
        return PerformServiceOperation(client => client.GetUser(userId));
    }

    //you can also check if any error occured if you can't throw exceptions       
    public bool TryGetUser(int userId, out User user)
    {
        return TryPerformServiceOperation(c => c.GetUser(userId), out user);
    }
}
public static class Service<TChannel>
{
    public static ChannelFactory<TChannel> ChannelFactory = new ChannelFactory<TChannel>("*");

    public static TReturn Use<TReturn>(Func<TChannel,TReturn> codeBlock)
    {
        var proxy = (IClientChannel)ChannelFactory.CreateChannel();
        var success = false;
        try
        {
            var result = codeBlock((TChannel)proxy);
            proxy.Close();
            success = true;
            return result;
        }
        finally
        {
            if (!success)
            {
                proxy.Abort();
            }
        }
    }
}

Il permet d'écrire des déclarations de retour bien:

return Service<IOrderService>.Use(orderService => 
{ 
    return orderService.PlaceOrder(request); 
}); 

Pour les intéressés, voici une traduction VB.NET de la réponse acceptée (ci-dessous). Je l'ai affiné un peu par souci de concision, combinant quelques-uns des conseils par d'autres dans ce fil.

Je reconnais qu'il est hors-sujet pour les étiquettes d'origine (C #), mais comme je ne pouvais pas trouver une version VB.NET de cette solution bien je suppose que d'autres seront à la recherche aussi bien. La traduction Lambda peut être un peu difficile, donc je voudrais sauver quelqu'un la peine.

Notez que cette mise en œuvre particulière permet de configurer le ServiceEndpoint lors de l'exécution.


Code:

Namespace Service
  Public NotInheritable Class Disposable(Of T)
    Public Shared ChannelFactory As New ChannelFactory(Of T)(Service)

    Public Shared Sub Use(Execute As Action(Of T))
      Dim oProxy As IClientChannel

      oProxy = ChannelFactory.CreateChannel

      Try
        Execute(oProxy)
        oProxy.Close()

      Catch
        oProxy.Abort()
        Throw

      End Try
    End Sub



    Public Shared Function Use(Of TResult)(Execute As Func(Of T, TResult)) As TResult
      Dim oProxy As IClientChannel

      oProxy = ChannelFactory.CreateChannel

      Try
        Use = Execute(oProxy)
        oProxy.Close()

      Catch
        oProxy.Abort()
        Throw

      End Try
    End Function



    Public Shared ReadOnly Property Service As ServiceEndpoint
      Get
        Return New ServiceEndpoint(
          ContractDescription.GetContract(
            GetType(T),
            GetType(Action(Of T))),
          New BasicHttpBinding,
          New EndpointAddress(Utils.WcfUri.ToString))
      End Get
    End Property
  End Class
End Namespace

Utilisation:

Public ReadOnly Property Jobs As List(Of Service.Job)
  Get
    Disposable(Of IService).Use(Sub(Client) Jobs = Client.GetJobs(Me.Status))
  End Get
End Property

Public ReadOnly Property Jobs As List(Of Service.Job)
  Get
    Return Disposable(Of IService).Use(Function(Client) Client.GetJobs(Me.Status))
  End Get
End Property

J'ai mon propre enveloppe pour un canal qui met en oeuvre Dispose comme suit:

public void Dispose()
{
        try
        {
            if (channel.State == CommunicationState.Faulted)
            {
                channel.Abort();
            }
            else
            {
                channel.Close();
            }
        }
        catch (CommunicationException)
        {
            channel.Abort();
        }
        catch (TimeoutException)
        {
            channel.Abort();
        }
        catch (Exception)
        {
            channel.Abort();
            throw;
        }
}

Cela semble bien fonctionner et permet à un bloc à l'aide à utiliser.

L'aide suivante permet d'appeler void et les méthodes non vides. Utilisation:

var calculator = new WcfInvoker<CalculatorClient>(() => new CalculatorClient());
var sum = calculator.Invoke(c => c.Sum(42, 42));
calculator.Invoke(c => c.RebootComputer());

La classe elle-même est:

public class WcfInvoker<TService>
    where TService : ICommunicationObject
{
    readonly Func<TService> _clientFactory;

    public WcfInvoker(Func<TService> clientFactory)
    {
        _clientFactory = clientFactory;
    }

    public T Invoke<T>(Func<TService, T> action)
    {
        var client = _clientFactory();
        try
        {
            var result = action(client);
            client.Close();
            return result;
        }
        catch
        {
            client.Abort();
            throw;
        }
    }

    public void Invoke(Action<TService> action)
    {
        Invoke<object>(client =>
        {
            action(client);
            return null;
        });
    }
}

Remplacer Dispose () du client sans qu'il soit nécessaire de générer une classe proxy basée sur ClientBase, également sans la nécessité de gérer la création de canal et de cache! (Notez que WcfClient n'est pas une classe abstraite et est basée sur ClientBase)

// No need for a generated proxy class
//using (WcfClient<IOrderService> orderService = new WcfClient<IOrderService>())
//{
//    results = orderService.GetProxy().PlaceOrder(input);
//}

public class WcfClient<TService> : ClientBase<TService>, IDisposable
    where TService : class
{
    public WcfClient()
    {
    }

    public WcfClient(string endpointConfigurationName) :
        base(endpointConfigurationName)
    {
    }

    public WcfClient(string endpointConfigurationName, string remoteAddress) :
        base(endpointConfigurationName, remoteAddress)
    {
    }

    public WcfClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :
        base(endpointConfigurationName, remoteAddress)
    {
    }

    public WcfClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
        base(binding, remoteAddress)
    {
    }

    protected virtual void OnDispose()
    {
        bool success = false;

        if ((base.Channel as IClientChannel) != null)
        {
            try
            {
                if ((base.Channel as IClientChannel).State != CommunicationState.Faulted)
                {
                    (base.Channel as IClientChannel).Close();
                    success = true;
                }
            }
            finally
            {
                if (!success)
                {
                    (base.Channel as IClientChannel).Abort();
                }
            }
        }
    }

    public TService GetProxy()
    {
        return this.Channel as TService;
    }

    public void Dispose()
    {
        OnDispose();
    }
}

Ma méthode de le faire a été de créer une classe héritée qui implémente explicitement IDisposable. Ceci est utile pour les gens qui utilisent l'interface utilisateur graphique pour ajouter la référence de service (Ajouter un service de référence). Je viens de déposer cette classe dans le projet de renvoi de service et l'utiliser à la place du client par défaut:

using System;
using System.ServiceModel;
using MyApp.MyService; // The name you gave the service namespace

namespace MyApp.Helpers.Services
{
    public class MyServiceClientSafe : MyServiceClient, IDisposable
    {
        void IDisposable.Dispose()
        {
            if (State == CommunicationState.Faulted)
            {
                Abort();
            }
            else if (State != CommunicationState.Closed)
            {
                Close();
            }

            // Further error checks and disposal logic as desired..
        }
    }
}

Note: Ceci est juste une implémentation simple de disposer, vous pouvez mettre en œuvre la logique de disposer plus complexe si vous aimez

.

Vous pouvez remplacer tous vos appels avec le client de service régulier avec les clients sûrs, comme ceci:

using (MyServiceClientSafe client = new MyServiceClientSafe())
{
    var result = client.MyServiceMethod();
}

J'aime cette solution car il ne me pas besoin d'avoir accès aux définitions d'interfaces et je peux utiliser l'instruction using que je me attends tout en laissant mon code à regarder de plus ou moins la même chose.

Vous aurez toujours besoin de gérer les exceptions qui peuvent être projetés comme indiqué dans d'autres commentaires sur ce sujet.

Vous pouvez également utiliser un DynamicProxy pour étendre la méthode Dispose(). De cette façon, vous pouvez faire quelque chose comme:

using (var wrapperdProxy = new Proxy<yourProxy>())
{
   // Do whatever and dispose of Proxy<yourProxy> will be called and work properly.
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top