Jakie jest najlepsze obejście problemu "używania" bloku przez klienta WCF?

Lubię tworzyć instancje moich klientów usług WCF w obrębie bloku using, ponieważ jest to standardowy sposób korzystania z zasobów implementujących IDisposable:

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

Ale, jak zauważono w w tym artykule MSDN , owinięcie klienta WCF w Blok using może zamaskować wszelkie błędy, które spowodują pozostawienie klienta w stanie uszkodzonym (np. timeout lub problem z komunikacją). Krótko mówiąc, gdy wywołana jest Dispose (), wywołana jest metoda Close() klienta, ale wyrzuca błąd, ponieważ jest w stan faulowany. Oryginalny wyjątek jest następnie maskowany przez drugi wyjątek. Niedobrze.

Sugerowanym obejściem w artykule MSDN jest całkowite unikanie używania bloku using, a zamiast tego tworzenie instancji klientów i używanie ich w następujący sposób:

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

W porównaniu do using bloku, myślę, że to brzydkie. I dużo kodu do napisania za każdym razem, gdy potrzebujesz klienta.

Na szczęście znalazłem kilka innych obejść, takich jak ten Na iService oriented. Zaczynasz z:

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

Który następnie pozwala:

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

Nie jest źle, ale myślę, że nie jest tak wyrazisty i zrozumiały jak using blok.

Obejście, którego obecnie próbuję użyć, o którym przeczytałem po raz pierwszy na blog.davidbarret.net . w zasadzie nadpisujesz metodę klienta Dispose() gdziekolwiek jej używasz. Coś w stylu:

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

To wydaje się być w stanie ponownie zezwolić na blok using bez niebezpieczeństwa maskowania wyjątku stanu uszkodzonego.

Czy są jakieś inne Gotcha, na których muszę uważać używając tych obejść? Czy ktoś wymyślił coś lepszego?

Author: live2, 2009-02-22

26 answers

Właściwie, chociaż blogowałem (Zobacz odpowiedź Łukasza), myślę, że Ten jest lepszy niż mój IDisposable wrapper. Typowy kod:

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

(edit per comments)

Ponieważ Use zwraca void, najprostszym sposobem obsługi zwracanych wartości jest przechwycona zmienna:

int newOrderId = 0; // need a value for definite assignment
Service<IOrderService>.Use(orderService=>
  {
    newOrderId = orderService.PlaceOrder(request);
  });
Console.WriteLine(newOrderId); // should be updated
 127
Author: Marc Gravell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:12

Biorąc pod uwagę wybór rozwiązania zalecanego przez IServiceOriented.com i rozwiązanie zaproponowane przez Blog Davida Barreta, wolę prostotę oferowaną przez nadpisanie metody Dispose () klienta. Pozwala mi to na dalsze używanie instrukcji using() tak, jak można by się spodziewać w przypadku obiektu jednorazowego użytku. Jednak, jak zauważył @ Brian, To rozwiązanie zawiera warunek rasy w tym, że stan może nie być uszkodzony, gdy jest sprawdzany, ale może być do czasu wywołania Close (), w w którym przypadku komunikacja nadal występuje.

Więc, aby to obejść, użyłem rozwiązania, które łączy to, co najlepsze z obu światów.
void IDisposable.Dispose()
{
    bool success = false;
    try 
    {
        if (State != CommunicationState.Faulted) 
        {
            Close();
            success = true;
        }
    } 
    finally 
    {
        if (!success) 
            Abort();
    }
}
 84
Author: Matt Davis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-17 18:48:03

Napisałem funkcję wyższego rzędu , Aby to działało poprawnie. Wykorzystaliśmy to w kilku projektach i wydaje się, że działa świetnie. W ten sposób rzeczy powinny być robione od początku, bez paradygmatu "używania" itp.

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

Możesz wykonywać połączenia w ten sposób:

