web-dev-qa-db-fra.com

Quelle est la meilleure solution de contournement pour le problème de blocage `using` du client WCF?

J'aime instancier mes clients du service WCF dans un bloc using, car il s’agit plus ou moins du moyen standard d’utiliser des ressources qui implémentent IDisposable:

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

Mais, comme indiqué dans cet article MSDN , emballer un client WCF dans un bloc using pourrait masquer les erreurs entraînant le client à rester dans un état défaillant (comme un problème de délai ou de communication ). En résumé, lorsque Dispose () est appelée, la méthode Close () du client se déclenche, mais renvoie une erreur car elle est dans un état défaillant. L'exception d'origine est alors masquée par la deuxième exception. Pas bon.

La solution suggérée dans l'article MSDN consiste à éviter complètement l'utilisation d'un bloc using, mais à instancier vos clients et à les utiliser comme suit:

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

Comparé au bloc using, je trouve ça moche. Et beaucoup de code à écrire à chaque fois que vous avez besoin d'un client.

Heureusement, j'ai trouvé quelques solutions de contournement, telles que celle 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 que ce soit aussi expressif et compréhensible que le bloc using.

La solution que j'essaie actuellement d'utiliser a d'abord été lue à propos de blog.davidbarret.net . Fondamentalement, vous substituez la méthode Dispose() du client partout 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 pouvoir autoriser le bloc using à nouveau sans risque de masquer une exception d'état défaillante.

Alors, y a-t-il d'autres pièges que je dois surveiller pour utiliser ces solutions de contournement? Est-ce que quelqu'un a trouvé quelque chose de mieux?

394
Eric King

En fait, bien que je blogué (voir réponse de Luke ), je pense que this est meilleur que mon emballage indélébile. Code typique:

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

(éditer par commentaires)

Puisque Use renvoie un vide, le moyen le plus simple de gérer les valeurs de retour consiste à utiliser 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
131
Marc Gravell

Étant donné le choix entre la solution préconisée par IServiceOriented.com et la solution préconisée par blog de David Barret , je préfère la simplicité offerte par le remplacement de la méthode Dispose () du client. Cela me permet de continuer à utiliser l'instruction using () comme on pourrait s'y attendre avec un objet jetable. Cependant, comme l'a souligné @Brian, cette solution contient une condition de concurrence critique en ce sens que l'État ne peut être blâmé lors de la vérification mais qu'il peut l'être au moment où Close () est appelé, auquel cas l'exception CommunicationException se produit toujours.

Alors, pour résoudre ce problème, j'ai utilisé 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();
    }
}
87
Matt Davis

J'ai écrit un fonction d'ordre supérieur pour que cela fonctionne correctement. Nous l'avons utilisé dans plusieurs projets et cela semble bien fonctionner. C’est ainsi que les choses auraient dû être faites depuis le début, sans le paradigme "d’utilisation" ou autre.

TReturn UseService<TChannel, TReturn>(Func<TChannel, TReturn> code)
{
    var chanFactory = GetCachedFactory<TChannel>();
    TChannel channel = chanFactory.CreateChannel();
    bool error = true;
    try {
        TReturn result = code(channel);
        ((IClientChannel)channel).Close();
        error = false;
        return result;
    }
    finally {
        if (error) {
            ((IClientChannel)channel).Abort();
        }
    }
}

Vous pouvez faire des appels comme ceci:

int a = 1;
int b = 2;
int sum = UseService((ICalculator calc) => calc.Add(a, b));
Console.WriteLine(sum);

C'est à peu près comme vous avez dans votre exemple. Dans certains projets, nous écrivons des méthodes d'assistance fortement typées, nous finissons donc par écrire des choses comme "Wcf.UseFooService (f => f ...)".

Je le trouve assez élégant, tout compte fait. Avez-vous rencontré un problème particulier?

