Qual è la soluzione alternativa migliore per il problema di blocco `using` del client WCF?


404

Mi piace creare un'istanza dei miei clienti del servizio WCF in un usingblocco poiché è praticamente il modo standard di utilizzare le risorse che implementano IDisposable:

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

Ma, come notato in questo articolo MSDN , il wrapping di un client WCF in un usingblocco potrebbe mascherare eventuali errori che portano al client a rimanere in uno stato di errore (come un timeout o un problema di comunicazione). Per farla breve, quando viene chiamato Dispose (), il metodo Close () del client viene attivato, ma genera un errore perché è in uno stato di errore. L'eccezione originale viene quindi mascherata dalla seconda eccezione. Non bene.

La soluzione suggerita nell'articolo di MSDN è quella di evitare completamente l'uso di un usingblocco e invece di creare un'istanza dei client e usarli in questo modo:

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

Rispetto al usingblocco, penso che sia brutto. E molto codice da scrivere ogni volta che hai bisogno di un client.

Fortunatamente, ho trovato alcune altre soluzioni alternative, come questa su IServiceOriented. Inizi con:

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

Che quindi consente:

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

Non è male, ma non credo sia espressivo e facilmente comprensibile come il usingblocco.

Per risolvere il problema che sto attualmente cercando di utilizzare, ho letto per la prima volta su blog.davidbarret.net . Fondamentalmente si sostituisce il Dispose()metodo del client ovunque lo si usi. Qualcosa di simile a:

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

Questo sembra essere in grado di consentire usingnuovamente il blocco senza il pericolo di mascherare un'eccezione di stato difettosa.

Quindi, ci sono altri gotcha che devo cercare per usare queste soluzioni alternative? Qualcuno ha trovato qualcosa di meglio?


42
L'ultimo (che controlla questo Stato) è una gara; potrebbe non essere difettoso quando si controlla il valore booleano, ma potrebbe essere difettoso quando si chiama Close ().
Brian,

15
Leggi stato; non è impeccabile. Prima di chiamare Close (), il canale si guasta. Lancia Close (). Gioco finito.
Brian,

4
Il tempo passa. Potrebbe essere un periodo di tempo molto breve, ma tecnicamente, nel periodo tra il controllo dello stato del canale e la sua richiesta di chiusura, lo stato del canale potrebbe cambiare.
Eric King,

8
Userei Action<T>invece di UseServiceDelegate<T>. minore.
hIpPy

2
Non mi piace molto questo aiutante statico Service<T>poiché complica i test unitari (come fanno la maggior parte delle cose statiche). Preferirei che fosse non statico in modo che potesse essere iniettato nella classe che lo utilizza.
Fabio Marreco,

Risposte:


137

In realtà, anche se ho bloggato (vedi la risposta di Luca ), credo che questo è meglio che il mio involucro IDisposable. Codice tipico:

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

(modifica per commenti)

Poiché Userestituisce un valore nullo, il modo più semplice per gestire i valori di ritorno è tramite una variabile acquisita:

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

2
@MarcGravell Dove posso iniettare quel client? Presumo che ChannelFactory crei il client e l'oggetto factory sia stato rinnovato all'interno della classe Service, il che significa che il codice dovrebbe essere sottoposto a refactoring un po 'per consentire una factory personalizzata. È corretto o mi sto perdendo qualcosa di ovvio qui?
Anttu,

16
È possibile modificare facilmente il wrapper in modo da non aver bisogno di una variabile di acquisizione per il risultato. Qualcosa del genere: public static TResult Use<TResult>(Func<T, TResult> codeBlock) { ... }
chris,

3
Forse utile https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/ ed https://devzone.channeladam.com/articles/2014/09/how-to-easily-call-wcf-service-properly/ e http://dzimchuk.net/post/wcf-error-helpers
PreguntonCojoneroCabrón

Come posso aggiungere credenziali usando in questo modo?
Ippaso,

2
A mio avviso, la soluzione più corretta sarebbe: 1) Eseguire il modello Chiudi / Interrompi senza una condizione di competizione 2) Gestire la situazione quando l'operazione di servizio genera eccezioni 3) Gestire le situazioni in cui entrambi i metodi Chiudi e Interrompi generano eccezioni 4) Gestire eccezioni asincrone come ThreadAbortException https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/
Kiquenet,

88

