Was ist die beste Problemumgehung für das Problem mit dem Block "Verwenden" des WCF-Clients?


404

Ich mag es, meine WCF-Service-Clients innerhalb eines usingBlocks zu instanziieren , da dies so ziemlich die Standardmethode für die Verwendung von Ressourcen ist, die Folgendes implementieren IDisposable:

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

Wie in diesem MSDN-Artikel erwähnt , kann das Umschließen eines WCF-Clients in einen usingBlock jedoch alle Fehler maskieren, die dazu führen, dass der Client in einem fehlerhaften Zustand bleibt (z. B. ein Timeout oder ein Kommunikationsproblem). Kurz gesagt, wenn Dispose () aufgerufen wird, wird die Close () -Methode des Clients ausgelöst, gibt jedoch einen Fehler aus, da sie sich in einem fehlerhaften Zustand befindet. Die ursprüngliche Ausnahme wird dann durch die zweite Ausnahme maskiert. Nicht gut.

Die im MSDN-Artikel vorgeschlagene Problemumgehung besteht darin, die Verwendung eines usingBlocks vollständig zu vermeiden und stattdessen Ihre Clients zu instanziieren und sie wie folgt zu verwenden:

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

Im Vergleich zum usingBlock finde ich das hässlich. Und jedes Mal, wenn Sie einen Client benötigen, müssen Sie viel Code schreiben.

Zum Glück habe ich einige andere Problemumgehungen gefunden, wie diese auf IServiceOriented. Sie beginnen mit:

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

Was dann erlaubt:

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

Das ist nicht schlecht, aber ich denke nicht, dass es so ausdrucksstark und leicht verständlich ist wie der usingBlock.

Die Problemumgehung, die ich derzeit verwenden möchte, habe ich zuerst auf blog.davidbarret.net gelesen . Grundsätzlich überschreiben Sie die Dispose()Methode des Clients, wo immer Sie sie verwenden. Etwas wie:

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

Dies scheint in der Lage zu sein, den usingBlock wieder zuzulassen , ohne dass die Gefahr besteht, eine Fehlerzustandsausnahme zu maskieren.

Gibt es noch andere Fallstricke, auf die ich bei der Verwendung dieser Problemumgehungen achten muss? Hat sich jemand etwas Besseres ausgedacht?


42
Der letzte (der dies inspiziert. Staat) ist ein Rennen; Es ist möglicherweise nicht fehlerhaft, wenn Sie den Booleschen Wert überprüfen, aber möglicherweise, wenn Sie Close () aufrufen.
Brian

15
Sie lesen Zustand; es ist nicht fehlerhaft. Bevor Sie Close () aufrufen, ist der Kanal fehlerhaft. Close () wirft. Spiel ist aus.
Brian

4
Zeit vergeht. Es kann eine sehr kurze Zeitspanne sein, aber technisch gesehen kann sich der Zustand des Kanals in der Zeitspanne zwischen der Überprüfung des Status des Kanals und der Aufforderung zum Schließen ändern.
Eric King

8
Ich würde Action<T>anstelle von verwenden UseServiceDelegate<T>. geringer.
hIpPy

2
Ich mag diesen statischen Helfer wirklich nicht, Service<T>da er das Testen von Einheiten erschwert (wie die meisten statischen Dinge). Ich würde es vorziehen, nicht statisch zu sein, damit es in die Klasse eingefügt werden kann, die es verwendet.
Fabio Marreco

Antworten:


137

Obwohl ich gebloggt habe (siehe Lukes Antwort ), denke ich, dass dies besser ist als mein IDisposable-Wrapper. Typischer Code:

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

(per Kommentar bearbeiten)

Da UseRückgaben ungültig sind, können Rückgabewerte am einfachsten über eine erfasste Variable verarbeitet werden:

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 Wo könnte ich diesen Client injizieren? Ich gehe davon aus, dass die ChannelFactory den Client erstellt und das Factory-Objekt innerhalb der Service-Klasse neu erstellt wurde. Dies bedeutet, dass der Code ein wenig überarbeitet werden sollte, um eine benutzerdefinierte Factory zu ermöglichen. Ist das richtig oder fehlt mir hier etwas Offensichtliches?
Anttu