int a = 1;
int b = 2;
int sum = UseService((ICalculator calc) => calc.Add(a, b));
Console.WriteLine(sum);
To jest prawie tak samo jak w twoim przykładzie. W niektórych projektach piszemy silnie wpisane metody pomocnicze, więc kończymy pisząc rzeczy takie jak " Wcf.UseFooService (f= > f...)".

I find it całkiem eleganckie, wszystko biorąc pod uwagę. Czy napotkałeś jakiś szczególny problem?

Pozwala to podłączyć inne sprytne funkcje. Na przykład w jednej witrynie witryna uwierzytelnia się do usługi w imieniu zalogowanego użytkownika. (Witryna sama w sobie nie ma poświadczeń.) Pisząc własny helper metody "UseService", możemy skonfigurować fabrykę kanałów tak, jak chcemy, itp. Nie jesteśmy również zobowiązani do korzystania z wygenerowanych proxy - każdy interfejs wystarczy.

 28
Author: MichaelGG,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-10 06:21:24

Jest to zalecany przez Microsoft sposób obsługi wywołań klienta WCF:

Aby uzyskać więcej szczegółów zobacz: oczekiwane wyjątki

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

Informacje dodatkowe Tak wiele osób wydaje się zadawać to pytanie na WCF, że Microsoft stworzył nawet dedykowaną próbkę, aby zademonstrować, jak radzić sobie z wyjątkami: {]}

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

Pobierz próbkę: C# lub VB

Biorąc pod uwagę, że jest tak wiele problemów związanych z używaniem instrukcji, (ogrzewany?) Wewnętrzne dyskusje i wątki w tym temacie Nie będę tracił czasu próbując stać się kowbojem kodu i znaleźć czystszy sposób. Po prostu wciągnę to i zaimplementuję klientów WCF w ten (jeszcze zaufany) sposób dla moich aplikacji serwerowych.

Opcjonalne dodatkowe awarie połowu

Wiele wyjątków pochodzi od CommunicationException i nie sądzę, aby większość z tych wyjątków powinna być ponownie osądzony. Przeglądałem każdy wyjątek w MSDN i znalazłem krótką listę wyjątków, które można powtórzyć (oprócz TimeOutException powyżej). Daj mi znać, jeśli przegapiłem wyjątek, który należy powtórzyć.

  // 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
}
Trzeba przyznać, że to trochę przyziemny kod do napisania. Obecnie preferuję tę odpowiedź i nie widzę żadnych "hacków" w tym kodzie, które mogą powodować problemy na drodze.
 26
Author: random65537,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:24

W końcu znalazłem pewne solidne kroki w kierunku czystego rozwiązania tego problemu.

To niestandardowe narzędzie rozszerza WCFProxyGenerator, aby zapewnić proxy obsługi wyjątków. Generuje on dodatkowy serwer proxy o nazwie ExceptionHandlingProxy<T>, który dziedziczy ExceptionHandlingProxyBase<T> - Ten ostatni implementuje funkcję serwera proxy. W rezultacie możesz użyć domyślnego serwera proxy, który dziedziczy ClientBase<T> lub ExceptionHandlingProxy<T>, który enkapsuje zarządzanie żywotnością fabryki kanału i kanału. ExceptionHandlingProxy szanuje twoje wybory w oknie dialogowym Dodaj odniesienie do usługi w odniesieniu do metod asynchronicznych i typów kolekcji.

Codeplex posiada projekt o nazwie Exception Handling WCF Proxy Generator. Zasadniczo instaluje nowe niestandardowe narzędzie do Visual Studio 2008, a następnie za pomocą tego narzędzia generuje nowy serwer proxy usługi (Dodaj odniesienie do usługi) . Ma kilka ładnych funkcji do radzenia sobie z uszkodzonymi kanałami, przerwami czasowymi i bezpiecznym usuwaniem. Jest tutaj doskonały film o nazwie ExceptionHandlingProxyWrapper wyjaśniający dokładnie, jak to działa.

