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

404

J'aime instancier mes clients de service WCF dans un usingbloc 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 , encapsuler un client WCF dans un usingbloc peut masquer toutes les erreurs qui entraînent le client dans un état défectueux (comme un délai d'attente ou un problème de communication). En bref, lorsque Dispose () est appelée, la méthode Close () du client se déclenche, mais génère une erreur car elle est dans un état défectueux. L'exception d'origine est ensuite masquée par la deuxième exception. Pas bon.

La solution de contournement suggérée dans l'article MSDN consiste à éviter complètement d'utiliser un usingbloc, et à la place, instancier vos clients et 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 usingbloc, 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 par:

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 facilement compréhensible que le usingbloc.

La solution de contournement que j'essaie actuellement d'utiliser J'ai d'abord lu sur blog.davidbarret.net . Fondamentalement, vous remplacez la Dispose()méthode 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 être en mesure d'autoriser à usingnouveau le bloc sans risque de masquer une exception d'état défectueux.

Alors, y a-t-il d'autres problèmes que je dois rechercher pour utiliser ces solutions de contournement? Quelqu'un a-t-il trouvé quelque chose de mieux?

Eric King
la source
42
Le dernier (qui inspecte cet état) est une course; il peut ne pas être défectueux lorsque vous vérifiez le booléen, mais peut être défectueux lorsque vous appelez Close ().
Brian
15
Vous lisez l'état; ce n'est pas la faute. Avant d'appeler Close (), le canal est défaillant. Close () lance. Jeu terminé.
Brian
4
Le temps passe. Il peut s'agir d'une très courte période de temps, mais techniquement, dans l'intervalle de temps entre la vérification de l'état du canal et sa demande de fermeture, l'état du canal peut changer.
Eric King
8
J'utiliserais Action<T>plutôt UseServiceDelegate<T>. mineur.
hIpPy
2
Je n'aime vraiment pas cette aide statique Service<T>car elle complique les tests unitaires (comme la plupart des choses statiques). Je préférerais qu'il ne soit pas statique afin qu'il puisse être injecté dans la classe qui l'utilise.
Fabio Marreco

Réponses:

137

En fait, bien que je blogué (voir la réponse de Luc ), je pense que c'est mieux que mon emballage IDisposable. Code typique:

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

(modifier par commentaires)

Puisque Usereturn est nul, 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
Marc Gravell
la source
2
@MarcGravell Où pourrais-je injecter ce client? Je suppose que ChannelFactory crée le client et que l'objet de fabrique est nouveau dans la classe Service, ce qui signifie que le code doit être refactorisé un peu pour permettre une fabrique personnalisée. Est-ce correct ou manque-t-il quelque chose d'évident ici?
Anttu
16
Vous pouvez facilement modifier le wrapper afin de ne pas avoir besoin d'une variable de capture pour le résultat. Quelque chose comme ça: public static TResult Use<TResult>(Func<T, TResult> codeBlock) { ... }
chris
3
Peut-être utile https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/ et https://devzone.channeladam.com/articles/2014/09/how-to-easily-call-wcf-service-properly/ et http://dzimchuk.net/post/wcf-error-helpers
PreguntonCojoneroCabrón
Comment puis-je ajouter des informations d'identification en utilisant cette méthode?
Hippasus
2
À mon avis, la solution la plus correcte serait: 1) Effectuer le modèle Close / Abort sans condition de concurrence critique 2) Gérer la situation lorsque l'opération de service lève des exceptions 3) Gérer les situations lorsque les méthodes Close et Abort lèvent des exceptions 4) Gérer exceptions asynchrones telles que ThreadAbortException https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/
Kiquenet
88

É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 en remplaçant 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 dans la mesure où l'État peut ne pas être en défaut lors de sa vérification, mais peut l'être au moment de l'appel de Close (), auquel cas l'exception CommunicationException se produit toujours.

