423 Stimmen

Was ist die beste Abhilfe für das WCF-Client "using" Block Problem?

Ich instanziere meine WCF-Dienst-Clients gerne innerhalb einer using Block, da dies so ziemlich die Standardmethode zur Verwendung von Ressourcen ist, die die IDisposable :

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

Aber, wie bereits in dieser MSDN-Artikel einen WCF-Client in eine using Block könnte alle Fehler maskieren, die dazu führen, dass der Client in einem fehlerhaften Zustand verbleibt (wie eine Zeitüberschreitung oder ein Kommunikationsproblem). Langer Rede kurzer Sinn, wenn Dispose() aufgerufen wird, wird der Client Close() Methode wird ausgelöst, löst aber einen Fehler aus, weil sie sich in einem fehlerhaften Zustand befindet. Die ursprüngliche Ausnahme wird dann von der zweiten Ausnahme überdeckt. Das ist nicht gut.

Die im MSDN-Artikel vorgeschlagene Abhilfe besteht darin, die Verwendung einer using Block zu verwenden, und stattdessen Ihre Clients zu instanziieren und sie etwa so zu verwenden:

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

Verglichen mit dem using Block, ich finde das hässlich. Und eine Menge Code zu schreiben, jedes Mal, wenn Sie einen Client benötigen.

Glücklicherweise habe ich einige andere Umgehungsmöglichkeiten gefunden, wie z. B. diese auf dem (jetzt nicht mehr existierenden) IServiceOriented Blog. 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(); 
            } 
        } 
     } 
} 

Das erlaubt dann:

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

Die Abhilfe, die ich derzeit versuche zu verwenden, habe ich zuerst auf blog.davidbarret.net . Im Grunde überschreiben Sie die Client Dispose() Methode, wo immer Sie sie verwenden. Etwa so:

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

Dies scheint es zu ermöglichen, die using wieder blockieren, ohne dass die Gefahr besteht, eine fehlerhafte Zustandsausnahme zu maskieren.

Gibt es noch andere Probleme, auf die ich bei der Verwendung dieser Workarounds achten muss? Ist jemandem etwas Besseres eingefallen?

0voto

Konstantin Spirin Punkte 19144

Der folgende Helfer erlaubt den Aufruf von void und nicht-offene Methoden. Verwendung:

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

0voto

hIpPy Punkte 4040

Ich habe mich auf einige Antworten in diesem Beitrag bezogen und sie nach meinen Bedürfnissen angepasst.

Ich wollte die Möglichkeit haben, etwas mit dem WCF-Client zu tun, bevor ich ihn verwende, damit 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 Hilfsklasse:

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 als verwenden:

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

0voto

Murad Duraidi Punkte 13

Dispose() des Clients überschreiben, ohne dass eine auf ClientBase basierende Proxy-Klasse erzeugt werden muss, und auch ohne die Notwendigkeit, die Erstellung und Zwischenspeicherung von Kanälen verwalten ! (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();
    }
}

0voto

Aleksandr Albert Punkte 1637

Meine Methode, dies zu tun, bestand darin, eine geerbte Klasse zu erstellen, die explizit IDisposable implementiert. Dies ist nützlich für Leute, die die Benutzeroberfläche verwenden, um die Dienstreferenz hinzuzufügen ( Add Service Reference ). Ich füge diese Klasse einfach in das Projekt ein, das die Dienstreferenz erstellt, 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 Anrufe, die Sie mit dem regulären Service-Client getätigt haben, durch die sicheren Clients ersetzen, etwa so:

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

Mir gefällt diese Lösung, da ich keinen Zugriff auf die Schnittstellendefinitionen haben muss und die using Anweisung, wie ich es erwarten würde, während mein Code mehr oder weniger gleich aussehen kann.

Sie müssen dennoch die Ausnahmen behandeln, die, wie in anderen Kommentaren in diesem Thread erwähnt, ausgelöst werden können.

-2voto

Uri Abramson Punkte 5719

Sie können auch eine DynamicProxy zur Erweiterung der Dispose() Methode. Auf diese Weise könnten Sie etwas tun wie:

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

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X