Jakie jest najlepsze obejście problemu z blokiem `using` klienta klienta WCF?

404

Lubię tworzyć wystąpienia moich klientów usług WCF w usingbloku, ponieważ jest to prawie standardowy sposób korzystania z zasobów, które implementują IDisposable:

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

Ale, jak zauważono w tym artykule MSDN , zawijanie klienta WCF w plikuusing bloku może maskować wszelkie błędy, które powodują pozostawienie stanie błędu (np. Przekroczenie limitu czasu lub problem z komunikacją). Krótko mówiąc, gdy wywoływana jest funkcja Dispose (), metoda Close () klienta uruchamia się, ale generuje błąd, ponieważ jest w stanie błędu. Oryginalny wyjątek jest następnie maskowany przez drugi wyjątek. Niedobrze.

Sugerowanym obejściem w artykule MSDN jest całkowite unikanie korzystania z usingbloku, 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 to na IServiceOriented. 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(); 
            } 
        } 
     } 
} 

Co następnie pozwala:

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

To nie jest złe, ale nie sądzę, aby było tak wyraziste i łatwo zrozumiałe jak usingblok.

Obejście, którego obecnie próbuję użyć, najpierw przeczytałem na blog.davidbarret.net . Zasadniczo zastępujesz Dispose()metodę klienta, gdziekolwiek jej używasz. Coś jak:

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

Wydaje się, że jest to w stanie zezwolić na usingblok ponownie bez niebezpieczeństwa maskowania wyjątku stanu z błędem.

Czy są jakieś inne problemy, na które muszę uważać, korzystając z tych obejść? Czy ktoś wymyślił coś lepszego?

Eric King
źródło
42
Ostatni (który to sprawdza. Stan) to wyścig; może nie być zakłócona podczas sprawdzania wartości logicznej, ale może być zakłócona podczas wywoływania Close ().
Brian
15
Czytasz stan; to nie wina. Zanim wywołasz Close (), kanał nie działa. Rzuty Close (). Koniec gry.
Brian
4
Czas mija. Może to być bardzo krótki okres czasu, ale technicznie rzecz biorąc, w okresie pomiędzy sprawdzeniem stanu kanału i poproszeniem go o zamknięcie, stan kanału może się zmienić.
Eric King,
8
Użyłbym Action<T>zamiast UseServiceDelegate<T>. mniejszy.
hIpPy
2
Naprawdę nie podoba mi się ten pomocnik statyczny, Service<T>ponieważ komplikuje testowanie jednostkowe (jak robi to większość statycznych rzeczy). Wolałbym, aby był niestatyczny, aby mógł zostać wstrzyknięty do klasy, która go używa.
Fabio Marreco

Odpowiedzi:

137

Rzeczywiście, chociaż na blogu (patrz odpowiedź Luke'a ), myślę, że ten jest lepszy niż mój IDisposable owijki. Typowy kod:

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

(edytuj według komentarzy)

Ponieważ Usezwroty są nieważne, najłatwiejszym sposobem obsługi zwracanych wartości jest przechwycenie zmiennej:

int newOrderId = 0; // need a value for definite assignment
Service<IOrderService>.Use(orderService=>
  {
    newOrderId = orderService.PlaceOrder(request);
  });
Console.WriteLine(newOrderId); // should be updated
Marc Gravell
źródło
2
@MarcGravell Gdzie mogę wstrzyknąć tego klienta? Zakładam, że ChannelFactory tworzy klienta, a obiekt fabryki jest nowy w klasie Service, co oznacza, że ​​kod powinien być nieco refaktoryzowany, aby umożliwić niestandardową fabrykę. Czy to prawda, czy brakuje mi tutaj czegoś oczywistego?
Anttu
16
Możesz łatwo zmodyfikować opakowanie, aby wynik nie był potrzebny. Coś w tym stylu: public static TResult Use<TResult>(Func<T, TResult> codeBlock) { ... }
Chris
3
Może użyteczny https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/ i https://devzone.channeladam.com/articles/2014/09/how-to-easily-call-wcf-service-properly/ i http://dzimchuk.net/post/wcf-error-helpers
PreguntonCojoneroCabrón
Jak mogę dodać poświadczenie, używając tej metody?
Hippasus
2
Moim zdaniem najbardziej poprawnym rozwiązaniem byłoby: 1) Wykonanie wzoru Zamknij / Przerwij bez warunków wyścigu 2) Poradzić sobie z sytuacją, w której operacja serwisowa zgłasza wyjątki 3) Poradzić sobie z sytuacjami, w których metody Zamknij i Przerwij zgłaszają wyjątki 4) Poradzić sobie asynchroniczne wyjątki, takie jak ThreadAbortException https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/
Kiquenet,
88