Donc, pour contourner cela, 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();
    }
}
Matt Davis
la source
2
n'est-il pas risqué d'utiliser l'instruction 'Try-Enfin' (ou le sucre syntaxique - "using () {}") avec des ressources non gérées? Par exemple, si l'option "Fermer" échoue, l'exception n'est pas interceptée et peut finalement ne pas s'exécuter. De plus, s'il existe une exception dans l'instruction finally, elle peut masquer d'autres exceptions. Je pense que c'est pourquoi Try-Catch est préféré.
Zack Jannsen du
Zack, pas clair sur ton objet; Qu'est-ce que je rate? Si la méthode Close lève une exception, le bloc finally s'exécutera avant que l'exception ne soit levée. Droite?
Patrick Szalapski
1
@jmoreno, j'ai défait votre modification. Si vous remarquez, il n'y a aucun bloc catch dans la méthode. L'idée est que toute exception qui se produit (même dans le final) doit être levée, pas silencieusement capturée.
Matt Davis
5
@MattDavis Pourquoi avez-vous besoin d'un successdrapeau? Pourquoi ne pas try { Close(); } catch { Abort(); throw; }?
Konstantin Spirin
Qu'en est-il de faire un essai / capture Close(); success = true;? Je ne voudrais pas qu'une exception soit levée si je pouvais l'abandonner avec succès dans le bloc finalement. Je ne voudrais qu'une exception levée si Abort () échoue dans ce cas. De cette façon, le try / catch cacherait l'exception potentielle de condition de concurrence et vous permettrait toujours d'abandonner () la connexion dans le bloc finally.
goku_da_master
32

J'ai écrit une fonction d'ordre supérieur pour le faire fonctionner 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épart, sans le paradigme «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 effectuer des appels comme celui-ci:

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

Je le trouve assez élégant, tout bien considéré. Y a-t-il un problème particulier que vous avez rencontré?

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

MichaelGG
la source
Je reçois une exception: la propriété Address sur ChannelFactory.Endpoint était nulle. Le point de terminaison de ChannelFactory doit avoir une adresse valide spécifiée . Quelle est la GetCachedFactoryméthode?
Marshall
28

C'est la méthode recommandée par Microsoft pour gérer les appels des clients 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 montrer comment gérer les exceptions:

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

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

Étant donné qu'il y a tellement de problèmes impliquant l'instruction using , (chauffé?) 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 simplement le sucer et implémenter les clients WCF de cette manière détaillée (mais fiable) pour mes applications serveur.

Échecs supplémentaires facultatifs pour attraper

De nombreuses exceptions dérivent de CommunicationExceptionet je ne pense pas que la plupart de ces exceptions devraient être réessayées. J'ai parcouru chaque exception sur MSDN et trouvé une courte liste d'exceptions pouvant être réessayées (en plus de TimeOutExceptionci-dessus). Faites-moi savoir si j'ai raté une exception qui devrait être réessayé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 aucun "piratage" dans ce code qui pourrait causer des problèmes en cours de route.

goodguys_activate
la source
1
Le code de l'exemple pose-t-il toujours des problèmes? J'ai essayé d'exécuter le projet UsingUsing (VS2013) mais la ligne avec "Hope this code wasn't important, because it might not happen."est toujours exécutée ...
janv8000
14

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é ExceptionHandlingProxy<T>qui hérite 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 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é Générateur de proxy WCF de gestion des exceptions . 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 de belles fonctionnalités pour gérer les canaux défaillants, 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 à Usingnouveau utiliser l' instruction en toute sécurité , et si le canal est défaillant sur toute demande (TimeoutException ou CommunicationException), le wrapper réinitialisera le canal défaillant et relancera la requête. Si cela échoue, il appellera la Abort()commande et supprimera le proxy et renverra l'exception. Si le service envoie un FaultExceptioncode, il cessera de s'exécuter et le proxy sera abandonné en toute sécurité en levant l'exception correcte comme prévu.

Neil
la source
@Shimmy Status Beta. Date: sam.11 juil.2009 par Michele Bustamante . Projet mort?
Kiquenet
11

Sur la base des réponses de Marc Gravell, MichaelGG et Matt Davis, nos développeurs ont trouvé 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 à retourner 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 de tuples.

Vous pouvez également l'utiliser 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 supprimer manuellement un proxy / canal à la place.

TrueWill
la source
Est-ce que cela a du sens si j'utilise PoolingDuplex et ne ferme pas la connexion après un appel afin que mon service client puisse vivre même quelques jours et gérer les rappels du serveur. Pour autant que je comprends la solution qui est discutée ici est logique pour un appel par session?
sll
@sll - c'est pour fermer la connexion immédiatement après le retour de l'appel (un appel par session).
TrueWill
@cacho Rendre DisposeSafelyprivé est certainement une option et éviterait toute confusion. Il peut y avoir des cas d'utilisation où quelqu'un voudrait l'appeler directement, mais je ne peux pas en trouver un tout de suite.
TrueWill
@truewill juste pour la documentation, il est également important de mentionner que cette méthode est thread-safe non?
Cacho Santa
1
À mon avis, la solution la plus correcte serait: 1) Effectuer le modèle Close / Abort sans condition de concurrence critique 2) Gérer la situation lorsque l'opération de service lève des exceptions 3) Gérer les situations lorsque les méthodes Close et Abort lèvent des exceptions 4) Gérer exceptions asynchrones telles que ThreadAbortException https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/
Kiquenet
8