Cela permet de brancher d’autres fonctionnalités intéressantes. Par exemple, sur un site, le site s’authentifie auprès du service pour le compte de l’utilisateur connecté. (Le site n'a aucune information d'identification par lui-même.) En écrivant notre propre assistant de méthode "UseService", nous pouvons configurer la fabrique de canaux comme nous le souhaitons, etc. .

31
MichaelGG

C’est la méthode recommandée par Microsoft pour gérer les appels du client WCF:

Pour plus de détails, voir: Exceptions attendues

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 Tant de gens semblent poser cette question à la WCF et Microsoft a même créé un exemple dédié pour montrer comment gérer les exceptions:

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

Téléchargez l'exemple: C # ou VB

Considérant qu'il y a tellement de problèmes impliquant la déclaration using , (chauffée?) Discussions internes et discussions sur cette question, je ne vais pas perdre mon temps à essayer de devenir un cow-boy de code et à trouver un moyen plus propre. Je vais me contenter d'implémenter les clients WCF de cette manière prolixe (mais fiable) pour mes applications serveur.

Autres échecs facultatifs à intercepter

Beaucoup d'exceptions proviennent de CommunicationException et je ne pense pas que la plupart de ces exceptions devraient être retentées. J'ai parcouru chaque exception sur MSDN et trouvé une courte liste d'exceptions ré-essayables (en plus de TimeOutException ci-dessus). Faites-moi savoir si j'ai manqué une exception qui devrait être retentée.

  // The following is typically thrown on the client when a channel is terminated due to the server closing the connection.
catch (ChannelTerminatedException cte)
{
secureSecretService.Abort();
// todo: Implement delay (backoff) and retry
}

// 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)
{
secureSecretService.Abort();
// todo: Implement delay (backoff) and retry
}

// The following exception that is thrown when a server is too busy to accept a message.
catch (ServerTooBusyException stbe)
{
secureSecretService.Abort();
// todo: Implement delay (backoff) and retry
}

Certes, c'est un peu de code banal à écrire. Je préfère actuellement cette réponse , et je ne vois dans ce code aucun "piratage" qui puisse causer des problèmes par la suite.

28
goodguys_activate

J'ai enfin trouvé des étapes solides vers une solution propre à ce problème.

Cet outil personnalisé étend WCFProxyGenerator pour fournir un proxy de traitement des exceptions. Il génère un proxy supplémentaire appelé ExceptionHandlingProxy<T>, qui hérite de ExceptionHandlingProxyBase<T>, ce dernier implémentant le contenu de base de la fonctionnalité du proxy. Le résultat est que vous pouvez choisir d'utiliser le proxy par défaut qui hérite de ClientBase<T> ou ExceptionHandlingProxy<T> qui encapsule la gestion de la durée de vie de la fabrique de canaux et du canal. ExceptionHandlingProxy respecte vos sélections dans la boîte de dialogue Ajouter une référence de service en ce qui concerne les méthodes asynchrones et les types de collection.

Codeplex a un projet appelé Gestion des exceptions, générateur de proxy WCF . Il installe essentiellement un nouvel outil personnalisé dans Visual Studio 2008, puis utilise cet outil pour générer le nouveau proxy de service (Ajouter une référence de service) . Il a quelques fonctionnalités intéressantes pour traiter les canaux défectueux, les délais d’expiration et l’élimination en toute sécurité. Il y a une excellente vidéo ici appelée ExceptionHandlingProxyWrapper expliquant exactement comment cela fonctionne.

Vous pouvez utiliser à nouveau l'instruction Using en toute sécurité et si le canal est défaillant pour toute requête (TimeoutException ou CommunicationException), le wrapper ré-initialisera le canal défaillant et relancera la requête. Si cela échoue, il appelle la commande Abort(), supprime le proxy et rediffuse l'exception. Si le service lève un code FaultException, il cessera de s'exécuter et le proxy sera abandonné en toute sécurité en lançant l'exception correcte comme prévu.

14
Neil

Sur la base des réponses de Marc Gravell, MichaelGG et Matt Davis, nos développeurs ont proposé ce qui suit:

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