Biorąc pod uwagę wybór między rozwiązaniem zalecanym przez IServiceOriented.com a rozwiązaniem zalecanym przez blog Davida Barreta , wolę prostotę oferowaną przez zastąpienie metody Dispose () klienta. To pozwala mi nadal używać instrukcji using (), jak można by się spodziewać po obiekcie jednorazowym. Jednak, jak wskazał @Brian, to rozwiązanie zawiera warunek wyścigu, w którym stan może nie być obwiniony, gdy jest sprawdzany, ale może być do czasu wywołania funkcji Close (), w którym to przypadku nadal występuje wyjątek CommunicationException.

Aby obejść ten problem, zastosowałem rozwiązanie, które łączy najlepsze cechy obu światów.

void IDisposable.Dispose()
{
    bool success = false;
    try 
    {
        if (State != CommunicationState.Faulted) 
        {
            Close();
            success = true;
        }
    } 
    finally 
    {
        if (!success) 
            Abort();
    }
}
Matt Davis
źródło
2
czy nie jest ryzykowne użycie wyrażenia „Try-Nareszcie” (lub syntaktycznego cukru - „using () {}”) z niezarządzanymi zasobami? W tym przypadku, jeśli opcja „Zamknij” się nie powiedzie, wyjątek nie zostanie przechwycony i ostatecznie może się nie uruchomić. Ponadto, jeśli w instrukcji last istnieje wyjątek, może maskować inne wyjątki. Myślę, że właśnie dlatego preferowany jest Try-Catch.
Zack Jannsen
Zack, niezrozumiały dla twojego obiektu; czego mi brakuje? Jeśli metoda Close zgłosi wyjątek, blok w końcu zostanie wykonany przed zgłoszeniem wyjątku. Dobrze?
Patrick Szalapski
1
@jmoreno, cofnąłem twoją edycję. Jeśli zauważysz, w metodzie nie ma żadnego bloku catch. Chodzi o to, że każdy wyjątek, który wystąpi (nawet w końcu), powinien zostać zgłoszony, a nie po cichu złapany.
Matt Davis,
5
@MattDavis Dlaczego w ogóle potrzebujesz successflagi? Dlaczego nie try { Close(); } catch { Abort(); throw; }?
Konstantin Spirin
Co powiesz na wypróbowanie / złapanie Close(); success = true;? Nie chciałbym, aby wyjątek został zgłoszony, gdybym mógł z powodzeniem przerwać go w ostatnim bloku. Chciałbym, aby wyjątek został zgłoszony tylko wtedy, gdy przerwanie () nie powiodło się w tym przypadku. W ten sposób try / catch ukryje potencjalny wyjątek warunku wyścigu i nadal pozwoli ci przerwać () połączenie w ostatnim bloku.
goku_da_master
32

Napisałem funkcję wyższego rzędu, aby działała poprawnie. Wykorzystaliśmy to w kilku projektach i wydaje się, że działa świetnie. Tak właśnie powinno było być od samego początku, bez paradygmatu „używania” lub tak dalej.

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ć takie połączenia:

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

To jest prawie tak jak w twoim przykładzie. W niektórych projektach piszemy silnie typowane metody pomocnicze, więc w końcu piszemy takie rzeczy jak „Wcf UseFooService (f => f ...)”.

Uważam to za dość eleganckie, biorąc pod uwagę wszystko. Czy napotkałeś szczególny problem?