16
Sie können den Wrapper leicht ändern, sodass Sie für das Ergebnis keine Erfassungsvariable benötigen. Etwas wie folgt aus : public static TResult Use<TResult>(Func<T, TResult> codeBlock) { ... }
chris

3
Vielleicht nützlich https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/ und https://devzone.channeladam.com/articles/2014/09/how-to-easily-call-wcf-service-properly/ und http://dzimchuk.net/post/wcf-error-helpers
PreguntonCojoneroCabrón

Wie kann ich auf diese Weise Anmeldeinformationen hinzufügen?
Hippasus

2
Meiner Meinung nach wäre die korrekteste Lösung: 1) Führen Sie das Close / Abort-Muster ohne Race-Bedingung aus. 2) Behandeln Sie die Situation, wenn der Servicevorgang Ausnahmen auslöst. 3) Behandeln Sie die Situationen, in denen sowohl die Close- als auch die Abort-Methode Ausnahmen auslösen. 4) Handle asynchrone Ausnahmen wie die ThreadAbortException https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/
Kiquenet

88

Sie haben die Wahl zwischen der von IServiceOriented.com empfohlenen Lösung und der von David Barrets Blog empfohlenen Lösung bevorzuge ich die Einfachheit, die durch das Überschreiben der Dispose () -Methode des Clients geboten wird. Dadurch kann ich die using () -Anweisung weiterhin so verwenden, wie man es von einem Einwegobjekt erwarten würde. Wie @Brian hervorhob, enthält diese Lösung jedoch eine Race-Bedingung, da der Status möglicherweise nicht fehlerhaft ist, wenn er überprüft wird, sondern zum Zeitpunkt des Aufrufs von Close (). In diesem Fall tritt die CommunicationException weiterhin auf.

Um dies zu umgehen, habe ich eine Lösung eingesetzt, die das Beste aus beiden Welten mischt.

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

2
Ist es nicht riskant, die Anweisung 'Try-finally' (oder den syntaktischen Zucker - "using () {}") mit nicht verwalteten Ressourcen zu verwenden? Wenn beispielsweise die Option "Schließen" fehlschlägt, wird die Ausnahme nicht abgefangen und möglicherweise nicht ausgeführt. Wenn die finally-Anweisung eine Ausnahme enthält, können auch andere Ausnahmen maskiert werden. Ich denke, deshalb wird Try-Catch bevorzugt.
Zack Jannsen

Zack, nicht klar über dein Objekt; Was vermisse ich? Wenn die Close-Methode eine Ausnahme auslöst, wird der finally-Block ausgeführt, bevor die Ausnahme ausgelöst wird. Recht?
Patrick Szalapski

1
@jmoreno, ich habe deine Bearbeitung rückgängig gemacht. Wenn Sie bemerken, gibt es in der Methode überhaupt keinen catch-Block. Die Idee ist, dass jede Ausnahme, die auftritt (auch in der endgültigen), geworfen und nicht still gefangen werden sollte.
Matt Davis

5
@MattDavis Warum brauchst du überhaupt eine successFlagge? Warum nicht try { Close(); } catch { Abort(); throw; }?
Konstantin Spirin

Was ist mit einem Versuch / Fang herum Close(); success = true;? Ich würde nicht wollen, dass eine Ausnahme ausgelöst wird, wenn ich sie im finally-Block erfolgreich abbrechen könnte. Ich möchte nur, dass eine Ausnahme ausgelöst wird, wenn Abort () in diesem Fall fehlschlägt. Auf diese Weise würde der Versuch / Fang die mögliche Ausnahme für die Rennbedingungen verbergen und es Ihnen dennoch ermöglichen, die Verbindung im finally-Block abzubrechen ().
goku_da_master