C'est aussi proche que possible de la syntaxe "using", vous n'avez pas à renvoyer de valeur factice lorsque vous appelez une méthode void, et vous pouvez effectuer plusieurs appels au service (et renvoyer plusieurs valeurs) sans avoir à utiliser des n-uplets.

En outre, vous pouvez utiliser ceci avec ClientBase<T> descendants au lieu de ChannelFactory si vous le souhaitez.

La méthode d'extension est exposée si un développeur souhaite disposer manuellement d'un proxy/canal.

10
TrueWill

@ Marc Gravell

Ne serait-il pas acceptable 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, la même chose (Func<T, TResult>) en cas de Service<IOrderService>.Use

Cela faciliterait le retour des variables.

8
pangular

Qu'est-ce que c'est?

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

La réponse acceptée fait référence à ce site Web n’est plus disponible . Pour vous éviter des ennuis, j'inclus ici les parties les plus pertinentes. De plus, je l'ai légèrement modifié pour inclure traitement des tentatives d'exception afin de gérer ces délais d'attente fastidieux sur le réseau.

Utilisation simple du client WCF

Une fois que vous avez généré votre proxy côté client, vous n’avez plus qu’à le mettre en œuvre.

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

ServiceDelegate.cs

Ajoutez ce fichier à votre solution. Aucune modification n'est requise dans ce fichier, sauf si vous souhaitez modifier le nombre de tentatives ou les exceptions que vous souhaitez 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: J'ai fait de ce post un wiki de communauté. Je ne collectionnerai pas les "points" de cette réponse, mais préférez-le à nouveau voter si vous êtes d'accord avec l'implémentation ou à le modifier pour le rendre meilleur.

7
goodguys_activate

Vous trouverez ci-dessous une version améliorée de la source de la question et étendue pour mettre en cache plusieurs fabriques de canaux et tenter de rechercher le noeud final dans le fichier de configuration par nom de contrat.

Il utilise .NET 4 (spécifiquement: 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.");
    }
}
7
Jesse C. Slicer

Un emballage comme celui-ci 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.

Le wrapper pourrait bien entendu capturer plus d'exceptions si cela est requis, mais le principe reste le même.

5
Tomas Jansson

Si vous n'avez pas besoin de IoC ou utilisez un client généré automatiquement (référence du service), vous pouvez simplement utiliser un wrapper pour gérer la fermeture et laisser le GC la base client quand elle est dans un état sûr ne lève aucune exception. Le GC appellera Dispose dans serviceclient, et ceci appellera Close. Comme il est déjà fermé, il ne peut causer aucun dommage. J'utilise ceci 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 la reconnaissance automatique:

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

    Ws.Open();

    Ws.Test();
}
4
Luiz Felipe

J'ai utilisé le proxy dynamique Castle pour résoudre le problème de Dispose () et j'ai également implémenté l'actualisation automatique du canal lorsqu'il est inutilisable. Pour utiliser cela, vous devez créer une nouvelle interface qui hérite de votre contrat de service et d'IDisposable. Le proxy dynamique implémente cette interface et encapsule 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 aient à se soucier des détails de WCF. Et il n'y a pas plus cruellement ajouté comme les autres solutions.

Regardez le code, il est en fait assez simple: proxy dynamique WCF

4
Jay Douglass

Utilisez 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;
    }
}
3
Johan Nyman

Sommaire

En utilisant les techniques décrites dans cette réponse, on peut utiliser un service WCF dans un bloc using avec 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 cela encore davantage pour obtenir un modèle de programmation plus concis et adapté à votre situation - mais le fait est que nous pouvons créer une implémentation de IMyService représentant le canal qui implémente correctement le modèle à usage unique.


Détails