Umożliwia to podłączenie innych fajnych funkcji. Na przykład w jednej witrynie witryna uwierzytelnia się w usłudze w imieniu zalogowanego użytkownika. (Strona sama w sobie nie ma poświadczeń.) Pisząc własnego pomocnika metody „UseService”, możemy skonfigurować fabrykę kanałów tak, jak chcemy, itp. Nie jesteśmy również zobowiązani do korzystania z generowanych serwerów proxy - dowolny interfejs zrobi .

MichaelGG
źródło
Dostaję wyjątek: właściwość Address na ChannelFactory.Endpoint była pusta. Punkt końcowy ChannelFactory musi mieć określony prawidłowy adres . Co to jest GetCachedFactorymetoda?
Marshall
28

Jest to zalecany przez Microsoft sposób obsługi połączeń klienckich WCF:

Aby uzyskać więcej informacji, 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();
}

Dodatkowe informacje Wydaje się, że tak wiele osób zadaje to pytanie w 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 istnieje tak wiele kwestii z udziałem using , (podgrzewany?) Wewnętrzne dyskusje i wątki na ten temat, nie będę tracić czasu próbuje stać kowboja kodu i znaleźć sposób czystszy. Po prostu wyssę to i zaimplementuję klientów WCF w ten pełny (ale zaufany) sposób dla moich aplikacji serwerowych.

Opcjonalne dodatkowe niepowodzenia wychwytywania

Wywodzi się wiele wyjątków CommunicationExceptioni nie sądzę, że większość z tych wyjątków powinna zostać ponowiona. Przejrzałem każdy wyjątek na MSDN i znalazłem krótką listę wyjątków, które można ponownie spróbować (oprócz TimeOutExceptionpowyższych). Daj mi znać, jeśli przegapiłem wyjątek, który należy ponowić.

  // 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 jest to trochę przyziemny kod do napisania. Obecnie wolę tę odpowiedź i nie widzę żadnych „hacków” w tym kodzie, które mogłyby powodować problemy w przyszłości.

goodguys_activate
źródło
1
Czy kod z próbki nadal powoduje problemy? Próbowałem uruchomić projekt UsingUsing (VS2013), ale linia z "Hope this code wasn't important, because it might not happen."jest nadal wykonywana ...
janv8000,
14

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

To niestandardowe narzędzie rozszerza WCFProxyGenerator o serwer proxy obsługi wyjątków. Generuje dodatkowy serwer proxy o nazwie ExceptionHandlingProxy<T>dziedziczy ExceptionHandlingProxyBase<T>- ten ostatni implementuje funkcjonalność serwera proxy. W rezultacie możesz wybrać domyślny serwer proxy, który dziedziczy ClientBase<T>lub ExceptionHandlingProxy<T>zarządza czasem życia fabryki kanałów i kanału. ExceptionHandlingProxy uwzględnia Twoje wybory w oknie dialogowym Dodaj odniesienie do usługi w odniesieniu do metod asynchronicznych i typów kolekcji.

Codeplex ma projekt o nazwie Exception Handling WCF Proxy Generator . Zasadniczo instaluje nowe niestandardowe narzędzie w programie Visual Studio 2008, a następnie używa tego narzędzia do generowania nowego serwera proxy usługi (Dodaj odniesienie do usługi) . Ma fajną funkcjonalność do radzenia sobie z uszkodzonymi kanałami, przekroczeniami czasu i bezpieczną utylizacją. Jest tutaj doskonały film wideo o nazwie ExceptionHandlingProxyWrapper, wyjaśniający dokładnie, jak to działa.

Możesz bezpiecznie użyć Usinginstrukcji ponownie, a jeśli kanał zostanie uszkodzony w przypadku dowolnego żądania (TimeoutException lub CommunicationException), Wrapper ponownie zainicjuje uszkodzony kanał i ponowi kwerendę. Jeśli to się nie powiedzie, wywoła Abort()polecenie i usunie proxy i ponownie zwróci wyjątek. Jeśli usługa wyrzuci aFaultException kod, przestanie działać, a serwer proxy zostanie bezpiecznie przerwany, zgłaszając poprawny wyjątek zgodnie z oczekiwaniami.