Data la scelta tra la soluzione sostenuta da IServiceOriented.com e la soluzione sostenuta dal blog di David Barret , preferisco la semplicità offerta sostituendo il metodo Dispose () del cliente. Questo mi permette di continuare a usare l'istruzione using () come ci si aspetterebbe da un oggetto usa e getta. Tuttavia, come sottolineato da @Brian, questa soluzione contiene una condizione di competizione in quanto lo Stato potrebbe non essere in errore quando viene controllato ma potrebbe essere al momento in cui viene chiamato Close (), nel qual caso si verifica ancora CommunicationException.

Quindi, per ovviare a questo, ho impiegato una soluzione che mescola il meglio di entrambi i mondi.

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

2
non è rischioso usare l'istruzione 'Try-Infine' (o lo zucchero sintattico - "using () {}") con risorse non gestite? Nel caso specifico, se l'opzione "Chiudi" non riesce, l'eccezione non viene rilevata e alla fine potrebbe non essere eseguita. Inoltre, se esiste un'eccezione nell'istruzione finally, può mascherare altre eccezioni. Penso che sia per questo che Try-Catch sia preferito.
Zack Jannsen,

Zack, non chiaro sul tuo oggetto; cosa mi sto perdendo? Se il metodo Close genera un'eccezione, il blocco finally verrà eseguito prima che venga generata l'eccezione. Giusto?
Patrick Szalapski,

1
@jmoreno, ho annullato la tua modifica. Se noterai, non esiste alcun blocco di cattura nel metodo. L'idea è che qualsiasi eccezione che si verifica (anche alla fine) dovrebbe essere lanciata, non catturata silenziosamente.
Matt Davis,

5
@MattDavis Perché hai bisogno di successbandiera a tutti? Perché no try { Close(); } catch { Abort(); throw; }?
Konstantin Spirin,

Che ne dici di provare / catturare Close(); success = true;? Non vorrei che venisse generata un'eccezione se potessi annullarla con successo nel blocco finally. Vorrei solo generare un'eccezione se Abort () non fosse riuscito in quel caso. In questo modo, il try / catch nasconderebbe la potenziale eccezione della race condition e ti permetterebbe comunque di interrompere () la connessione nel blocco finally.
goku_da_master

32

Ho scritto una funzione di ordine superiore per farlo funzionare correttamente. L'abbiamo usato in diversi progetti e sembra funzionare alla grande. È così che le cose avrebbero dovuto essere fatte dall'inizio, senza il paradigma "utilizzo" o giù di lì.

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

Puoi effettuare chiamate in questo modo:

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

Questo è praticamente come hai fatto nel tuo esempio. In alcuni progetti, scriviamo metodi helper fortemente tipizzati, quindi finiamo per scrivere cose come "Wcf.UseFooService (f => f ...)".

Lo trovo abbastanza elegante, tutto sommato. C'è un problema particolare che hai riscontrato?

Ciò consente di collegare altre funzionalità intelligenti. Ad esempio, su un sito, il sito si autentica al servizio per conto dell'utente che ha effettuato l'accesso. (Il sito non ha credenziali da solo.) Scrivendo il nostro aiuto per il metodo "UseService", possiamo configurare la fabbrica di canali nel modo che desideriamo, ecc. Inoltre, non siamo tenuti a utilizzare i proxy generati - qualsiasi interfaccia lo farà .


Ricevo un'eccezione: la proprietà Address su ChannelFactory.Endpoint era null. L'endpoint di ChannelFactory deve avere un indirizzo valido specificato . Qual è il GetCachedFactorymetodo?
Marshall,

28

Questo è il modo consigliato da Microsoft per gestire le chiamate client WCF:

Per ulteriori dettagli, consultare: Eccezioni previste

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

Informazioni aggiuntive Tante persone sembrano porre questa domanda su WCF che Microsoft ha persino creato un esempio dedicato per dimostrare come gestire le eccezioni:

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

Scarica l'esempio: C # o VB

Considerando che ci sono così tanti problemi che coinvolgono la dichiarazione di utilizzo , (accesa?) Discussioni interne e discussioni su questo problema, non perderò il mio tempo cercando di diventare un cowboy del codice e trovare un modo più pulito. Lo aspetterò e implementerò i client WCF in questo modo dettagliato (ma affidabile) per le mie applicazioni server.

Errori aggiuntivi facoltativi da rilevare