32

Ich habe eine Funktion höherer Ordnung geschrieben , damit es richtig funktioniert. Wir haben dies in mehreren Projekten verwendet und es scheint großartig zu funktionieren. So hätten die Dinge von Anfang an gemacht werden sollen, ohne das Paradigma der "Verwendung" oder so weiter.

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

Sie können folgende Anrufe tätigen:

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

Dies ist so ziemlich genau wie in Ihrem Beispiel. In einigen Projekten schreiben wir stark typisierte Hilfsmethoden, sodass wir am Ende Dinge wie "Wcf.UseFooService (f => f ...)" schreiben.

Alles in allem finde ich es ziemlich elegant. Gibt es ein bestimmtes Problem, auf das Sie gestoßen sind?

Auf diese Weise können andere nützliche Funktionen angeschlossen werden. Beispielsweise authentifiziert sich die Site an einem Standort im Namen des angemeldeten Benutzers beim Dienst. (Die Site hat selbst keine Anmeldeinformationen.) Indem wir unseren eigenen "UseService" -Methodenhelfer schreiben, können wir die Channel-Factory nach unseren Wünschen konfigurieren usw. Wir sind auch nicht an die Verwendung der generierten Proxys gebunden - jede Schnittstelle reicht aus .


Ich erhalte eine Ausnahme: Die Address-Eigenschaft in ChannelFactory.Endpoint war null. Für den Endpunkt der ChannelFactory muss eine gültige Adresse angegeben sein . Was ist eine GetCachedFactoryMethode?
Marshall

28

Dies ist die von Microsoft empfohlene Methode zur Verarbeitung von WCF-Clientaufrufen:

Weitere Einzelheiten finden Sie unter: Erwartete Ausnahmen

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

Zusätzliche Informationen In WCF scheinen so viele Leute diese Frage zu stellen, dass Microsoft sogar ein spezielles Beispiel erstellt hat, um zu demonstrieren, wie mit Ausnahmen umgegangen wird:

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

Laden Sie das Beispiel herunter: C # oder VB

Bedenkt man, dass es so viele Fragen sind die Anweisung using beteiligt , (beheizt?) Interne Diskussionen und Themen zu diesem Thema, ich werde meine Zeit nicht verschwenden, einen Code Cowboy zu werden und einen sauberen Weg zu finden. Ich werde es einfach aufsaugen und WCF-Clients auf diese ausführliche (aber vertrauenswürdige) Weise für meine Serveranwendungen implementieren.

Optionale zusätzliche Fehler beim Fangen

Viele Ausnahmen ergeben sich aus CommunicationExceptionund ich denke nicht, dass die meisten dieser Ausnahmen wiederholt werden sollten. Ich habe jede Ausnahme auf MSDN durchgesehen und eine kurze Liste von wiederholbaren Ausnahmen gefunden (zusätzlich zu den TimeOutExceptionoben genannten). Lassen Sie mich wissen, wenn ich eine Ausnahme verpasst habe, die wiederholt werden sollte.

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

Zugegeben, dies ist ein bisschen banaler Code zum Schreiben. Ich bevorzuge derzeit diese Antwort und sehe keine "Hacks" in diesem Code, die später Probleme verursachen könnten.


1
Verursacht der Code aus dem Beispiel immer noch Probleme? Ich habe versucht, das UsingUsing-Projekt (VS2013) auszuführen, aber die Zeile mit "Hope this code wasn't important, because it might not happen."wird noch ausgeführt ...
janv8000

14

Ich habe endlich einige solide Schritte in Richtung einer sauberen Lösung für dieses Problem gefunden.