Neil
źródło
@Shimmy Status Beta. Data: 11 lipca 2009 r. Autor: Michele Bustamante . Dead Project?
Kiquenet,
11

Na podstawie odpowiedzi Marc Gravell, MichaelGG i Matt Davis nasi programiści wymyślili:

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 zastosowania:

string result = string.Empty;

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

Jest tak zbliżony do składni „using”, jak to możliwe, nie musisz zwracać wartości fikcyjnej 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 także użyć tego z ClientBase<T>potomkami zamiast ChannelFactory, jeśli chcesz.

Metoda rozszerzenia jest ujawniana, jeśli programista chce zamiast tego ręcznie pozbyć się proxy / kanału.

TrueWill
źródło
Czy korzystanie z tego ma sens, jeśli korzystam z PoolingDuplex i nie zamykam połączenia po rozmowie, aby moja usługa klienta mogła żyć nawet kilka dni i obsługiwać połączenia zwrotne z serwerem. O ile rozumiem rozwiązanie omówione tutaj ma sens dla jednego połączenia na sesję?
sll
@sll - służy do zamykania połączenia natychmiast po powrocie połączenia (jedno połączenie na sesję).
TrueWill
@cacho Własność DisposeSafelyjest z pewnością opcją i pozwoli uniknąć zamieszania. Mogą istnieć przypadki użycia, w których ktoś chciałby zadzwonić bezpośrednio, ale nie mogę wymyślić jednego od ręki.
TrueWill
@truewill tylko do dokumentacji, należy również wspomnieć, że ta metoda jest bezpieczna dla wątków, prawda?
Cacho Santa
1
Moim zdaniem najbardziej poprawnym rozwiązaniem byłoby: 1) Wykonanie wzorca Zamknij / Przerwij bez warunków wyścigu 2) Poradzić sobie z sytuacją, w której operacja serwisowa zgłasza wyjątki 3) Poradzić sobie z sytuacjami, w których metody Zamknij i Przerwij zgłaszają wyjątki 4) Poradzić sobie asynchroniczne wyjątki, takie jak ThreadAbortException https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/
Kiquenet,
8

@Marc Gravell

Czy nie byłoby dobrze użyć tego:

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 przypadkuService<IOrderService>.Use

Ułatwi to zwracanie zmiennych.

trójkątny
źródło
2
+1 @MarcGravell Myślę, że twoja odpowiedź „mogłaby zrobić lepiej”: P (i działanie, które można zaimplementować w postaci Func z zerowym zwrotem). Cała ta strona to bałagan - sformułowałem zunifikowany i skomentowałem duplikaty, gdybym przewidywał użycie WCF w dowolnym momencie tej dekady ...
Ruben Bartelink
7

Co to jest?

To jest wersja CW zaakceptowanej odpowiedzi, ale z (co uważam za kompletne) dołączoną obsługą wyjątków.

Przyjęta odpowiedź odnosi się do tej witryny, której już nie ma . Aby zaoszczędzić ci kłopotów, zamieszczam tutaj najbardziej odpowiednie części. Ponadto zmodyfikowałem go nieco, aby uwzględnić obsługę ponownych prób wyjątków w celu obsługi tych nieznośnych limitów czasu w sieci.

Proste użycie klienta WCF

Po wygenerowaniu serwera proxy po stronie klienta wystarczy go zaimplementować.

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

ServiceDelegate.cs

Dodaj ten plik do swojego rozwiązania. Ten plik nie wymaga żadnych zmian, chyba że chcesz zmienić liczbę ponownych prób lub jakie wyjątki 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: Stworzyłem ten post jako wiki społeczności. Nie będę zbierać „punktów” z tej odpowiedzi, ale wolę głosować za nią, jeśli zgadzasz się z implementacją, lub edytować ją, aby była lepsza.