@Marc Gravell

Ne serait-il pas acceptable de l'utiliser:

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 deService<IOrderService>.Use

Cela faciliterait le retour des variables.

pangulaire
la source
2
+1 @MarcGravell Je pense que votre réponse 'pourrait faire mieux' aussi: P (et l'action peut être implémentée en termes de Func avec un retour nul). Toute cette page est un gâchis - j'irais en formuler un unifié et commenter les doublons si j'envisageais d'utiliser WCF à tout moment de cette décennie ...
Ruben Bartelink
7

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) la gestion des exceptions incluse.

La réponse acceptée fait référence à ce site Web qui n'existe plus . Pour vous éviter des ennuis, j'inclus ici les pièces les plus pertinentes. En outre, je l'ai légèrement modifié pour inclure la gestion des tentatives d'exception pour gérer ces délais d'attente réseau ennuyeux.

Utilisation simple du client WCF

Une fois que vous avez généré 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 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 communautaire. Je ne collecterai pas de "points" à partir de cette réponse, mais préférez que vous le votiez si vous êtes d'accord avec la mise en œuvre, ou que vous la modifiiez pour l'améliorer.

LamonteCristo
la source
Je ne suis pas sûr d'être d'accord avec votre qualification de cette réponse. C'est la version CW avec votre idée de gestion des exceptions ajoutée.
John Saunders
@JohnSaunders - True (mon concept de gestion des exceptions). Faites-moi savoir toutes les exceptions qui me manquent ou que je traite mal.
goodguys_activate
Qu'en est-il de la variable de réussite? Il faut ajouter au code source: if (success) return; ??
Kiquenet
Si le premier appel est lancé et que le second réussit, mostRecentEx ne sera pas nul, vous lancez donc une exception qui a échoué 5 tentatives de toute façon. ou est-ce que je manque quelque chose? Je ne vois pas où vous effacez le plusRecentEx si un 2e, 3e, 4e ou 5e essai a réussi. Ne voyez pas non plus un retour ou un succès. Je devrais manquer quelque chose ici, mais ce code ne fonctionnera pas toujours 5 fois si aucune exception n'est levée?
Bart Calixto
@Bart - J'ai ajouté success == falseà la déclaration finale if
goodguys_activate
7

Vous trouverez ci-dessous 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 (en particulier: 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.");
    }
}
Jesse C. Slicer
la source
1
Pourquoi utiliser UseServiceDelegate<T>au lieu de Action<T>?
Mike Mayer
1
La seule raison pour laquelle je peux penser que l'auteur d'origine l'a fait était d'avoir un délégué fortement typé que le développeur saurait appartenir à appeler un service. Mais, pour autant que je puisse voir, Action<T>fonctionne aussi bien.
Jesse C. Slicer
5

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'encapsuleur pourrait bien sûr intercepter plus d'exceptions si cela est nécessaire, mais le principe reste le même.

Tomas Jansson
la source
Je me souviens que la discussion concernant Dispose n'était pas appelée dans certaines conditions ... entraînant une fuite de mémoire avec WCF.
goodguys_activate
Je ne suis pas sûr que cela entraînait des fuites de mémoire, mais le problème est le suivant. Lorsque vous appelez Disposeun IChannel, il peut lever une exception si le canal est dans un état défectueux, c'est un problème car Microsoft spécifie qu'il Disposene doit jamais lancer. Donc, ce que le code ci-dessus fait est de gérer le cas quand Closelève une exception. Si Abortjette, cela pourrait être quelque chose de grave. J'ai écrit un article à ce sujet en décembre dernier: blog.tomasjansson.com/2010/12/disposible-wcf-client-wrapper
Tomas Jansson
4

J'ai utilisé le proxy dynamique Castle 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 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 n'aient à se soucier des détails de WCF. Et il n'y a pas de cruauté supplémentaire comme les autres solutions.

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

Jay Douglass
la source
4

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;
    }
}
Johan Nyman
la source
4