Dieses benutzerdefinierte Tool erweitert WCFProxyGenerator um einen Proxy für die Ausnahmebehandlung. Es wird ein zusätzlicher Proxy generiert, der als " ExceptionHandlingProxy<T>erben" bezeichnet wird. ExceptionHandlingProxyBase<T>Letzterer implementiert das Fleisch der Funktionalität des Proxys. Das Ergebnis ist, dass Sie den Standard-Proxy verwenden können, der die Verwaltung der Lebensdauer der Channel Factory und des Kanals erbt ClientBase<T>oder ExceptionHandlingProxy<T>kapselt. ExceptionHandlingProxy berücksichtigt Ihre Auswahl im Dialogfeld "Dienstreferenz hinzufügen" in Bezug auf asynchrone Methoden und Auflistungstypen.

Codeplex hat ein Projekt namens Exception Handling WCF Proxy Generator . Grundsätzlich wird ein neues benutzerdefiniertes Tool in Visual Studio 2008 installiert und anschließend mit diesem Tool der neue Service-Proxy generiert (Service-Referenz hinzufügen) . Es hat einige nette Funktionen, um mit fehlerhaften Kanälen, Zeitüberschreitungen und sicherer Entsorgung umzugehen. Hier gibt es ein ausgezeichnetes Video namens ExceptionHandlingProxyWrapper , das genau erklärt, wie das funktioniert.

Sie können die UsingAnweisung sicher wieder verwenden. Wenn der Kanal bei einer Anforderung (TimeoutException oder CommunicationException) fehlerhaft ist, initialisiert der Wrapper den fehlerhaften Kanal neu und wiederholt die Abfrage. Wenn dies fehlschlägt, ruft es den Abort()Befehl auf, entsorgt den Proxy und löst die Ausnahme erneut aus. Wenn der Dienst einen FaultExceptionCode auslöst, wird die Ausführung beendet und der Proxy wird abgebrochen, wobei die richtige Ausnahme wie erwartet ausgelöst wird.


@ Shimmy Status Beta. Datum: Sa 11. Juli 2009 von Michele Bustamante . Totes Projekt?
Kiquenet

11

Basierend auf den Antworten von Marc Gravell, MichaelGG und Matt Davis haben unsere Entwickler Folgendes entwickelt:

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

Anwendungsbeispiel:

string result = string.Empty;

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

Es kommt der "using" -Syntax so nahe wie möglich, Sie müssen beim Aufrufen einer void-Methode keinen Dummy-Wert zurückgeben und können den Dienst mehrfach aufrufen (und mehrere Werte zurückgeben), ohne Tupel verwenden zu müssen.

ClientBase<T>Falls gewünscht , können Sie dies auch mit Nachkommen anstelle von ChannelFactory verwenden.

Die Erweiterungsmethode wird angezeigt, wenn ein Entwickler stattdessen einen Proxy / Kanal manuell entsorgen möchte.


Ist dies sinnvoll, wenn ich PoolingDuplex verwende und die Verbindung nach einem Anruf nicht schließe, sodass mein Client-Service möglicherweise sogar einige Tage in Betrieb ist und Server-Rückrufe verarbeitet. Soweit ich weiß, ist die hier diskutierte Lösung für einen Anruf pro Sitzung sinnvoll?
sll

@sll - Dies dient zum Schließen der Verbindung unmittelbar nach der Rückkehr des Anrufs (ein Anruf pro Sitzung).
TrueWill

@cacho DisposeSafelyPrivat zu machen ist sicherlich eine Option und würde Verwirrung vermeiden. Es kann Anwendungsfälle geben, in denen jemand es direkt anrufen möchte, aber ich kann mir nicht ohne weiteres eine einfallen lassen.
TrueWill

@truewill nur zur Dokumentation, es ist auch wichtig zu erwähnen, dass diese Methode threadsicher ist, oder?
Cacho Santa

1
Meiner Meinung nach wäre die korrekteste Lösung: 1) Führen Sie das Close / Abort-Muster ohne Race-Bedingung aus. 2) Behandeln Sie die Situation, wenn der Servicevorgang Ausnahmen auslöst. 3) Behandeln Sie die Situationen, in denen sowohl die Close- als auch die Abort-Methode Ausnahmen auslösen. 4) Handle asynchrone Ausnahmen wie die ThreadAbortException https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/
Kiquenet