Toutes les réponses données jusqu'ici traitent du problème de contournement du "bogue" dans la mise en oeuvre du canal WCF de IDisposable. La réponse qui semble offrir le modèle de programmation le plus concis (vous permettant d'utiliser le bloc using pour disposer sur des ressources non gérées) est celui-ci - où le proxy est modifié pour implémenter IDisposable avec une implémentation sans bug. Le problème avec cette approche est la maintenabilité - nous devons ré-implémenter cette fonctionnalité pour chaque proxy que nous utilisons. Sur une variante de cette réponse, nous verrons comment utiliser la composition plutôt que l’héritage pour rendre cette technique générique.

Premier essai

Il semble y avoir différentes implémentations pour l'implémentation IDisposable, mais pour les besoins de l'argumentation, nous utiliserons une adaptation de celle utilisée par = réponse actuellement acceptée .

[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 en utilisant le bloc using:

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

Faire de ce générique

Tout ce que nous avons fait jusqu’à présent est de reformuler solution de Tomas . Ce qui empêche ce code d’être générique, c’est le fait que la classe ProxyWrapper doit être réimplémentée pour chaque contrat de service souhaité. Nous allons maintenant regarder une classe qui nous permet de créer ce type dynamiquement en utilisant 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'assistance, 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 qui héritent pour ClientBase<> (au lieu d'utiliser ChannelFactory<>), ou si vous souhaitez utiliser une implémentation différente de IDisposable pour fermer votre chaîne.

3
Lawrence

J'aime cette façon de fermer la connexion:

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

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, en combinant certains des conseils donnés par d'autres dans ce fil.

J'admets que cela est hors sujet pour les balises d'origine (C #), mais comme je n'ai pas été en mesure de trouver une version VB.NET de cette solution de qualité, je suppose que d'autres rechercheront également. La traduction Lambda peut être un peu délicate, alors je voudrais éviter à quelqu'un les ennuis.

Notez que cette implémentation particulière offre la possibilité de configurer le ServiceEndpoint au moment 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

tilisation:

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

Cela permet donc d’écrire joliment des instructions de retour:

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

Notre architecture système utilise souvent le cadre nityIoC pour créer des instances de ClientBase. Il est donc impossible de faire en sorte que les autres développeurs utilisent même des blocs using{}. Afin de le rendre aussi infaillible que possible, j'ai créé cette classe personnalisée qui étend ClientBase et gère la fermeture du canal lors de la suppression ou de la finalisation au cas où quelqu'un ne disposerait pas explicitement de l'instance créée par Unity.

Il y a aussi des choses à faire dans le constructeur pour configurer le canal pour les informations d'identification personnalisées, donc c'est ici aussi.

public abstract class PFServer2ServerClientBase<TChannel> : ClientBase<TChannel>, IDisposable where TChannel : class
{
    private bool disposed = false;

    public PFServer2ServerClientBase()
    {
        // Copy information from custom identity into credentials, and other channel setup...
    }

    ~PFServer2ServerClientBase()
    {
        this.Dispose(false);
    }

    void IDisposable.Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }

    public void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            try
            {
                    if (this.State == CommunicationState.Opened)
                        this.Close();
            }
            finally
            {
                if (this.State == CommunicationState.Faulted)
                    this.Abort();
            }
            this.disposed = true;
        }
    }
}

Ensuite, un client peut simplement:

internal class TestClient : PFServer2ServerClientBase<ITest>, ITest
{
    public string TestMethod(int value)
    {
        return base.Channel.TestMethod(value);
    }
}

Et l'appelant peut faire n'importe lequel de ces tâches:

public SomeClass
{
    [Dependency]
    public ITest test { get; set; }

    // Not the best, but should still work due to finalizer.
    public string Method1(int value)
    {
        return this.test.TestMethod(value);
    }

    // The good way to do it
    public string Method2(int value)
    {
        using(ITest t = unityContainer.Resolve<ITest>())
        {
            return t.TestMethod(value);
        }
    }
}
1
CodingWithSpike

J'ai écrit ne classe de base simple qui gère cela. Il est disponible sous forme de paquet NuGet et il est assez 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);
    }
}
1

