wake-up-neo.com

Was ist die beste Problemumgehung für den WCF-Client, der das Blockproblem verwendet?

Ich mag es, meine WCF-Service-Clients innerhalb eines using -Blocks zu instantiieren, da dies so ziemlich die Standardmethode ist, um Ressourcen zu verwenden, die IDisposable implementieren:

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

Wie in in diesem MSDN-Artikel erwähnt, kann das Umschließen eines WCF-Clients in einen using -Block alle Fehler maskieren, die dazu führen, dass der Client in einem fehlerhaften Zustand verbleibt (z. B. Zeitüberschreitung oder Kommunikation) Problem). Kurz gesagt, wenn Dispose () aufgerufen wird, wird die Close () -Methode des Clients ausgelöst, es wird jedoch ein Fehler ausgegeben, da der Status fehlerhaft ist. Die ursprüngliche Ausnahme wird dann durch die zweite Ausnahme maskiert. Nicht gut.

Die vorgeschlagene Problemumgehung im MSDN-Artikel besteht darin, die Verwendung eines using -Blocks vollständig zu vermeiden und stattdessen Ihre Clients zu instanziieren und sie in etwa 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 using -Block finde ich das hässlich. Und jede Menge Code, den Sie jedes Mal schreiben müssen, wenn Sie einen Client benötigen.

Zum Glück habe ich einige andere Problemumgehungen gefunden, wie z. B. diese bei 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 using -Block.

Die Problemumgehung, die ich derzeit versuche, habe ich zuerst auf blog.davidbarret.net gelesen. Grundsätzlich überschreiben Sie die Dispose() -Methode des Clients, wo immer Sie sie verwenden. So 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 Block using erneut zuzulassen, ohne dass die Gefahr besteht, eine fehlerhafte Zustandsausnahme zu maskieren.

Gibt es also andere Fallstricke, auf die ich achten muss, um diese Problemumgehungen zu verwenden? Hat sich jemand etwas Besseres einfallen lassen?

394
Eric King

Eigentlich, obwohl ich gebloggt (siehe Lukes Antwort ), denke ich das ist besser als mein IDisposable Wrapper. Typischer Code:

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

(per Kommentar bearbeiten)

Da Use void zurückgibt, 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
131
Marc Gravell

Angesichts der Wahl zwischen der von IServiceOriented.com empfohlenen Lösung und der von David Barrets Blog empfohlenen Lösung bevorzuge ich die Einfachheit, die durch Überschreiben der Dispose () -Methode des Clients geboten wird. Dies ermöglicht mir, die using () -Anweisung weiter zu verwenden, wie es bei einem Einwegobjekt zu erwarten ist. Wie @Brian betonte, enthält diese Lösung jedoch eine Race-Bedingung, die darin besteht, dass 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 verwendet, die das Beste aus beiden Welten kombiniert.

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

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 "Using" -Paradigma 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 folgendermaßen telefonieren:

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

Das ist so ziemlich genau wie in deinem Beispiel. In einigen Projekten schreiben wir stark typisierte Hilfsmethoden, so dass 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 eingebunden werden. Beispielsweise authentifiziert sich die Site auf einer Site gegenüber dem Dienst im Namen des angemeldeten Benutzers. (Die Site hat selbst keine Anmeldeinformationen.) Indem wir unseren eigenen "UseService" -Methoden-Helfer schreiben, können wir die Channel-Factory so konfigurieren, wie wir es möchten usw. Wir sind auch nicht an die Verwendung der generierten Proxys gebunden - jede Schnittstelle reicht aus .

31
MichaelGG

Dies ist die von Microsoft empfohlene Methode zum Verarbeiten 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 Diese Frage wurde anscheinend so häufig in WCF gestellt, dass Microsoft sogar ein spezielles Beispiel erstellt hat, um zu veranschaulichen, wie Ausnahmen behandelt werden:

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

Laden Sie das Beispiel herunter: C # oder VB

In Anbetracht der Tatsache, dass es so viele Probleme gibt einschließlich der using-Anweisung , (hitzig?) Interne Diskussionen und Threads zu diesem Thema gehe ich nicht Verschwenden Sie meine Zeit damit, ein Code-Cowboy zu werden und einen saubereren Weg zu finden. Ich werde es einfach aufsaugen und WCF-Clients so ausführlich (und dennoch vertrauenswürdig) für meine Serveranwendungen implementieren.

