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

J'aime instancier mes clients de service WCF dans un bloc using car c'est à peu près la façon standard d'utiliser les ressources qui implémentent IDisposable:

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

Mais, comme indiqué dans cet article MSDN , l'encapsulation d'un client WCF dans un bloc using pourrait masquer toute erreur entraînant un défaut du client (comme un délai d'attente ou un problème de communication). En bref, lorsque Dispose () est appelé, la méthode Close() du client se déclenche, mais génère une erreur car elle est dans un reproché à l'etat. L'exception d'origine est ensuite masquée par la deuxième exception. Pas bonne.

La solution de contournement suggérée dans L'article MSDN est d'éviter complètement d'utiliser un bloc using, et d'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 moche. Et beaucoup de code à écrire chaque fois que vous avez besoin d'un client.

Heureusement, j'ai trouvé quelques autres solutions de contournement, comme celle-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(); 
            } 
        } 
     } 
} 

, 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 facilement compréhensible que le bloc using.

La solution de contournement que j'essaie actuellement d'utiliser, j'ai d'abord lu sur blog.davidbarret.net . fondamentalement, vous remplacez la méthode Dispose() du client où que vous l'utilisiez. 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 à nouveau le bloc using sans risquer 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? A quelqu'un de venir avec quelque chose de mieux?

380
demandé sur live2 2009-02-22 02:02:24

26 réponses

En fait, bien que j'aie blogué (Voirla réponse de Luke ), je pense queCE est meilleur que mon wrapper IDisposable. Code typique:

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

(Modifier par Commentaires)

Puisque Use renvoie void, la façon la plus simple de gérer les valeurs de retour est via 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
127
répondu Marc Gravell 2017-05-23 12:10:12

Étant donné le choix entre la solution préconisée par IServiceOriented.com et la solution préconisée par le blog de David Barret , je préfère la simplicité offerte par la substitution 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 @ Brian l'a souligné, cette solution contient une condition de concurrence en ce sens que l'état peut ne pas être défectueux quand il est vérifié mais peut l'être au moment où Close() est appelé, dans auquel cas L'Exception CommunicationException se produit toujours.

Donc, pour contourner ce problème, j'ai utilisé une solution qui allie le meilleur des deux mondes.

void IDisposable.Dispose()
{
    bool success = false;
    try 
    {
        if (State != CommunicationState.Faulted) 
        {
            Close();
            success = true;
        }
    } 
    finally 
    {
        if (!success) 
            Abort();
    }
}
84
répondu Matt Davis 2017-06-17 18:48:03

J'ai écrit une fonction d'ordre supérieur pour que cela fonctionne correctement. Nous l'avons utilisé dans plusieurs projets et cela semble très bien fonctionner. C'est ainsi que les choses auraient dû être faites dès le début, sans le paradigme "using" OU ainsi de suite.

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 l'avez dans votre exemple. Dans certains projets, nous écrivons des méthodes d'aide fortement typées, donc nous finissons par écrire des choses comme "Wcf.UseFooService(f=>f...)".

Je le trouve très élégant, toutes choses considérées. Est-il un problème particulier vous rencontrés?

Cela permet de brancher d'autres fonctionnalités astucieuses. Par exemple, sur un site, le site s'authentifie sur le service au nom de l'utilisateur connecté. (Le site n'a pas d'informations d'identification par lui-même.) En écrivant notre propre assistant de méthode" UseService", nous pouvons configurer l'usine de canal comme nous le voulons, etc. Nous ne sommes pas non plus tenus d'utiliser les proxies générés-n'importe quelle interface fera l'affaire.

28
répondu MichaelGG 2016-10-10 06:21:24

C'est la manière recommandée par Microsoft de 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 supplémentaires Tant de gens semblent poser cette question sur WCF que Microsoft a même créé un exemple dédié pour démontrer comment gérer les exceptions:

C:\WF_WCF_Samples\WCF\Basic\Client\ExpectedExceptions\CS\client

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

Considérant que il y a tellement de problèmes impliquant l'instruction using, (chauffé?) Discussions internes et threads sur cette question, Je ne vais pas perdre mon temps à essayer de devenir un cow-boy de code et de trouver un moyen plus propre. Je vais juste le sucer, et implémenter les clients WCF de cette manière verbeuse (mais fiable) pour mes applications serveur.