Si vous n'avez pas besoin d' IoC ou 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 la base cliente lorsqu'il est dans un état sûr qui ne lèvera aucune exception. Le GC appellera Dispose in serviceclient, et ceci appellera Close. Puisqu'il est déjà fermé, il ne peut causer aucun dommage. J'utilise ceci sans problème 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 usingdans l'autodisconect:

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

    Ws.Open();

    Ws.Test();
}
Luiz Felipe
la source
3

Sommaire

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 adapter cela 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 IMyServicereprentation du canal qui implémente correctement le modèle jetable.


Détails

Toutes les réponses données jusqu'à présent traitent du problème de contourner le «bogue» 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 usingbloc pour disposer de ressources non gérées) est celle-ci - où le proxy est modifié pour être implémenté IDisposableavec une implémentation sans bogue. 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 existe différentes implémentations pour l' IDisposableimplémentation, mais pour les besoins de l'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 usingbloc:

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

Rendre ce 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 ProxyWrapperclasse 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éritant de ClientBase<>(au lieu d'utiliser ChannelFactory<>), ou si vous souhaitez utiliser une implémentation différente de IDisposablepour fermer votre chaîne.

Lawrence
la source
2

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

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

J'ai écrit une classe de base simple qui gère cela. Il est disponible sous forme de package 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);
    }
}
Ufuk Hacıoğulları
la source
Des mises à jour pour VS2013-.net 4.5.1? des options pour réessayer comme stackoverflow.com/a/9370880/206730 ? -
Kiquenet
@Kiquenet Je ne travaille plus sur WCF. Si vous m'envoyez une pull request, je peux la fusionner et mettre à jour le package.
Ufuk Hacıoğulları
1
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 des instructions de retour bien:

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

Je voudrais ajouter l'implémentation de Service 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();
            }
        }
    }
}
PSsam
la source
1

Pour les personnes intéressées, 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 d'autres dans ce fil.

J'avoue que c'est hors sujet pour les balises d'origine (C #), mais comme je n'ai pas pu trouver une version VB.NET de cette bonne solution, je suppose que d'autres chercheront également. La traduction de Lambda peut être un peu délicate, donc je voudrais éviter à quelqu'un le problème.

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

Usage:

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
InteXX
la source
1

Notre architecture système utilise souvent le cadre Unity IoC 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êmeusing{} blocs. 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 et tout ça, 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;
        }
    }
}

Un client peut alors simplement:

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

Et l'appelant peut effectuer l'une des opérations suivantes:

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);
        }
    }
}
CodingWithSpike
la source
Vous n'utilisez jamais le paramètre de disposition dans votre méthode Dispose
CaffGeek
@Chad - Je suivais le modèle de conception Finalize / Dispose commun de Microsoft: msdn.microsoft.com/en-us/library/b1yfkh5e%28VS.71%29.aspx Il est vrai que je n'utilise pas la variable, car je ne pas besoin de faire un nettoyage différent entre une élimination normale et une finalisation. Il peut être réécrit pour que Finalize appelle Dispose () et déplace le code de Dispose (bool) vers Dispose ().
CodingWithSpike
Les finaliseurs ajoutent des frais généraux et ne sont pas déterministes. Je les évite autant que possible. Vous pouvez utiliser les usines automatiques d'Unity pour injecter des délégués et les placer dans des blocs, ou (mieux) masquer le comportement du service de création / appel / suppression derrière une méthode sur une interface injectée. Chaque appel à la dépendance crée le proxy, l'appelle et le supprime.
TrueWill
0

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 pour la DoSomethingWithClient()méthode.

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));
hippie
la source
Qu'en est-il du constructeur client qui utilise la liaison et la terminaison? TClient (reliure, endpoing)
Kiquenet
0

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.

Joe
la source
0

L'assistant suivant permet d'appeler voidet de méthodes non nulles. 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;
        });
    }
}
Konstantin Spirin
la source
0

Remplacez 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 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();
    }
}
Murad Duraidi
la source
0

Ma méthode consiste à 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 simplement tomber cette classe dans le projet 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..
        }
    }
}

Remarque: Il s'agit simplement d'une implémentation simple de disposer, vous pouvez implémenter une logique de disposition plus complexe si vous le souhaitez.

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

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

J'aime cette solution car elle ne nécessite pas d'avoir accès aux définitions d'interface et je peux utiliser le using instruction comme je m'y attendais tout en permettant à mon code de ressembler plus ou moins à la même chose.

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

Aleksandr Albert
la source
-2

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

using (var wrapperdProxy = new Proxy<yourProxy>())
{
   // Do whatever and dispose of Proxy<yourProxy> will be called and work properly.
}
Uri Abramson
la source