Optionale zusätzliche Fehler zu fangen

Viele Ausnahmen ergeben sich aus CommunicationException und ich denke nicht, dass die meisten dieser Ausnahmen wiederholt werden sollten. Ich habe die einzelnen Ausnahmen in MSDN durchgearbeitet und eine kurze Liste mit wieder verwendbaren Ausnahmen gefunden (zusätzlich zu TimeOutException oben). 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, das ist ein bisschen banaler Code, den man schreiben muss. Ich bevorzuge derzeit diese Antwort und sehe keine "Hacks" in diesem Code, die später Probleme verursachen könnten.

28

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 bereitzustellen. Es generiert einen zusätzlichen Proxy mit dem Namen ExceptionHandlingProxy<T>, Der ExceptionHandlingProxyBase<T> Erbt. Letzterer implementiert die Funktionalität des Proxys. Das Ergebnis ist, dass Sie wählen können, ob Sie den Standard-Proxy verwenden möchten, der ClientBase<T> Oder ExceptionHandlingProxy<T> Erbt, der die Verwaltung der Lebensdauer der Kanalfactory und des Kanals einschließt. ExceptionHandlingProxy berücksichtigt Ihre Auswahl im Dialogfeld Dienstreferenz hinzufügen in Bezug auf asynchrone Methoden und Auflistungstypen.

Codeplex hat ein Projekt mit dem Namen Exception Handling WCF Proxy Generator . Grundsätzlich wird ein neues benutzerdefiniertes Tool für Visual Studio 2008 installiert. Anschließend wird mit diesem Tool der neue Dienstproxy generiert (Dienstreferenz hinzufügen). Es hat einige nette Funktionen, um mit fehlerhaften Kanälen, Zeitüberschreitungen und sicherer Entsorgung umzugehen. Hier gibt es ein exzellentes Video mit dem Namen ExceptionHandlingProxyWrapper , das genau erklärt, wie das funktioniert.

Sie können die Using -Anweisung problemlos erneut 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 Befehl Abort() auf, entsorgt den Proxy und löst die Ausnahme erneut aus. Wenn der Dienst einen FaultException Code auslöst, wird die Ausführung abgebrochen und der Proxy wird wie erwartet sicher abgebrochen, um die richtige Ausnahme auszulösen.

14
Neil

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 keinen Dummy-Wert zurückgeben, wenn Sie eine void-Methode aufrufen, und Sie können den Service mehrfach aufrufen (und mehrere Werte zurückgeben), ohne Tupel verwenden zu müssen.

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

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

10
TrueWill

@ 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 dasselbe (Func<T, TResult>) im Falle von Service<IOrderService>.Use

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

8
pangular

Was ist das?

Dies ist die CW-Version der akzeptierten Antwort, aber mit (was ich als vollständig betrachte) Ausnahmebehandlung.

Die akzeptierte Antwort verweist auf diese Website gibt es nicht mehr . Um Ihnen Ärger zu ersparen, füge ich hier die wichtigsten Teile ein. Außerdem habe ich es leicht modifiziert, um Behandlung von Ausnahmewiederholungen einzuschließen, um diese lästigen Netzwerk-Timeouts zu behandeln.

Einfache WCF-Client-Verwendung

Sobald Sie Ihren clientseitigen Proxy generiert haben, müssen Sie ihn nur noch implementieren.

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 es ist Ihnen lieber, wenn Sie mit der Implementierung einverstanden sind oder sie bearbeiten, um sie zu verbessern.

7

Unten finden Sie eine erweiterte Version der Quelle von die Frage , die so erweitert wurde, dass mehrere Channel-Fabriken zwischengespeichert werden und versucht wird, den Endpunkt in der Konfigurationsdatei nach dem Vertragsnamen zu suchen.

Es verwendet .NET 4 (insbesondere: Contravariance, LINQ, var):

/// <summary>
/// Delegate type of the service method to perform.
/// </summary>
/// <param name="proxy">The service proxy.</param>
/// <typeparam name="T">The type of service to use.</typeparam>
internal delegate void UseServiceDelegate<in T>(T proxy);