Facultatif Échecs supplémentaires à attraper

De nombreuses exceptions dérivent de CommunicationException et je ne pense pas que la plupart de ces exceptions devraient une nouvelle tentative. 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 rejugé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 ne vois pas de "hacks" dans ce code qui peuvent causer des problèmes sur la route.

26
répondu random65537 2017-05-23 11:47:24

J'ai finalement trouvé quelques étapes 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é {[0] } qui hérite de ExceptionHandlingProxyBase<T> - ce dernier implémente 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 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 WCF proxy Generator . Il installe essentiellement un nouvel outil personnalisé à 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 quelques fonctionnalités intéressantes pour traiter les canaux défectueux, les délais d'attente 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 à nouveau utiliser l'instruction Using en toute sécurité, et si le canal est défectueux sur une requête (TimeoutException ou CommunicationException), le Wrapper réinitialisera le canal défectueux et réessayera la requête. Si cela échoue, il appellera la commande Abort() et disposera du proxy et repoussera l'Exception. Si le service lance 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
répondu Neil 2015-07-26 16:40:58

Sur la base des réponses de Marc Gravell, MichaelGG, et Matt Davis, nos développeurs sont venus avec 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 de la syntaxe "using" que possible, vous n'avez pas besoin de renvoyer une valeur fictive lors de l'appel d'une méthode void, et vous pouvez effectuer plusieurs appels au service (et renvoyer plusieurs valeurs) sans avoir à utiliser des tuples.

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

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

10
répondu TrueWill 2012-08-24 20:41:27

@Marc Gravel

Ne serait-il pas correct 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>) dans le cas de Service<IOrderService>.Use

Cela rendrait les variables de retour plus faciles.

8
répondu codeRecap 2013-05-02 15:16:56

Qu'est-Ce que cela?

C'est la version CW de la réponse acceptée mais avec (ce que je considère comme complet) la gestion des exceptions incluse.

La réponse acceptée fait référence à ce site web qui n'est plus autour de . Pour vous épargner des ennuis, j'inclus les parties les plus pertinentes ici. De plus, je l'ai légèrement modifié pour inclure exception retry handling pour gérer ces délais d'attente réseau embêtants.

Utilisation simple du Client WCF

Une fois vous générez votre proxy côté client, c'est tout ce dont vous avez besoin pour l'implémenter.

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

ServiceDelegate.cs

Ajoutez ce fichier à votre solution. Aucune modification n'est nécessaire pour ce fichier, sauf si vous souhaitez modifier le nombre de tentatives ou des 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 communautaire. Je ne collecterai pas de" points " de cette réponse, mais je préfère que vous la votiez si vous êtes d'accord avec l'implémentation, ou que vous la modifiez pour l'améliorer.

7
répondu LamonteCristo 2017-05-23 10:31:11

Ci-dessous est une version améliorée de la source de la question et étendue pour mettre en cache plusieurs usines de canaux et tenter de rechercher le point de terminaison 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
répondu Jesse C. Slicer 2017-05-23 12:03:02

Un wrapper 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.

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

5
répondu Tomas Jansson 2015-07-26 16:43:08

J'ai utilisé Castle dynamic proxy pour résoudre le problème Dispose (), et j'ai également implémenté l'actualisation automatique du canal lorsqu'il est dans un état inutilisable. Pour l'utiliser, vous devez créer une nouvelle interface qui hérite de votre contrat de service et IDisposable. Le proxy dynamique implémente cette interface et enveloppe 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 puisque vous pouvez injecter des services WCF sans que les consommateurs aient à s'inquiéter des détails de WCF. Et il n'y a pas de cruft ajouté comme l'autre solution.

Jetez un oeil au code, c'est en fait assez simple: WCF Proxy dynamique

4
répondu Jay Douglass 2012-01-06 05:29:58