LamonteCristo
źródło
Nie jestem pewien, czy zgadzam się z twoją charakterystyką tej odpowiedzi. Jest to wersja CW ze swojej idei obsługi wyjątków dodanej.
John Saunders,
@JohnSaunders - True (moja koncepcja obsługi wyjątków). Poinformuj mnie o wyjątkach, których mi brakuje lub które są niewłaściwe w obsłudze.
goodguys_activate
A co ze zmienną sukcesu? Musi dodać do kodu źródłowego: jeśli (sukces) zwraca; ??
Kiquenet,
Jeśli pierwsze połączenie zostanie odrzucone, a drugie odniesie sukces, MostRecentEx nie będzie miał wartości zerowej, więc zgłaszasz wyjątek, który i tak nie powiódł się 5 prób. czy coś mi brakuje? Nie widzę miejsca, w którym wyczyścisz najwięcejRecentEx, jeśli przy 2., 3., 4. lub 5. próbie się powiedzie. Nie widzę też powodzenia. Powinienem tu coś przegapić, ale ten kod nie będzie działał zawsze 5 razy, jeśli nie zostanie zgłoszony żaden wyjątek?
Bart Calixto
@Bart - Dodałem success == falsedo ostatecznej instrukcji if
goodguys_activate
7

Poniżej znajduje się ulepszona wersja źródła z pytania i rozszerzona o buforowanie fabryk wielu kanałów i próba wyszukania punktu końcowego w pliku konfiguracyjnym według nazwy kontraktu.

Wykorzystuje .NET 4 (w szczególności: 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.");
    }
}
Jesse C. Slicer
źródło
1
Dlaczego warto korzystać UseServiceDelegate<T>zamiast Action<T>?
Mike Mayer
1
Jedyny powód, dla którego mogę pomyśleć, że zrobił to autor, to mieć silnie typowanego delegata, o którym programista wiedziałby, że należy do wywołania usługi. Ale, o ile widzę, Action<T>działa równie dobrze.
Jesse C. Slicer
5

Takie opakowanie działałoby:

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żliwić Ci napisanie kodu takiego jak:

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

Opakowanie może oczywiście wychwycić więcej wyjątków, jeśli jest to wymagane, ale zasada pozostaje taka sama.

Tomas Jansson
źródło
Pamiętam dyskusję dotyczącą tego, czy Dispose nie jest wywoływany w określonych warunkach ... co powoduje wyciek pamięci z WCF.
goodguys_activate
Nie jestem pewien, czy to spowodowało wycieki pamięci, ale problem jest taki. Gdy wywołujesz Disposekanał IChannel, może on zgłosić wyjątek, jeśli kanał jest w stanie błędu, jest to problem, ponieważ Microsoft określa, że Disposenigdy nie powinien generować. Tak więc powyższy kod zajmuje się sprawą, gdy Closezgłasza wyjątek. Jeśli Abortrzuca, może to być coś poważnie złego. W grudniu napisałem o tym post na blogu: blog.tomasjansson.com/2010/12/disposible-wcf-client-wrapper
Tomas Jansson
4

Użyłem dynamicznego proxy Castle do rozwiązania problemu Dispose (), a także wdrożyłem automatyczne odświeżanie kanału, gdy jest on w stanie bezużytecznym. Aby z tego skorzystać, musisz utworzyć nowy interfejs, który odziedziczy umowę o świadczenie usług i IDisposable. 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 wstrzykiwać usługi WCF bez potrzeby martwienia się o szczegóły WCF przez konsumentów. I nie ma dodanego cruftu, jak inne rozwiązania.

Spójrz na kod, w rzeczywistości jest dość prosty: WCF Dynamic Proxy

Jay Douglass
źródło
4

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;
    }
}
Johan Nyman
źródło
4

Jeśli nie potrzebujesz IoC lub korzystasz z automatycznie generowanego klienta (Service Reference), możesz po prostu użyć otoki do zarządzania zamykaniem i pozwolić GC przejąć bazę klientów, gdy jest ona w stanie bezpiecznym, który nie spowoduje wyjątku. GC wywoła Dispose in service service, a to zadzwoni Close. Ponieważ jest już zamknięty, nie może powodować ż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;
        }
    }
}

Następnie, kiedy uzyskujesz dostęp do serwera, tworzysz klienta i używasz usingw autodisconect:

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

    Ws.Open();

    Ws.Test();
}
Luiz Felipe
źródło
3