Można bezpiecznie użyć instrukcji Using Ponownie, a jeśli kanał zostanie uszkodzony w dowolnym żądaniu (TimeoutException lub CommunicationException), Wrapper ponownie zainicjuje kanał uszkodzony i ponownie spróbuje zapytania. Jeśli to się nie powiedzie, wywoła polecenie Abort() i pozbywa się proxy i ponownie usuwa wyjątek. Jeśli usługa rzuci kod FaultException, przestanie wykonywać, a proxy zostanie bezpiecznie przerwane, rzucając prawidłowy wyjątek zgodnie z oczekiwaniami.

 14
Author: Neil,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-26 16:40:58

W oparciu o odpowiedzi marca Gravella, Michaelgga i Matta Davisa, nasi programiści opracowali:]}

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

Przykład użycia:

string result = string.Empty;

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

Jest jak najbardziej zbliżona do składni "using", nie musisz zwracać wartości atrapy podczas wywoływania metody void i możesz wykonywać wiele wywołań usługi (i zwracać wiele wartości) bez konieczności używania krotek.

Możesz również użyć tego z ClientBase<T> potomkami zamiast ChannelFactory, jeśli chcesz.

The metoda rozszerzenia jest narażona, jeśli programista chce ręcznie pozbyć się proxy / kanału.

 10
Author: TrueWill,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-24 20:41:27

@Marc Gravell

Wouldn ' t it be OK to use this:

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

Lub to samo (Func<T, TResult>) w przypadku Service<IOrderService>.Use

Ułatwiłoby to zwracanie zmiennych.

 8
Author: codeRecap,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-02 15:16:56

Co to jest?

To jest wersja CW zaakceptowanej odpowiedzi, ale z (co uważam za kompletną) obsługą WYJĄTKÓW.

Zaakceptowana odpowiedź odwołuje się do tej strony, która już nie istnieje . Aby oszczędzić wam kłopotów, załączam tutaj najważniejsze części. Dodatkowo zmodyfikowałem go nieznacznie, aby uwzględnić obsługę powtórzeń exception , aby obsłużyć te nieprzyjemne przerwy czasowe w sieci.

Simple WCF client Usage

Once generujesz serwer proxy po stronie klienta, to wszystko, czego potrzebujesz, aby go zaimplementować.

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

/ align = "left" / cs

Dodaj ten plik do swojego rozwiązania. Nie są potrzebne żadne zmiany w tym pliku, chyba że chcesz zmienić liczbę powtórzeń lub wyjątki, które chcesz obsłużyć.

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: zrobiłem ten post wiki społeczności. Nie będę zbierał "punktów" z tej odpowiedzi, ale wolę, abyś ją poprawił, jeśli zgadzasz się z implementacją, lub edytował, aby była lepsza.

 7
Author: LamonteCristo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:11

Poniżej znajduje się ulepszona wersja źródła z pytanie i rozszerzone do buforowania wielu fabryk kanałów i próby wyszukania punktu końcowego w pliku konfiguracyjnym według nazwy kontraktu.

Używa. NET 4 (konkretnie: 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
Author: Jesse C. Slicer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:03:02

Taki wrapper by zadziałał:

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

To powinno umożliwiać pisanie kodu w stylu:

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

Owijarka może oczywiście wychwycić więcej WYJĄTKÓW, jeśli jest to wymagane, ale zasada pozostaje taka sama.

 5
Author: Tomas Jansson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-26 16:43:08

Użyłem Castle dynamic proxy do rozwiązania problemu Dispose (), a także zaimplementowałem automatyczne odświeżanie kanału, gdy jest on w stanie bezużytecznym. Aby z niego skorzystać, musisz utworzyć nowy interfejs, który dziedziczy Twoją umowę serwisową i jest możliwy do zidentyfikowania. Dynamiczny serwer proxy implementuje ten interfejs i otacza kanał WCF:

Func<object> createChannel = () =>
    ChannelFactory<IHelloWorldService>
        .CreateChannel(new NetTcpBinding(), new EndpointAddress(uri));
var factory = new WcfProxyFactory();
var proxy = factory.Create<IDisposableHelloWorldService>(createChannel);
proxy.HelloWorld();
Podoba mi się to, ponieważ możesz wprowadzić usługi WCF bez potrzeby martwienia się o szczegóły WCF. I nie ma dodanych cruft jak inne rozwiązania.

Spójrz na kod, jest to właściwie dość proste: WCF Dynamic Proxy

 4
Author: Jay Douglass,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-06 05:29:58

Jeśli nie potrzebujesz IoC lub korzystasz z automatycznego klienta (referencji usługi), możesz po prostu użyć wrappera do zarządzania zamknięciem i pozwolić GC zająć bazę klientów, gdy jest ona w bezpiecznym stanie, który nie wyrzuci żadnego wyjątku. GC wywoła Dispose w serviceclient, a to wywoła Close. Ponieważ jest już zamknięty, nie może spowodować żadnych szkód. Używam tego bez problemów w kodzie produkcyjnym.

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

Wtedy, gdy uzyskujesz dostęp do serwera, Utwórz klienta i użyj using w autodisconect:

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

    Ws.Open();

    Ws.Test();
}
 4