/// <summary>
/// Wraps using a WCF service.
/// </summary>
/// <typeparam name="T">The type of service to use.</typeparam>
internal static class Service<T>
{
    /// <summary>
    /// A dictionary to hold looked-up endpoint names.
    /// </summary>
    private static readonly IDictionary<Type, string> cachedEndpointNames = new Dictionary<Type, string>();

    /// <summary>
    /// A dictionary to hold created channel factories.
    /// </summary>
    private static readonly IDictionary<string, ChannelFactory<T>> cachedFactories =
        new Dictionary<string, ChannelFactory<T>>();

    /// <summary>
    /// Uses the specified code block.
    /// </summary>
    /// <param name="codeBlock">The code block.</param>
    internal static void Use(UseServiceDelegate<T> codeBlock)
    {
        var factory = GetChannelFactory();
        var proxy = (IClientChannel)factory.CreateChannel();
        var success = false;

        try
        {
            using (proxy)
            {
                codeBlock((T)proxy);
            }

            success = true;
        }
        finally
        {
            if (!success)
            {
                proxy.Abort();
            }
        }
    }

    /// <summary>
    /// Gets the channel factory.
    /// </summary>
    /// <returns>The channel factory.</returns>
    private static ChannelFactory<T> GetChannelFactory()
    {
        lock (cachedFactories)
        {
            var endpointName = GetEndpointName();

            if (cachedFactories.ContainsKey(endpointName))
            {
                return cachedFactories[endpointName];
            }

            var factory = new ChannelFactory<T>(endpointName);

            cachedFactories.Add(endpointName, factory);
            return factory;
        }
    }

    /// <summary>
    /// Gets the name of the endpoint.
    /// </summary>
    /// <returns>The name of the endpoint.</returns>
    private static string GetEndpointName()
    {
        var type = typeof(T);
        var fullName = type.FullName;

        lock (cachedFactories)
        {
            if (cachedEndpointNames.ContainsKey(type))
            {
                return cachedEndpointNames[type];
            }

            var serviceModel = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None).SectionGroups["system.serviceModel"] as ServiceModelSectionGroup;

            if ((serviceModel != null) && !string.IsNullOrEmpty(fullName))
            {
                foreach (var endpointName in serviceModel.Client.Endpoints.Cast<ChannelEndpointElement>().Where(endpoint => fullName.EndsWith(endpoint.Contract)).Select(endpoint => endpoint.Name))
                {
                    cachedEndpointNames.Add(type, endpointName);
                    return endpointName;
                }
            }
        }

        throw new InvalidOperationException("Could not find endpoint element for type '" + fullName + "' in the ServiceModel client configuration section. This might be because no configuration file was found for your application, or because no endpoint element matching this name could be found in the client element.");
    }
}
7
Jesse C. Slicer

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 weitere Ausnahmen abfangen, wenn dies erforderlich ist, aber das Prinzip bleibt dasselbe.

5
Tomas Jansson

Wenn Sie IoC nicht benötigen oder einen automatisch generierten Client (Service Reference) verwenden, können Sie das Schließen einfach mit einem Wrapper verwalten und das GC Nimm die Client-Datenbank, wenn sie sich in einem sicheren Zustand befindet, der keine Ausnahme auslöst. Der GC ruft Dispose in serviceclient auf, und dies ruft Close auf. Da es bereits geschlossen ist, kann es keinen Schaden anrichten. Ich benutze dies ohne Probleme in 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 using für die automatische Trennung:

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

    Ws.Open();

    Ws.Test();
}
4
Luiz Felipe

Ich habe den dynamischen Proxy von Castle verwendet, um das Dispose () -Problem zu lösen. Außerdem habe ich 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 übernimmt. 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 injizieren können, ohne dass sich die Verbraucher um Details von WCF kümmern müssen. Und wie bei den anderen Lösungen gibt es kein zusätzliches Cruft.

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

4
Jay Douglass

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

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 für Ihre Situation präziseres Programmiermodell zu erhalten - aber der Punkt ist, dass wir eine Implementierung von IMyService erstellen können, die den Kanal darstellt, der das verfügbare Muster korrekt implementiert.


Einzelheiten