Podsumowanie

Korzystając z technik opisanych w tej odpowiedzi, można korzystać z usługi 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 uzyskać bardziej zwięzły model programowania specyficzny dla twojej sytuacji - ale chodzi o to, że możemy stworzyć implementację IMyServicereprentowania kanału, która poprawnie implementuje wzorzec jednorazowy.


Detale

Wszystkie dotychczasowe odpowiedzi dotyczą problemu obejścia „błędu” w implementacji kanału WCF IDisposable. Odpowiedź, która wydaje się oferować najbardziej zwięzły model programistyczny (pozwalający na użycie usingbloku do zarządzania niezarządzanymi zasobami), brzmi następująco - gdzie proxy jest modyfikowane do implementacji IDisposablez implementacją bezbłędną . Problemem w tym podejściu jest łatwość konserwacji - musimy ponownie wdrożyć tę funkcjonalność dla każdego serwera proxy, którego używamy. W odmianie tej odpowiedzi zobaczymy, w jaki sposób możemy używać kompozycji zamiast dziedziczenia, aby uczynić tę technikę ogólną.

Pierwsze podejscie

Wydaje się, że istnieją różne implementacje do IDisposableimplementacji, ale dla argumentu użyjemy adaptacji tej, która jest stosowana w obecnie akceptowanej odpowiedzi .

[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 pisać

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 naszych usług za pomocą usingbloku:

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

Czyniąc to ogólnym

Wszystko, co do tej pory zrobiliśmy, to przeformułowanie rozwiązania Tomasa . Tym, co uniemożliwia generowanie tego kodu, jest fakt, że ProxyWrapperklasa musi zostać ponownie zaimplementowana dla każdej umowy serwisowej, jakiej chcemy. Przyjrzymy się teraz klasie, która pozwala nam dynamicznie tworzyć ten typ za pomocą 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();
    }
}

Dzięki naszej nowej klasie pomocników możemy teraz pisać

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

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

Pamiętaj, że możesz również użyć tej samej techniki (z niewielkimi modyfikacjami) w przypadku automatycznie generowanych klientów dziedziczących po ClientBase<>(zamiast używania ChannelFactory<>) lub jeśli chcesz użyć innej implementacji IDisposabledo zamknięcia kanału.

Lawrence
źródło
2

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

var client = new ProxyClient();
try
{
    ...
    client.Close();
}
finally
{
    if(client.State != CommunicationState.Closed)
        client.Abort();
}
Uriil
źródło
1

Napisałem prostą klasę podstawową, która to obsługuje. 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);
    }
}
Ufuk Hacıoğulları
źródło
Jakieś aktualizacje dla VS2013-.net 4.5.1? jakieś opcje Ponów, takie jak stackoverflow.com/a/9370880/206730 ? -
Kiquenet
@Kiquenet Nie pracuję już nad WCF. Jeśli wyślesz mi żądanie ściągnięcia, mogę je scalić i zaktualizować pakiet.
Ufuk Hacıoğulları
1
public static class Service<TChannel>
{
    public static ChannelFactory<TChannel> ChannelFactory = new ChannelFactory<TChannel>("*");

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

Pozwala to ładnie pisać zwroty:

return Service<IOrderService>.Use(orderService => 
{ 
    return orderService.PlaceOrder(request); 
}); 
Andriy Buday
źródło
1

Chciałbym dodać implementację usługi z odpowiedzi Marca Gravella na wypadek użycia 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();
            }
        }
    }
}
PSsam
źródło
1

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