Author: Luiz Felipe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-26 16:53:10

Użyj metody rozszerzenia:

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
Author: Johan Nyman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-09 08:47:10

Podsumowanie

Używając technik opisanych w tej odpowiedzi można wykorzystać usługę WCF w bloku używającym o następującej składni:

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

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

Możesz oczywiście dostosować to jeszcze bardziej, aby osiągnąć bardziej zwięzły model programowania specyficzny dla twojej sytuacji - ale chodzi o to, że możemy stworzyć implementację IMyService reprenting kanału, który poprawnie implementuje jednorazowy wzór.


Szczegóły

Wszystkie odpowiedzi udzielone do tej pory rozwiązanie problemu obejścia "błędu" w implementacji kanału WCF IDisposable. Odpowiedź, która wydaje się oferować najbardziej zwięzły model programowania (pozwalający na użycie bloku using do rozporządzania niezarządzanymi zasobami) to ten - gdzie proxy jest modyfikowany do implementacji IDisposable z implementacją wolną od błędów. Problemem z tym podejściem jest konserwowalność - musimy ponownie zaimplementować tę funkcjonalność dla każdego serwera proxy, którego używamy. Na odmianie tej odpowiedzi zobaczymy jak możemy użyć kompozycji zamiast dziedziczenia, aby uczynić tę technikę ogólną.

Pierwsza Próba

Wydaje się, że istnieją różne implementacje dla IDisposable implementacji, ale ze względu na argument użyjemy adaptacji używanej przez aktualnie akceptowaną odpowiedź.

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

Uzbrojeni w powyższe klasy możemy teraz napisać

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

To pozwala nam korzystać z naszej usługi za pomocą using bloku:

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

Making this generic

Dotychczas zrobiliśmy tylko przeformułowanie rozwiązania Tomasa . To, co uniemożliwia ten kod z generycznego jest fakt, że klasa ProxyWrapper musi być ponownie zaimplementowana dla każdego kontraktu serwisowego, który chcemy. Teraz przyjrzymy się klasie, która pozwala nam tworzyć ten typ dynamicznie używając IL:

public class ServiceHelper<T>
{
    private readonly ChannelFactory<T> _channelFactory;

    private static readonly Func<T, IDisposable, T> _channelCreator;

    static ServiceHelper()
    {
        /** 
         * Create a method that can be used generate the channel. 
         * This is effectively a compiled verion of new ProxyWrappper(channel, channelDisposer) for our proxy type
         * */
        var assemblyName = Guid.NewGuid().ToString();
        var an = new AssemblyName(assemblyName);
        var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
        var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName);

        var proxyType = CreateProxyType(moduleBuilder, typeof(T), typeof(IDisposable));

        var channelCreatorMethod = new DynamicMethod("ChannelFactory", typeof(T),
            new[] { typeof(T), typeof(IDisposable) });