8

@ Marc Gravell

Wäre es nicht in Ordnung, dies zu verwenden:

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

Oder das Gleiche (Func<T, TResult>)im Fall vonService<IOrderService>.Use

Dies würde die Rückgabe von Variablen erleichtern.


2
+1 @MarcGravell Ich denke, Ihre Antwort könnte auch besser sein: P (und die Aktion, die man in Form eines Func mit einer Null-Rückgabe implementieren kann). Diese ganze Seite ist ein Chaos - ich würde eine einheitliche Seite formulieren und Dups kommentieren, wenn ich beabsichtige, WCF in diesem Jahrzehnt jederzeit zu verwenden ...
Ruben Bartelink

7

Was ist das?

Dies ist die CW-Version der akzeptierten Antwort, jedoch mit (was ich für vollständig halte) Ausnahmebehandlung.

Die akzeptierte Antwort verweist auf diese Website, die es nicht mehr gibt . Um Ihnen Ärger zu ersparen, füge ich hier die wichtigsten Teile hinzu. Darüber hinaus habe ich es geringfügig geändert , um die Behandlung von Ausnahmewiederholungen einzuschließen , um diese lästigen Netzwerk-Timeouts zu behandeln.

Einfache Verwendung des WCF-Clients

Sobald Sie Ihren clientseitigen Proxy generiert haben, ist dies alles, was Sie zur Implementierung benötigen.

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

ServiceDelegate.cs

Fügen Sie diese Datei Ihrer Lösung hinzu. An dieser Datei sind keine Änderungen erforderlich, es sei denn, Sie möchten die Anzahl der Wiederholungsversuche oder die Ausnahmen ändern, die Sie behandeln möchten.

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: Ich habe diesen Beitrag zu einem Community-Wiki gemacht. Ich werde aus dieser Antwort keine "Punkte" sammeln, aber ich bevorzuge es, wenn Sie mit der Implementierung einverstanden sind oder sie bearbeiten, um sie besser zu machen.


Ich bin mir nicht sicher, ob ich Ihrer Charakterisierung dieser Antwort zustimme. Es ist die CW-Version mit Ihrer Idee der Ausnahmebehandlung .
John Saunders

@ JohnSaunders - True (mein Konzept der Ausnahmebehandlung). Lassen Sie mich wissen, welche Ausnahmen ich vermisse oder falsch handele.
goodguys_activate

Was ist mit Erfolgsvariablen? Es muss zum Quellcode hinzugefügt werden: if (success) return; ??
Kiquenet

Wenn der erste Aufruf ausgelöst wird und der zweite erfolgreich ist, ist mostRecentEx nicht null, sodass Sie eine Ausnahme auslösen, bei der 5 Wiederholungsversuche fehlgeschlagen sind. oder fehlt mir etwas Ich sehe nicht, wo Sie die mostRecentEx löschen, wenn bei einem 2., 3., 4. oder 5. Versuch erfolgreich war. Sehen Sie auch keine Rückkehr zum Erfolg. Ich sollte hier etwas vermissen, aber dieser Code wird nicht immer fünfmal ausgeführt, wenn keine Ausnahme ausgelöst wird?
Bart Calixto

@ Bart - habe ich hinzugefügt success == false der endgültigen if-Anweisung hinzu
goodguys_activate

7

Unten finden Sie eine erweiterte Version der Quelle aus der Frage, die erweitert wurde, um mehrere Kanalfabriken zwischenzuspeichern und zu versuchen, den Endpunkt in der Konfigurationsdatei nach Vertragsnamen zu suchen.