Przyznaję, że jest to nie na temat oryginalnych tagów (C #), ale ponieważ nie mogłem znaleźć wersji tego doskonałego rozwiązania VB.NET, zakładam, że inni też będą szukać. Tłumaczenie Lambda może być nieco trudne, więc chciałbym komuś zaoszczędzić kłopotów.

Należy pamiętać, że ta konkretna implementacja zapewnia możliwość skonfigurowania ServiceEndpointśrodowiska wykonawczego.


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

Stosowanie:

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
InteXX
źródło
1

Nasza architektura systemu często używa frameworku Unity IoC do tworzenia instancji ClientBase, więc nie ma pewności, jak wymusić, aby inni programiści korzystali nawet z using{}bloków. Aby uczynić go tak głupim, jak to możliwe, stworzyłem tę niestandardową klasę, która rozszerza ClientBase i obsługuje zamykanie kanału podczas usuwania lub finalizacji, na wypadek, gdyby ktoś nie pozbył się wyraźnie instancji utworzonej przez Unity.

W konstruktorze trzeba też wykonać pewne czynności, aby skonfigurować kanał dla niestandardowych danych uwierzytelniających i innych rzeczy, więc tutaj też ...

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

Następnie klient może po prostu:

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

Dzwoniący może wykonać dowolną z tych czynności:

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);
        }
    }
}
CodingWithSpike
źródło
Nigdy nie używasz parametru dysponującego w metodzie Dispose
CaffGeek
@Chad - śledziłem wspólny wzorzec projektowy Finalize / Dispose Microsoftu: msdn.microsoft.com/en-us/library/b1yfkh5e%28VS.71%29.aspx Prawdą jest, że nie używam tej zmiennej, ponieważ nie nie trzeba wykonywać żadnych innych czynności porządkowych między normalnym usunięciem a finalizacją. Można go przepisać, aby po prostu wywołać funkcję Finalize Dispose () i przenieść kod z Dispose (bool) do Dispose ().
CodingWithSpike
Finalizatory dodają narzut i nie są deterministyczne. Unikam ich, gdy tylko jest to możliwe. Możesz użyć automatycznych fabryk Unity do wstrzykiwania delegatów i umieszczania ich w blokach lub (lepiej) ukryć zachowanie usługi tworzenia / wywoływania / usuwania za metodą na wstrzykiwanym interfejsie. Każde wywołanie zależności tworzy proxy, wywołuje je i usuwa.
TrueWill
0

Odniosłem się do kilku odpowiedzi w tym poście i dostosowałem go do moich potrzeb.

Chciałem mieć możliwość zrobienia czegoś z klientem WCF przed użyciem go, więc DoSomethingWithClient()metoda.

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));
POMOC
źródło
Co z konstruktorem klienta korzystającym z wiązania i zakończenia? TClient (wiązanie, zakończenie)
Kiquenet
0

Mam własne opakowanie dla kanału, który implementuje Dispose 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;
        }
}

Wydaje się, że działa to dobrze i pozwala na użycie bloku używającego.

Joe
źródło
0

Następujący pomocnik pozwala na wywoływanie voidi nieważne metody. Stosowanie:

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

Sama klasa 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;
        });
    }
}
Konstantin Spirin
źródło
0

Zastąp funkcję Dispose () klienta bez konieczności generowania klasy proxy na podstawie ClientBase, a także bez konieczności zarządzania tworzeniem kanałów i buforowaniem ! (Uwaga: WcfClient nie jest klasą ABSTRACT i jest oparty 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();
    }
}
Murad Duraidi
źródło
0

Moją metodą tego było stworzenie odziedziczonej klasy, która jawnie implementuje IDisposable. Jest to przydatne dla osób, które używają GUI, aby dodać odwołanie do usługi (Dodaj odwołanie do usługi). Po prostu upuszczam tę klasę w projekcie, odwołując się 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: To tylko prosta implementacja usuwania, możesz zaimplementować bardziej złożoną logikę usuwania.

Następnie możesz zastąpić wszystkie połączenia wykonane zwykłym klientem usługi bezpiecznymi klientami:

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ć usinginstrukcji zgodnie z oczekiwaniami, jednocześnie pozwalając, aby mój kod wyglądał mniej więcej tak samo.

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

Aleksandr Albert
źródło
-2

Możesz także użyć a, DynamicProxyaby rozszerzyć Dispose()metodę. W ten sposób możesz zrobić coś takiego:

using (var wrapperdProxy = new Proxy<yourProxy>())
{
   // Do whatever and dispose of Proxy<yourProxy> will be called and work properly.
}
Uri Abramson
źródło