Alle bisher gegebenen Antworten befassen sich mit dem Problem, den "Bug" in der WCF-Channel-Implementierung von IDisposable zu umgehen. Die Antwort, die das prägnanteste Programmiermodell zu bieten scheint (mit dem Sie den Block using für nicht verwaltete Ressourcen verwenden können), lautet dieses -, wobei der Proxy so geändert wird, dass IDisposable mit einer fehlerfreien Implementierung implementiert wird . Das Problem bei diesem Ansatz ist die Wartbarkeit - wir müssen diese Funktionalität für jeden von uns verwendeten Proxy erneut implementieren. Bei 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 gibt verschiedene Implementierungen für die Implementierung von IDisposable, aber aus Gründen der Argumentation werden wir eine Anpassung der von derzeit akzeptierte 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();
    }
}

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

Auf diese Weise können wir unseren Service mit dem Block using nutzen:

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

Dies generisch machen

Bisher haben wir nur Tomas 'Lösung umformuliert. Was verhindert, dass dieser Code generisch ist, ist die Tatsache, dass die Klasse ProxyWrapper für jeden von uns gewünschten Servicevertrag erneut implementiert werden muss. Wir werden uns nun eine Klasse ansehen, mit der wir diesen Typ dynamisch mit IL 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<> Erben (anstatt ChannelFactory<> Zu verwenden), oder wenn Sie eine andere Implementierung von IDisposable verwenden möchten. um deinen Kanal zu schließen.

3
Lawrence

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

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

Für Interessenten 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 ursprünglichen Tags (C #) kein Thema ist, aber da ich keine VB.NET-Version dieser guten Lösung finden konnte, gehe ich davon aus, dass andere auch danach 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, ServiceEndpoint zur Laufzeit 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

Verwendung:

Public ReadOnly Property Jobs As List(Of Service.Job)
  Get
    Disposable(Of IService).Use(Sub(Client) Jobs = Client.GetJobs(Me.Status))
  End Get
End Property

Public ReadOnly Property Jobs As List(Of Service.Job)
  Get
    Return Disposable(Of IService).Use(Function(Client) Client.GetJobs(Me.Status))
  End Get
End Property
1
InteXX
public static class Service<TChannel>
{
    public static ChannelFactory<TChannel> ChannelFactory = new ChannelFactory<TChannel>("*");

    public static TReturn Use<TReturn>(Func<TChannel,TReturn> codeBlock)
    {
        var proxy = (IClientChannel)ChannelFactory.CreateChannel();
        var success = false;
        try
        {
            var result = codeBlock((TChannel)proxy);
            proxy.Close();
            success = true;
            return result;
        }
        finally
        {
            if (!success)
            {
                proxy.Abort();
            }
        }
    }
}

So ist es möglich, Return-Anweisungen schön zu schreiben:

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

Unsere Systemarchitektur verwendet häufig das nityIoC 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 Abschließen für den Fall handhabt, dass jemand die von Unity erstellte Instanz nicht explizit entsorgt.

Es gibt auch Dinge, die im Konstruktor erledigt werden müssen, um den Kanal für benutzerdefinierte Anmeldeinformationen und ähnliches einzurichten.

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

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

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
PSsam

Meine Methode hierfür bestand darin, eine geerbte Klasse zu erstellen, die IDisposable explizit implementiert. Dies ist nützlich für Benutzer, die die Benutzeroberfläche zum Hinzufügen der Dienstreferenz verwenden (Dienstreferenz hinzufügen). Ich füge diese Klasse einfach in das Projekt ein und verwende sie anstelle des Standard-Clients:

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. Wenn Sie möchten, können Sie eine komplexere Dispositionslogik implementieren.

Sie können dann alle Anrufe, die Sie mit dem regulären Service-Client getätigt haben, durch sichere Clients 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 using -Anweisung wie erwartet verwenden kann, während mein Code mehr oder weniger gleich aussieht.

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

0

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

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

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

Der folgende Helfer erlaubt den Aufruf von void und non-void-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 Proxy-Klasse basierend auf ClientBase generieren zu müssen, auch ohne Kanalerstellung und Caching 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
Murad Duraidi

Ich habe meinen eigenen Wrapper für einen Channel, 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
Joe