J'aimerais ajouter l'implémentation de Service from réponse de Marc Gravell pour utiliser ServiceClient à la place de ChannelFactory.

public interface IServiceConnector<out TServiceInterface>
{
    void Connect(Action<TServiceInterface> clientUsage);
    TResult Connect<TResult>(Func<TServiceInterface, TResult> channelUsage);
}

internal class ServiceConnector<TService, TServiceInterface> : IServiceConnector<TServiceInterface>
    where TServiceInterface : class where TService : ClientBase<TServiceInterface>, TServiceInterface, new()
{
    public TResult Connect<TResult>(Func<TServiceInterface, TResult> channelUsage)
    {
        var result = default(TResult);
        Connect(channel =>
        {
            result = channelUsage(channel);
        });
        return result;
    }

    public void Connect(Action<TServiceInterface> clientUsage)
    {
        if (clientUsage == null)
        {
            throw new ArgumentNullException("clientUsage");
        }
        var isChanneldClosed = false;
        var client = new TService();
        try
        {
            clientUsage(client);
            client.Close();
            isChanneldClosed = true;
        }
        finally
        {
            if (!isChanneldClosed)
            {
                client.Abort();
            }
        }
    }
}
1
PSsam

Ma méthode a consisté à créer une classe héritée qui implémente explicitement IDisposable. Ceci est utile pour les personnes qui utilisent l'interface graphique pour ajouter la référence de service (Ajouter une référence de service). Je viens de déposer cette classe dans le projet faisant la référence de service et de 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..
        }
    }
}

Remarque: Ceci est juste une simple implémentation de dispose, vous pouvez mettre en œuvre une logique de disposition plus complexe si vous le souhaitez.

Vous pouvez ensuite remplacer tous vos appels passés avec le client du service régulier par les clients sécurisés, comme ceci:

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

J'aime cette solution car elle ne nécessite pas l'accès aux définitions d'interface et je peux utiliser l'instruction using comme je le souhaiterais tout en permettant à mon code de se ressembler plus ou moins.

Vous aurez toujours besoin de gérer les exceptions qui peuvent être levées comme indiqué dans d'autres commentaires de ce fil.

0
Aleksandr Albert

J'ai référé quelques réponses sur ce post et l'ai personnalisé selon mes besoins.

Je voulais pouvoir faire quelque chose avec le client WCF avant de l'utiliser, donc la méthode DoSomethingWithClient().

public interface IServiceClientFactory<T>
{
    T DoSomethingWithClient();
}
public partial class ServiceClient : IServiceClientFactory<ServiceClient>
{
    public ServiceClient DoSomethingWithClient()
    {
        var client = this;
        // do somthing here as set client credentials, etc.
        //client.ClientCredentials = ... ;
        return client;
    }
}

Voici la classe d'assistance:

public static class Service<TClient>
    where TClient : class, ICommunicationObject, IServiceClientFactory<TClient>, new()
{
    public static TReturn Use<TReturn>(Func<TClient, TReturn> codeBlock)
    {
        TClient client = default(TClient);
        bool success = false;
        try
        {
            client = new TClient().DoSomethingWithClient();
            TReturn result = codeBlock(client);
            client.Close();
            success = true;
            return result;
        }
        finally
        {
            if (!success && client != null)
            {
                client.Abort();
            }
        }
    }
}

Et je peux l'utiliser comme:

string data = Service<ServiceClient>.Use(x => x.GetData(7));
0
hIpPy

L'assistant suivant permet d'appeler void et des méthodes non vides. Usage:

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;
        });
    }
}
0
Konstantin Spirin

Remplacez Dispose () du client sans qu'il soit nécessaire de générer une classe proxy basée sur ClientBase, également sans avoir besoin de gérer la création et la mise en cache de canaux ! (Notez que WcfClient n'est pas une classe ABSTRACT et est basé 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();
    }
}
0
Murad Duraidi

J'ai mon propre wrapper pour un canal qui implémente 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 d'utiliser un bloc d'utilisation.

0
Joe