Si vous n'avez pas besoin de IoC ou si vous utilisez un client généré automatiquement (référence de Service), vous pouvez simplement utiliser un wrapper pour gérer la fermeture et laisser le GC prendre le clientbase quand il est dans un état sûr qui ne lancera aucune exception. Le GC appellera Dispose dans serviceclient, et cela appellera Close. Comme il est déjà fermé, il ne peut causer aucun dommage. Je l'utilise 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;
        }
    }
}

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

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

    Ws.Open();

    Ws.Test();
}
4
répondu Luiz Felipe 2015-07-26 16:53:10

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;
    }
}
3
répondu Johan Nyman 2013-12-09 08:47:10

Résumé

En Utilisant les techniques décrites dans cette réponse, on peut consommer 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 l'adapter encore plus pour obtenir un modèle de programmation plus concis spécifique à 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 jetable.


Détails

Toutes les réponses données jusqu'à présent résoudre le problème de contourner le "bug" dans l'implémentation 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 de 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 jamais proxy que nous utilisons. Sur une variante de cette réponse, nous verrons comment nous pouvons utiliser composition plutôt que l'héritage pour rendre cette technique Générique.

Première Tentative

Il semble y avoir différentes implémentations pour l'implémentation IDisposable, mais par souci d'argument, Nous utiliserons une adaptation de celle utilisée par la 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é 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 ceci générique

Tout ce que nous avons fait jusqu'à présent est de reformuler la 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 que nous voulons. 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'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ériter pour ClientBase<> (au lieu d'utiliser ChannelFactory<>), ou si vous voulez utiliser une implémentation différente de IDisposable pour fermer votre canal.

3
répondu Lawrence 2017-05-23 12:26:07

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
répondu Uriil 2015-10-07 07:07:59

J'ai écrit une simple classe de base qui gère cela. Il est disponible en tant que 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
répondu Ufuk Hacıoğulları 2013-01-15 22:20:54
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();
            }
        }
    }
}

Donc, il permet d'écrire des instructions de retour bien:

return Service<IOrderService>.Use(orderService => 
{ 
    return orderService.PlaceOrder(request); 
}); 
1
répondu Andriy Buday 2013-05-08 16:22:56

Je voudrais ajouter l'implémentation du Service à partir de la réponse de Marc Gravell pour le cas d'utilisation de ServiceClient au lieu 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
répondu PSsam 2017-05-23 12:03:02

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

J'admets que c'est hors sujet pour les balises d'origine (C#), mais comme je n'ai pas pu trouver un VB.NET version de cette bonne solution, je suppose que d'autres seront à la recherche aussi bien. La traduction Lambda peut être un peu délicate, donc je voudrais sauver quelqu'un de la peine.

Notez que cette implémentation 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
1
répondu InteXX 2015-02-07 04:59:56

Notre architecture système utilise souvent l'Unité IOC framework pour créer des instances de ClientBase, il n'y a donc aucun moyen sûr 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 sur dispose, ou sur finalize au cas où quelqu'un ne dispose pas explicitement de l'instance créée Unity.

Il y a aussi des choses qui devaient être faites dans le constructeur de configurer le canal de badges personnalisés et des trucs, 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;
        }
    }
}

Alors 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 l'un de ces:

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
répondu CodingWithSpike 2015-07-26 16:36:19

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

Je voulais la possibilité de 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'AIDE:

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
répondu hIpPy 2013-08-16 06:53:23

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 using.

0
répondu Joe 2014-01-15 12:13:46

L'Assistant suivant permet d'appeler des méthodes void et non-void. 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;
        });
    }
}
0
répondu Konstantin Spirin 2014-12-23 01:48:35

Remplacez la fonction Dispose() du client sans avoir besoin 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 des canaux! (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();
    }
}
0
répondu Murad Duraidi 2015-09-07 11:25:06

Ma méthode pour cela a été de 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 laisse juste tomber cette classe dans le projet en faisant la référence de service et l'utilise à 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 dispose, vous pouvez implémenter une logique de dispose plus complexe si vous le souhaitez.

Vous pouvez alors remplacer tous vos appels effectués 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 elle ne me nécessite pas d'avoir accès aux définitions D'Interface et je peux utiliser l'instruction using comme je m'y attendais tout en permettant à mon code de ressembler plus ou moins au même.

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

0
répondu Aleksandr Albert 2016-01-28 19:30:39

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.
}
-2
répondu Uri Abramson 2013-06-01 15:55:23