        var ilGen = channelCreatorMethod.GetILGenerator();
        var proxyVariable = ilGen.DeclareLocal(typeof(T));
        var disposableVariable = ilGen.DeclareLocal(typeof(IDisposable));
        ilGen.Emit(OpCodes.Ldarg, proxyVariable);
        ilGen.Emit(OpCodes.Ldarg, disposableVariable);
        ilGen.Emit(OpCodes.Newobj, proxyType.GetConstructor(new[] { typeof(T), typeof(IDisposable) }));
        ilGen.Emit(OpCodes.Ret);

        _channelCreator =
            (Func<T, IDisposable, T>)channelCreatorMethod.CreateDelegate(typeof(Func<T, IDisposable, T>));

    }

    public ServiceHelper(ChannelFactory<T> channelFactory)
    {
        _channelFactory = channelFactory;
    }

    public T CreateChannel()
    {
        var channel = _channelFactory.CreateChannel();
        var channelDisposer = new ProxyDisposer(channel as IClientChannel);
        return _channelCreator(channel, channelDisposer);
    }

   /**
    * Creates a dynamic type analogous to ProxyWrapper, implementing T and IDisposable.
    * This method is actually more generic than this exact scenario.
    * */
    private static Type CreateProxyType(ModuleBuilder moduleBuilder, params Type[] interfacesToInjectAndImplement)
    {
        TypeBuilder tb = moduleBuilder.DefineType(Guid.NewGuid().ToString(),
            TypeAttributes.Public | TypeAttributes.Class);

        var typeFields = interfacesToInjectAndImplement.ToDictionary(tf => tf,
            tf => tb.DefineField("_" + tf.Name, tf, FieldAttributes.Private));

        #region Constructor

        var constructorBuilder = tb.DefineConstructor(
            MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName |
            MethodAttributes.RTSpecialName,
            CallingConventions.Standard,
            interfacesToInjectAndImplement);

        var il = constructorBuilder.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Call, typeof(object).GetConstructor(new Type[0]));

        for (var i = 1; i <= interfacesToInjectAndImplement.Length; i++)
        {
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldarg, i);
            il.Emit(OpCodes.Stfld, typeFields[interfacesToInjectAndImplement[i - 1]]);
        }
        il.Emit(OpCodes.Ret);

        #endregion

        #region Add Interface Implementations

        foreach (var type in interfacesToInjectAndImplement)
        {
            tb.AddInterfaceImplementation(type);
        }

        #endregion

        #region Implement Interfaces

        foreach (var type in interfacesToInjectAndImplement)
        {
            foreach (var method in type.GetMethods())
            {
                var methodBuilder = tb.DefineMethod(method.Name,
                    MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig |
                    MethodAttributes.Final | MethodAttributes.NewSlot,
                    method.ReturnType,
                    method.GetParameters().Select(p => p.ParameterType).ToArray());
                il = methodBuilder.GetILGenerator();

                if (method.ReturnType == typeof(void))
                {
                    il.Emit(OpCodes.Nop);
                    il.Emit(OpCodes.Ldarg_0);
                    il.Emit(OpCodes.Ldfld, typeFields[type]);
                    il.Emit(OpCodes.Callvirt, method);
                    il.Emit(OpCodes.Ret);
                }
                else
                {
                    il.DeclareLocal(method.ReturnType);

                    il.Emit(OpCodes.Nop);
                    il.Emit(OpCodes.Ldarg_0);
                    il.Emit(OpCodes.Ldfld, typeFields[type]);

                    var methodParameterInfos = method.GetParameters();
                    for (var i = 0; i < methodParameterInfos.Length; i++)
                        il.Emit(OpCodes.Ldarg, (i + 1));
                    il.Emit(OpCodes.Callvirt, method);

                    il.Emit(OpCodes.Stloc_0);
                    var defineLabel = il.DefineLabel();
                    il.Emit(OpCodes.Br_S, defineLabel);
                    il.MarkLabel(defineLabel);
                    il.Emit(OpCodes.Ldloc_0);
                    il.Emit(OpCodes.Ret);
                }

                tb.DefineMethodOverride(methodBuilder, method);
            }
        }

        #endregion

        return tb.CreateType();
    }
}