Molte eccezioni derivano CommunicationExceptione non credo che la maggior parte di tali eccezioni debba essere ripetuta. Ho esaminato ogni eccezione su MSDN e ho trovato un breve elenco di eccezioni in grado di riprovare (oltre a quanto TimeOutExceptionsopra). Fammi sapere se ho perso un'eccezione che dovrebbe essere riprovata.

  // 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
}

Certo, questo è un po 'di codice banale da scrivere. Al momento preferisco questa risposta e non vedo alcun "hack" in quel codice che possa causare problemi lungo la strada.


1
Il codice dell'esempio sta ancora causando problemi? Ho provato a eseguire il progetto UsingUsing (VS2013) ma la linea con "Hope this code wasn't important, because it might not happen."è ancora eseguita ...
janv8000

14

Ho finalmente trovato alcuni solidi passi verso una soluzione pulita a questo problema.

Questo strumento personalizzato estende WCFProxyGenerator per fornire un proxy di gestione delle eccezioni. Genera un proxy aggiuntivo chiamato ExceptionHandlingProxy<T>che eredita ExceptionHandlingProxyBase<T>- quest'ultimo dei quali implementa la funzionalità della funzionalità del proxy. Il risultato è che puoi scegliere di utilizzare il proxy predefinito che eredita ClientBase<T>o ExceptionHandlingProxy<T>che incapsula gestendo la durata della fabbrica e del canale del canale. ExceptionHandlingProxy rispetta le tue selezioni nella finestra di dialogo Aggiungi riferimento al servizio rispetto ai metodi asincroni e ai tipi di raccolta.

Codeplex ha un progetto chiamato Exception Handling WCF Proxy Generator . In pratica installa un nuovo strumento personalizzato in Visual Studio 2008, quindi utilizza questo strumento per generare il nuovo proxy di servizio (Aggiungi riferimento al servizio) . Ha alcune belle funzionalità per gestire canali difettosi, timeout e smaltimento sicuro. C'è un eccellente video qui chiamato ExceptionHandlingProxyWrapper che spiega esattamente come funziona.

È possibile utilizzare di Usingnuovo in modo sicuro l' istruzione e se il canale presenta un errore su qualsiasi richiesta (TimeoutException o CommunicationException), il wrapper reinizializzerà il canale con errore e ritenterà la query. In caso contrario, chiamerà il Abort()comando, eliminerà il proxy e riproverà l'eccezione. Se il servizio genera un FaultExceptioncodice, interromperà l'esecuzione e il proxy verrà interrotto in modo sicuro generando l'eccezione corretta come previsto.


@Shimmy Status Beta. Data: sabato 11 luglio 2009 di Michele Bustamante . Progetto morto?
Kiquenet,

11

Sulla base delle risposte di Marc Gravell, MichaelGG e Matt Davis, i nostri sviluppatori hanno ideato quanto segue:

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

Esempio di utilizzo:

string result = string.Empty;

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

È il più vicino possibile alla sintassi "utilizzo", non è necessario restituire un valore fittizio quando si chiama un metodo vuoto e è possibile effettuare più chiamate al servizio (e restituire più valori) senza dover utilizzare le tuple.

Inoltre, puoi usarlo con i ClientBase<T>discendenti invece di ChannelFactory, se lo desideri.

Il metodo di estensione è esposto se uno sviluppatore desidera invece disporre manualmente di un proxy / canale.


Sta usando questo ha senso se sto usando PoolingDuplex e non chiudo la connessione dopo una chiamata, quindi il mio servizio clienti potrebbe vivere anche pochi giorni e gestire i callback del server. Per quanto ho capito la soluzione che è discussa qui ha senso per una chiamata per sessione?
sabato

@sll: serve per chiudere la connessione immediatamente dopo il ritorno della chiamata (una chiamata per sessione).
TrueWill,

@cacho Rendere DisposeSafelyprivato è certamente un'opzione ed eviterebbe confusione. Potrebbero esserci casi di utilizzo in cui qualcuno vorrebbe chiamarlo direttamente, ma non riesco a trovarne uno di mano.
TrueWill

@truewill solo per la documentazione, è anche importante ricordare che questo metodo è thread-safe, giusto?
Cacho Santa,

1
A mio avviso, la soluzione più corretta sarebbe: 1) Eseguire il modello Chiudi / Interrompi senza una condizione di competizione 2) Gestire la situazione quando l'operazione di servizio genera eccezioni 3) Gestire le situazioni in cui entrambi i metodi Chiudi e Interrompi generano eccezioni 4) Gestire eccezioni asincrone come ThreadAbortException https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/
Kiquenet,