Es verwendet .NET 4 (speziell: Kontravarianz, 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
Warum UseServiceDelegate<T>statt verwenden Action<T>?
Mike Mayer

1
Der einzige Grund, warum ich glauben kann, dass der ursprüngliche Autor dies getan hat, war ein stark typisierter Delegierter, von dem der Entwickler wissen würde, dass er zum Aufrufen eines Dienstes gehört. Aber soweit ich sehen kann, Action<T>funktioniert es genauso gut.
Jesse C. Slicer

5

Ein Wrapper wie dieser würde funktionieren:

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

Das sollte es Ihnen ermöglichen, Code wie folgt zu schreiben:

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

Der Wrapper könnte natürlich mehr Ausnahmen abfangen, wenn dies erforderlich ist, aber das Prinzip bleibt dasselbe.


Ich erinnere mich an die Diskussion darüber, dass Dispose unter bestimmten Bedingungen nicht aufgerufen wird ... was zu einem Speicherverlust mit WCF führt.
goodguys_activate

Ich bin nicht sicher, ob es zu Speicherlecks geführt hat, aber das Problem ist dies. Wenn Sie Disposeeinen IChannel aufrufen, kann dies eine Ausnahme auslösen, wenn sich der Kanal in einem fehlerhaften Zustand befindet. Dies ist ein Problem, da Microsoft angibt, dass dies Disposeniemals ausgelöst werden soll. Der obige Code behandelt also den Fall, wenn Closeeine Ausnahme ausgelöst wird. Wenn Abortwirft, könnte etwas ernsthaft falsch sein. Ich habe letzten Dezember einen Blog-Beitrag darüber geschrieben: blog.tomasjansson.com/2010/12/disposible-wcf-client-wrapper
Tomas Jansson

4

Ich habe den dynamischen Proxy von Castle verwendet, um das Dispose () -Problem zu lösen, und auch die automatische Aktualisierung des Kanals implementiert, wenn er sich in einem unbrauchbaren Zustand befindet. Um dies zu verwenden, müssen Sie eine neue Schnittstelle erstellen, die Ihren Servicevertrag und IDisposable erbt. Der dynamische Proxy implementiert diese Schnittstelle und umschließt einen WCF-Kanal:

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

Ich mag dies, da Sie WCF-Dienste einspeisen können, ohne dass sich Verbraucher um Details von WCF kümmern müssen. Und es gibt keine zusätzliche Kruft wie bei den anderen Lösungen.

Schauen Sie sich den Code an, es ist eigentlich ziemlich einfach: WCF Dynamic Proxy


4

Verwenden Sie eine Erweiterungsmethode:

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

Wenn Sie kein IoC benötigen oder einen automatisch generierten Client (Servicereferenz) verwenden, können Sie das Schließen einfach mit einem Wrapper verwalten und den GC die Clientbasis übernehmen lassen, wenn er sich in einem sicheren Zustand befindet, der keine Ausnahme auslöst. Der GC ruft Dispose in serviceclient auf und ruft auf Close. Da es bereits geschlossen ist, kann es keinen Schaden verursachen. Ich benutze dies ohne Probleme im Produktionscode.

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

Wenn Sie dann auf den Server zugreifen, erstellen Sie den Client und verwenden ihn usingim Autodisconect:

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

    Ws.Open();

    Ws.Test();
}

3

Zusammenfassung

Mit den in dieser Antwort beschriebenen Techniken kann ein WCF-Dienst in einem using-Block mit der folgenden Syntax verwendet werden:

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

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

Sie können dies natürlich noch weiter anpassen, um ein präziseres Programmiermodell zu erhalten, das auf Ihre Situation IMyServicezugeschnitten ist. Der Punkt ist jedoch, dass wir eine Implementierung der Repräsentation des Kanals erstellen können, die das Einwegmuster korrekt implementiert.


Einzelheiten

Alle bisher gegebenen Antworten befassen sich mit dem Problem, den "Fehler" in der WCF-Kanal-Implementierung von zu umgehen IDisposable. Die Antwort, die das knappste Programmiermodell zu bieten scheint (so dass Sie die verwenden usingBlock auf nicht verwalteten Ressourcen zu verfügen) ist dies ein - wo der Proxy modifed zu implementieren ist IDisposablemit einer fehlerfreien Umsetzung. Das Problem bei diesem Ansatz ist die Wartbarkeit. Wir müssen diese Funktionalität für jeden von uns verwendeten Proxy erneut implementieren. Anhand einer Variation dieser Antwort werden wir sehen, wie wir Komposition anstelle von Vererbung verwenden können, um diese Technik generisch zu machen.