Z naszą nową klasą pomocniczą możemy teraz napisać

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

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

Zauważ, że możesz również użyć tej samej techniki (z niewielkimi modyfikacjami) Dla automatycznie generowanych klientów dziedziczenie dla ClientBase<> (zamiast używania ChannelFactory<>), lub jeśli chcesz użyć innej implementacji IDisposable do zamknięcia kanału.

 3
Author: Lawrence,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:07

Podoba mi się ten sposób zamykania połączenia:

var client = new ProxyClient();
try
{
    ...
    client.Close();
}
finally
{
    if(client.State != CommunicationState.Closed)
        client.Abort();
}
 2
Author: Uriil,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-07 07:07:59

Napisałem prostą klasę bazową , która zajmuje się tym. Jest dostępny jako pakiet NuGet i jest dość łatwy w użyciu.

//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);
    }
}
 1
Author: Ufuk Hacıoğulları,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-15 22:20:54
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();
            }
        }
    }
}

Pozwala więc ładnie pisać deklaracje zwrotne:

return Service<IOrderService>.Use(orderService => 
{ 
    return orderService.PlaceOrder(request); 
}); 
 1
Author: Andriy Buday,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-08 16:22:56

Chciałbym dodać implementację usługi z Marc Gravell ' s answer w przypadku używania ServiceClient zamiast ChannelFactory.

public interface IServiceConnector<out TServiceInterface>
{
    void Connect(Action<TServiceInterface> clientUsage);
    TResult Connect<TResult>(Func<TServiceInterface, TResult> channelUsage);
}

internal class ServiceConnector<TService, TServiceInterface> : IServiceConnector<TServiceInterface>
    where TServiceInterface : class where TService : ClientBase<TServiceInterface>, TServiceInterface, new()
{
    public TResult Connect<TResult>(Func<TServiceInterface, TResult> channelUsage)
    {
        var result = default(TResult);
        Connect(channel =>
        {
            result = channelUsage(channel);
        });
        return result;
    }

    public void Connect(Action<TServiceInterface> clientUsage)
    {
        if (clientUsage == null)
        {
            throw new ArgumentNullException("clientUsage");
        }
        var isChanneldClosed = false;
        var client = new TService();
        try
        {
            clientUsage(client);
            client.Close();
            isChanneldClosed = true;
        }
        finally
        {
            if (!isChanneldClosed)
            {
                client.Abort();
            }
        }
    }
}
 1
Author: PSsam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:03:02

Dla zainteresowanych, oto VB.NET tłumaczenie zaakceptowanej Odpowiedzi (poniżej). Dopracowałem go trochę dla zwięzłości, łącząc niektóre wskazówki przez innych w tym wątku.