8

@Marc Gravell

Non sarebbe OK usare questo:

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

O, la stessa cosa (Func<T, TResult>)in caso diService<IOrderService>.Use

Ciò renderebbe più semplice la restituzione delle variabili.


2
+1 @MarcGravell Penso che anche la tua risposta 'potrebbe fare di meglio': P (e l'azione può essere implementata in termini di un Func con un ritorno nullo). Tutta questa pagina è un disastro - andrei a formularne una unificata e commenterei i dups se immaginavo di usare il WCF in qualsiasi momento in questo decennio ...
Ruben Bartelink,

7

Cos'è questo?

Questa è la versione CW della risposta accettata ma con (ciò che considero completo) inclusa la gestione delle eccezioni.

La risposta accettata fa riferimento a questo sito Web che non esiste più . Per evitare problemi, includo qui le parti più rilevanti. Inoltre, l'ho modificato leggermente per includere la gestione dei tentativi di eccezione per gestire quei fastidiosi timeout di rete.

Utilizzo client WCF semplice

Una volta generato il proxy lato client, questo è tutto ciò che serve per implementarlo.

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

ServiceDelegate.cs

Aggiungi questo file alla tua soluzione. Non sono necessarie modifiche a questo file, a meno che non si desideri modificare il numero di tentativi o quali eccezioni si desidera gestire.

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: ho reso questo post un wiki della community. Non raccoglierò "punti" da questa risposta, ma preferisco che tu la voti se sei d'accordo con l'implementazione o la modifichi per renderla migliore.


Non sono sicuro di essere d'accordo con la tua caratterizzazione di questa risposta. È la versione CW con la tua idea di gestione delle eccezioni aggiunta.
John Saunders,

@JohnSaunders - True (il mio concetto di gestione delle eccezioni). Fammi sapere di eventuali eccezioni che mi mancano o che sto gestendo male.
goodguys_activate il

Che dire della variabile di successo? È necessario aggiungere al codice sorgente: if (esito positivo) restituisce; ??
Kiquenet,

Se viene lanciata la prima chiamata e la seconda ha esito positivo la maggior parte di RecentEx non sarà nulla, quindi stai generando un'eccezione che ha comunque fallito 5 tentativi. Oppure mi sfugge qualcosa? Non vedo dove si cancella la maggior parte di RecentEx se al 2 °, 3 °, 4 ° o 5 ° tentativo è riuscito. Inoltre, non vedere un ritorno o riuscire. Dovrei mancare qualcosa qui, ma questo codice non verrà eseguito sempre 5 volte se non viene generata alcuna eccezione?
Bart Calixto,

@Bart - Ho aggiunto success == falsealla finale if statement
goodguys_activate il

7

Di seguito è riportata una versione migliorata dell'origine della domanda ed estesa per memorizzare nella cache più fabbriche di canali e tentare di cercare l'endpoint nel file di configurazione in base al nome del contratto.

Utilizza .NET 4 (in particolare: contravarianza, 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.");
    }
}

1
Perché usare UseServiceDelegate<T>invece di Action<T>?
Mike Mayer,

1
L'unica ragione per cui posso pensare che l'autore originale abbia fatto così era avere un delegato fortemente tipizzato che lo sviluppatore avrebbe saputo appartenere alla chiamata di un servizio. Ma, per quanto posso vedere, Action<T>funziona altrettanto bene.
Jesse C. Slicer,

5

Un wrapper come questo funzionerebbe:

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
        }
    }
}

Ciò dovrebbe consentirti di scrivere codice come:

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

Il wrapper potrebbe ovviamente catturare più eccezioni se ciò è necessario, ma il principio rimane lo stesso.


Ricordo che la discussione su Dispose non veniva chiamata in determinate condizioni ... con conseguente perdita di memoria con WCF.
goodguys_activate

Non sono sicuro che causasse perdite di memoria, ma il problema è questo. Quando si chiama Disposeun canale IC potrebbe generare un'eccezione se il canale è in uno stato di errore, questo è un problema poiché Microsoft specifica che Disposenon deve mai essere lanciato. Quindi ciò che fa il codice sopra è gestire il caso quando Closegenera un'eccezione. Se Abortlancia potrebbe essere qualcosa di gravemente sbagliato. Ho scritto un post sul blog a riguardo lo scorso dicembre: blog.tomasjansson.com/2010/12/disposible-wcf-client-wrapper
Tomas Jansson