Erster Versuch

Es scheint verschiedene Implementierungen für die IDisposableImplementierung zu geben, aber aus Gründen der Argumentation werden wir eine Anpassung der von der derzeit akzeptierten Antwort verwendeten verwenden .

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

Mit den oben genannten Klassen können wir jetzt schreiben

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

Dies ermöglicht es uns, unseren Service über den usingBlock zu nutzen:

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

Dies generisch machen

Bisher haben wir lediglich die Lösung von Tomas neu formuliert . Was verhindert, dass dieser Code generisch ist, ist die Tatsache, dass die ProxyWrapperKlasse für jeden gewünschten Servicevertrag neu implementiert werden muss. Wir werden uns nun eine Klasse ansehen, mit der wir diesen Typ mithilfe von IL dynamisch erstellen können:

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

Mit unserer neuen Helferklasse können wir jetzt schreiben

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

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

Beachten Sie, dass Sie dieselbe Technik (mit geringfügigen Änderungen) auch für automatisch generierte Clients verwenden können, die für ClientBase<>(anstatt zu verwenden ChannelFactory<>) erben , oder wenn Sie eine andere Implementierung von verwenden möchten, um IDisposableIhren Kanal zu schließen.


2

Ich mag diese Art, die Verbindung zu schließen:

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

1

Ich habe eine einfache Basisklasse geschrieben , die dies behandelt. Es ist als NuGet-Paket erhältlich und recht einfach zu bedienen.

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

Irgendwelche Updates für VS2013-.net 4.5.1? Gibt es Optionen für einen erneuten Versuch wie stackoverflow.com/a/9370880/206730 ? -
Kiquenet

@Kiquenet Ich arbeite nicht mehr an WCF. Wenn Sie mir eine Pull-Anfrage senden, kann ich diese zusammenführen und das Paket aktualisieren.
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();
            }
        }
    }
}

So können Sie return-Anweisungen gut schreiben:

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

1

Ich möchte die Implementierung von Service aus Marc Gravells Antwort für den Fall hinzufügen, dass ServiceClient anstelle von ChannelFactory verwendet wird.

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

Für Interessierte ist hier eine VB.NET-Übersetzung der akzeptierten Antwort (unten). Ich habe es der Kürze halber etwas verfeinert und einige der Tipps anderer in diesem Thread kombiniert.