Przyznaję, że to off-topic dla znaczników źródłowych (C#), ale ponieważ nie byłem w stanie znaleźć VB.NET wersja tego dobrego rozwiązania zakładam, że inni będą szukać, jak również. Tłumaczenie Lambda może być trochę trudne, więc chciałbym oszczędzić komuś kłopotu.

Zauważ, że ta konkretna implementacja umożliwia konfigurację ServiceEndpoint w czasie wykonywania.


Kod:

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

Użycie:

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
Author: InteXX,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-07 04:59:56

Nasza Architektura systemu często używa Unity IOC framework do tworzenia instancji ClientBase, więc nie ma pewnego sposobu, aby wymusić, że inni programiści używają nawet bloków using{}. Aby uczynić go jak najbardziej niezawodnym, stworzyłem tę niestandardową klasę, która rozszerza ClientBase i obsługuje zamykanie kanału na dispose, lub na finalize w przypadku, gdy ktoś nie usunie utworzonej instancji Unity.

Są też rzeczy, które trzeba było zrobić w konstruktor skonfiguruje kanał dla niestandardowych poświadczeń i takich tam, więc to też jest tutaj...

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

Wtedy klient może po prostu:

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

I rozmówca może wykonać dowolne z nich:

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
Author: CodingWithSpike,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-26 16:36:19

Skierowałem kilka odpowiedzi na ten post i dostosowałem go zgodnie z moimi potrzebami.

Chciałem mieć możliwość zrobienia czegoś z klientem WCF przed użyciem metody DoSomethingWithClient().

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

Oto Klasa pomocnicza:

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

I mogę go użyć jako:

string data = Service<ServiceClient>.Use(x => x.GetData(7));
 0
Author: hIpPy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-16 06:53:23

Mam własny wrapper dla kanału, który implementuje w następujący sposób:

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

To wydaje się działać dobrze i pozwala na użycie bloku using.

 0
Author: Joe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-15 12:13:46

Poniższy helper pozwala na wywołanie metod void i non-void. Sposób użycia:

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

Klasa sama w sobie to:

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
Author: Konstantin Spirin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-23 01:48:35

Nadpisuje client ' s Dispose () bez potrzeby generowania klasy proxy bazującej na ClientBase, również bez potrzeby zarządzania tworzeniem kanałów i buforowaniem ! (Zauważ, że WcfClient nie jest klasą abstrakcyjną i opiera się na ClientBase)

// No need for a generated proxy class
//using (WcfClient<IOrderService> orderService = new WcfClient<IOrderService>())
//{
//    results = orderService.GetProxy().PlaceOrder(input);
//}

public class WcfClient<TService> : ClientBase<TService>, IDisposable
    where TService : class
{
    public WcfClient()
    {
    }

    public WcfClient(string endpointConfigurationName) :
        base(endpointConfigurationName)
    {
    }

    public WcfClient(string endpointConfigurationName, string remoteAddress) :
        base(endpointConfigurationName, remoteAddress)
    {
    }

    public WcfClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :
        base(endpointConfigurationName, remoteAddress)
    {
    }

    public WcfClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
        base(binding, remoteAddress)
    {
    }

    protected virtual void OnDispose()
    {
        bool success = false;

        if ((base.Channel as IClientChannel) != null)
        {
            try
            {
                if ((base.Channel as IClientChannel).State != CommunicationState.Faulted)
                {
                    (base.Channel as IClientChannel).Close();
                    success = true;
                }
            }
            finally
            {
                if (!success)
                {
                    (base.Channel as IClientChannel).Abort();
                }
            }
        }
    }

    public TService GetProxy()
    {
        return this.Channel as TService;
    }

    public void Dispose()
    {
        OnDispose();
    }
}
 0
Author: Murad Duraidi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-07 11:25:06

Moją metodą było stworzenie odziedziczonej klasy, która jawnie implementuje IDisposable. Jest to przydatne dla ludzi, którzy używają gui, aby dodać odniesienie do usługi(Dodaj odniesienie do usługi). Po prostu opuszczam tę klasę w projekcie, tworząc odniesienie do usługi i używam jej zamiast domyślnego klienta:

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

Uwaga: Jest to tylko prosta implementacja dispose, możesz zaimplementować bardziej złożoną logikę dispose, jeśli chcesz.

Możesz następnie zastąpić wszystkie połączenia wykonane ze stałym klientem usług z bezpiecznymi klientami, jak to:

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

Podoba mi się to rozwiązanie, ponieważ nie wymaga ode mnie dostępu do definicji interfejsu i mogę używać using instrukcji tak, jak bym się spodziewał, pozwalając mojemu kodowi wyglądać mniej więcej tak samo.

Nadal będziesz musiał obsługiwać wyjątki, które mogą zostać rzucone, jak wskazano w innych komentarzach w tym wątku.

 0
Author: Aleksandr Albert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-28 19:30:39

Możesz również użyć DynamicProxy do rozszerzenia metody Dispose(). W ten sposób można zrobić coś w stylu:

using (var wrapperdProxy = new Proxy<yourProxy>())
{
   // Do whatever and dispose of Proxy<yourProxy> will be called and work properly.
}
 -2
Author: Uri Abramson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-01 15:55:23