4

Ho usato il proxy dinamico Castle per risolvere il problema Dispose () e ho anche implementato l'aggiornamento automatico del canale quando è in uno stato inutilizzabile. Per utilizzare questo è necessario creare una nuova interfaccia che eredita il contratto di servizio e IDisposable. Il proxy dinamico implementa questa interfaccia e racchiude un canale WCF:

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

Mi piace dal momento che è possibile iniettare servizi WCF senza che i consumatori debbano preoccuparsi dei dettagli di WCF. E non c'è alcuna aggiunta aggiuntiva come le altre soluzioni.

Dai un'occhiata al codice, in realtà è piuttosto semplice: WCF Dynamic Proxy


4

Utilizzare un metodo di estensione:

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

4

Se non è necessario l' IoC o si utilizza un client generato automaticamente (riferimento al servizio), è possibile utilizzare semplicemente un wrapper per gestire la chiusura e lasciare che il GC prenda la base client quando si trova in uno stato sicuro che non genererà alcuna eccezione. Il GC chiamerà Dispose in serviceclient e questo chiamerà Close. Poiché è sempre chiuso, non può causare alcun danno. Sto usando questo senza problemi nel codice di produzione.

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

Quindi, quando si accede al server, si crea il client e si utilizza usingin autodisconect:

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

    Ws.Open();

    Ws.Test();
}

3

Sommario

Utilizzando le tecniche descritte in questa risposta è possibile utilizzare un servizio WCF in un blocco utilizzando con la seguente sintassi:

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

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

Ovviamente puoi adattarlo ulteriormente per ottenere un modello di programmazione più conciso specifico per la tua situazione, ma il punto è che possiamo creare un'implementazione di IMyServicereinventare il canale che implementa correttamente il modello usa e getta.


Dettagli

Tutte le risposte fornite finora affrontano il problema di aggirare il "bug" nell'implementazione del canale WCF IDisposable. La risposta che sembra offrire il modello di programmazione più conciso (che consente di utilizzare il usingblocco per disporre su risorse non gestite) è questa : il proxy viene modificato per implementare IDisposablecon un'implementazione senza errori. Il problema con questo approccio è la manutenibilità: dobbiamo reimplementare questa funzionalità per ogni proxy che utilizziamo. Su una variazione di questa risposta vedremo come possiamo usare la composizione piuttosto che l'eredità per rendere generica questa tecnica.

Primo tentativo

Sembra che ci siano varie implementazioni per l' IDisposableimplementazione, ma per ragioni di argomento useremo un adattamento di quello usato dalla risposta attualmente accettata .

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

Armati con le classi sopra possiamo ora scrivere

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

Questo ci consente di usufruire del nostro servizio utilizzando il usingblocco:

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

Rendendo questo generico

Finora non abbiamo fatto altro che riformulare la soluzione di Tomas . Ciò che impedisce a questo codice di essere generico è il fatto che la ProxyWrapperclasse deve essere nuovamente implementata per ogni contratto di servizio che desideriamo. Vedremo ora una classe che ci consente di creare dinamicamente questo tipo usando 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();
    }
}

Con la nostra nuova classe helper ora possiamo scrivere

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

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

Tieni presente che puoi anche utilizzare la stessa tecnica (con lievi modifiche) per i client generati automaticamente per l'ereditarietà ClientBase<>(anziché utilizzare ChannelFactory<>) o se desideri utilizzare un'implementazione diversa IDisposableper chiudere il tuo canale.


2

Mi piace questo modo di chiudere la connessione:

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

1

Ho scritto una semplice classe base che gestisce questo. È disponibile come pacchetto NuGet ed è abbastanza facile da usare.

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

Eventuali aggiornamenti per VS2013-.net 4.5.1? eventuali opzioni per Riprovare come stackoverflow.com/a/9370880/206730 ? -
Kiquenet,

@Kiquenet Non sto più lavorando su WCF. Se mi invii una richiesta pull, posso unirla e aggiornare il pacchetto.
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();
            }
        }
    }
}

Quindi consente di scrivere correttamente le dichiarazioni di ritorno:

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

1

Vorrei aggiungere l'implementazione del servizio dalla risposta di Marc Gravell nel caso di utilizzo di ServiceClient invece di 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