Ich gebe zu, dass es für die Ursprungs-Tags (C #) kein Thema ist, aber da ich keine VB.NET-Version dieser guten Lösung finden konnte, gehe ich davon aus, dass auch andere suchen werden. Die Lambda-Übersetzung kann etwas knifflig sein, deshalb möchte ich jemandem die Mühe ersparen.

Beachten Sie, dass diese spezielle Implementierung die Möglichkeit bietet, die zur ServiceEndpointLaufzeit zu konfigurieren .


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

Verwendungszweck:

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

Unsere Systemarchitektur verwendet häufig das Unity IoC- Framework, um Instanzen von ClientBase zu erstellen, sodass es keine sichere Möglichkeit gibt, zu erzwingen, dass die anderen Entwickler sogar using{}Blöcke verwenden. Um es so narrensicher wie möglich zu machen, habe ich diese benutzerdefinierte Klasse erstellt, die ClientBase erweitert und das Schließen des Kanals beim Entsorgen oder beim Finalisieren behandelt, falls jemand die von Unity erstellte Instanz nicht explizit entsorgt.

Es gibt auch Dinge, die im Konstruktor erledigt werden mussten, um den Kanal für benutzerdefinierte Anmeldeinformationen und Dinge einzurichten, also ist das auch hier ...

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

Dann kann ein Kunde einfach:

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

Und der Anrufer kann Folgendes tun:

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

Sie verwenden niemals den Parameter disposing in Ihrer Dispose-Methode
CaffGeek

@Chad - Ich habe das allgemeine Entwurfsmuster von Microsoft zum Finalisieren / Entsorgen befolgt : msdn.microsoft.com/en-us/library/b1yfkh5e%28VS.71%29.aspx Es stimmt, dass ich die Variable jedoch nicht verwende, weil ich sie nicht verwende Es muss keine andere Bereinigung zwischen einer normalen Entsorgung und einer endgültigen Entsorgung durchgeführt werden. Es könnte umgeschrieben werden, wenn Finalize nur Dispose () aufruft und den Code von Dispose (bool) nach Dispose () verschiebt.
CodingWithSpike

Finalizer erhöhen den Overhead und sind nicht deterministisch. Ich vermeide sie wann immer möglich. Sie können die automatischen Fabriken von Unity verwenden, um Delegaten einzufügen und diese mithilfe von Blöcken einzufügen, oder (besser) das Verhalten beim Erstellen / Aufrufen / Entsorgen von Diensten hinter einer Methode auf einer injizierten Schnittstelle ausblenden. Jeder Aufruf der Abhängigkeit erstellt den Proxy, ruft ihn auf und entsorgt ihn.
TrueWill

0

Ich habe nur wenige Antworten auf diesen Beitrag gegeben und ihn an meine Bedürfnisse angepasst.

Ich wollte die Möglichkeit haben, etwas mit dem WCF-Client zu tun, bevor ich ihn verwende, also die DoSomethingWithClient()Methode.

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

Hier ist die Helferklasse:

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

Und ich kann es verwenden als:

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

Was ist mit dem Client-Konstruktor, der Bindung und Endpoing verwendet? TClient (Bindung, Endpoing)
Kiquenet

0

Ich habe meinen eigenen Wrapper für einen Kanal, der Dispose wie folgt implementiert:

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

Dies scheint gut zu funktionieren und ermöglicht die Verwendung eines using-Blocks.


0

Der folgende Helfer ermöglicht das Aufrufen voidund Nicht-Leeren von Methoden. Verwendungszweck:

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

Die Klasse selbst ist:

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

Überschreiben Sie Dispose () des Clients, ohne eine auf ClientBase basierende Proxy-Klasse generieren zu müssen, auch ohne die Erstellung und das Caching von Kanälen verwalten zu müssen ! (Beachten Sie, dass WcfClient keine ABSTRACT-Klasse ist und auf ClientBase basiert.)

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

Meine Methode bestand darin, eine geerbte Klasse zu erstellen, die IDisposable explizit implementiert. Dies ist nützlich für Leute, die die GUI verwenden, um die Dienstreferenz hinzuzufügen (Dienstreferenz hinzufügen). Ich lasse diese Klasse einfach im Projekt fallen und erstelle die Dienstreferenz und verwende sie anstelle des Standardclients:

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

Hinweis: Dies ist nur eine einfache Implementierung von dispose. Sie können eine komplexere Dispose-Logik implementieren, wenn Sie möchten.

Sie können dann alle Ihre mit dem regulären Service-Client getätigten Anrufe durch die sicheren Clients wie folgt ersetzen:

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

Ich mag diese Lösung, da ich keinen Zugriff auf die Schnittstellendefinitionen haben muss und die usingAnweisung wie erwartet verwenden kann, während mein Code mehr oder weniger gleich aussieht.

Sie müssen weiterhin die Ausnahmen behandeln, die ausgelöst werden können, wie in anderen Kommentaren in diesem Thread ausgeführt.


-2

Sie können auch eine verwenden DynamicProxy, um die Dispose()Methode zu erweitern . Auf diese Weise können Sie Folgendes tun:

using (var wrapperdProxy = new Proxy<yourProxy>())
{
   // Do whatever and dispose of Proxy<yourProxy> will be called and work properly.
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.