Per chi fosse interessato, ecco una traduzione VB.NET della risposta accettata (sotto). L'ho raffinato un po 'per brevità, combinando alcuni dei suggerimenti di altri in questo thread.

Ammetto che è fuori tema per i tag di origine (C #), ma poiché non sono stato in grado di trovare una versione VB.NET di questa bella soluzione, suppongo che anche gli altri saranno alla ricerca. La traduzione Lambda può essere un po 'complicata, quindi vorrei salvare qualcuno il problema.

Si noti che questa particolare implementazione offre la possibilità di configurare il ServiceEndpointruntime.


Codice:

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

Uso:

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

La nostra architettura di sistema utilizza spesso il framework Unity IoC per creare istanze di ClientBase, quindi non c'è modo sicuro per imporre che gli altri sviluppatori utilizzinousing{} blocchi. Per renderlo il più sicuro possibile, ho creato questa classe personalizzata che estende ClientBase e gestisce la chiusura del canale in caso di smaltimento o di finalizzazione nel caso in cui qualcuno non elimini esplicitamente l'istanza creata da Unity.

Ci sono anche cose che dovevano essere fatte nel costruttore per impostare il canale per le credenziali e le cose personalizzate, quindi anche questo è qui ...

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

Quindi un cliente può semplicemente:

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

E il chiamante può eseguire una di queste operazioni:

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

Non utilizzare mai il parametro disposing nel metodo Dispose
CaffGeek

@Chad - Stavo seguendo il modello di progettazione comune Finalize / Dispose di Microsoft: msdn.microsoft.com/en-us/library/b1yfkh5e%28VS.71%29.aspx È vero che non sto usando la variabile, perché Non è necessario eseguire una pulizia diversa tra uno smaltimento normale e una finalizzazione. Potrebbe essere riscritto solo per avere Finalize call Dispose () e spostare il codice da Dispose (bool) a Dispose ().
CodingWithSpike

I finalizzatori aggiungono costi generali e non sono deterministici. Li evito quando possibile. È possibile utilizzare le fabbriche automatiche di Unity per iniettare i delegati e inserirli con i blocchi oppure (meglio) nascondere il comportamento del servizio di creazione / chiamata / eliminazione dietro un metodo su un'interfaccia iniettata. Ogni chiamata alla dipendenza crea il proxy, lo chiama e lo elimina.
TrueWill,

0

Ho inviato alcune risposte su questo post e personalizzato secondo le mie esigenze.

Volevo la possibilità di fare qualcosa con il client WCF prima di usarlo così il DoSomethingWithClient()metodo.

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

Ecco la classe di aiuto:

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

E posso usarlo come:

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

Che dire del costruttore del client che utilizza il binding e l'endpoing? TClient (rilegatura, endpoing)
Kiquenet,

0

Ho il mio wrapper per un canale che implementa Dispose come segue:

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

Questo sembra funzionare bene e consente di utilizzare un blocco using.


0

Il seguente helper consente di chiamare voidmetodi non nulli. Uso:

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

La classe stessa è:

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

Sostituisci Dispose () del client senza la necessità di generare una classe proxy basata su ClientBase, anche senza la necessità di gestire la creazione e la memorizzazione nella cache del canale ! (Notare che WcfClient non è una classe ABSTRACT e si basa su 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

Il mio metodo per farlo è stato quello di creare una classe ereditata che implementa esplicitamente IDisposable. Questo è utile per le persone che usano la GUI per aggiungere il riferimento al servizio (Aggiungi riferimento al servizio). Lascio cadere questa classe nel progetto facendo il riferimento al servizio e lo uso al posto del client predefinito:

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

Nota: questa è solo una semplice implementazione di dispose, se lo desideri puoi implementare una logica di smaltimento più complessa.

È quindi possibile sostituire tutte le chiamate effettuate con il normale client di servizio con i client sicuri, in questo modo:

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

Mi piace questa soluzione in quanto non mi richiede di avere accesso alle definizioni di interfaccia e posso usare il using istruzione come mi aspetterei mentre consento al mio codice di apparire più o meno lo stesso.

Sarà comunque necessario gestire le eccezioni che possono essere generate come indicato in altri commenti in questo thread.


-2

È inoltre possibile utilizzare a DynamicProxyper estendere il Dispose()metodo. In questo modo potresti fare qualcosa del tipo:

using (var wrapperdProxy = new Proxy<yourProxy>())
{
   // Do whatever and dispose of Proxy<yourProxy> will be called and work properly.
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.