Який найкращий спосіб вирішити проблему блоку клієнта WCF?


404

Мені подобається інстанціювати своїх клієнтів послуг WCF в межах usingблоку, оскільки це майже стандартний спосіб використання ресурсів, які реалізують IDisposable:

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

Але, як зазначається в цій статті MSDN , загортання клієнта WCF в usingблок може маскувати будь-які помилки, в результаті яких клієнт залишається в несправному стані (наприклад, час очікування або проблема зв'язку). Короткий опис короткого оповідання, коли викликається Dispose (), метод Close () клієнта спрацьовує, але видає помилку, оскільки він знаходиться в несправному стані. Оригінальний виняток потім маскується другим винятком. Не добре.

Пропоноване рішення у статті MSDN полягає у тому, щоб повністю уникати використання usingблоку та замість того, щоб створити інстанціювання своїх клієнтів та використовувати їх приблизно так:

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

У порівнянні з usingблоком, я думаю, що це некрасиво. І багато коду для написання кожного разу, коли вам потрібен клієнт.

На щастя, я знайшов кілька інших способів вирішення, таких як цей на IServiceOriented. Ви починаєте з:

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

Що потім дозволяє:

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

Це непогано, але я не думаю, що це так виразно і легко зрозуміло, як usingблок.

Вирішення, яке я зараз намагаюся використовувати, я вперше прочитав про blog.davidbarret.net . В основному ви перекриваєте Dispose()метод клієнта, де б ви його не використовували. Щось на зразок:

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

Здається, це зможе знову дозволити usingблок без небезпеки замаскувати виняток з несправним станом.

Отже, чи є які-небудь інші проблеми, з якими я повинен бути уважним для використання цих методів? Хтось придумав щось краще?


42
Останній (який оглядає це. Держава) - це раса; він може не бути помилковим, коли ви перевіряєте булеву, але може бути помилковим, коли ви телефонуєте Close ().
Брайан

15
Ви читаєте стан; це не винне. Перш ніж зателефонувати на Close (), канал виходить з ладу. Близько () кидає. Гра завершена.
Брайан

4
Час минає. Це може бути дуже короткий проміжок часу, але технічно в період часу між перевіркою стану каналу та проханням його закрити стан каналу може змінитися.
Ерік Кінг

8
Я б використовував Action<T>замість цього UseServiceDelegate<T>. неповнолітній.
hIpPy

2
Мені не подобається цей статичний помічник, Service<T>оскільки він ускладнює тестування одиниць (як це робить більшість статичних речей). Я вважаю за краще, щоб він був нестатичним, щоб його можна було ввести в клас, який ним використовується.
Фабіо Марреко

Відповіді:


137

Насправді, хоч я і веду блоги (див . Відповідь Луки ), я вважаю, що це краще, ніж моя обгортка, яку можна використовувати. Типовий код:

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

(редагувати за коментарями)

Оскільки Useповернення недійсне, найпростіший спосіб обробляти значення повернення - через захоплену змінну:

int newOrderId = 0; // need a value for definite assignment
Service<IOrderService>.Use(orderService=>
  {
    newOrderId = orderService.PlaceOrder(request);
  });
Console.WriteLine(newOrderId); // should be updated

2
@MarcGravell Де я можу ввести цього клієнта? Я припускаю, що ChannelFactory створює клієнта, а фабричний об’єкт з’являється в межах класу Service, а це означає, що код слід трохи відремонтувати, щоб дозволити користувальницьку фабрику. Це правильно, чи я тут пропускаю щось очевидне?
Антту

16
Ви можете легко змінити обгортку, щоб вам не потрібна змінна захоплення для результату. Щось подібне: public static TResult Use<TResult>(Func<T, TResult> codeBlock) { ... }
chris

3
Можливо, корисно https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/ і https://devzone.channeladam.com/articles/2014/09/how-to-easily-call-wcf-service-properly/ і http://dzimchuk.net/post/wcf-error-helpers
PreguntonCojoneroCabrón,

Як я можу додати облікові дані, використовуючи цей спосіб?
Гіппас

2
На мою думку, найбільш правильним рішенням було б: 1) Виконати схему Close / Abort без умови перегонів 2) Виправити ситуацію, коли операція служби кидає винятки 3) Виправити ситуації, коли і методи Close, і Abort кидають винятки 4) Handle асинхронні винятки, такі як ThreadAbortException https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/
Kiquenet

88

З огляду на вибір між рішенням, яке пропагує IServiceOriented.com, і рішенням, яке пропагує блог Девіда Баррета , я віддаю перевагу простоті, запропонованій методом Dispose () клієнта. Це дозволяє мені продовжувати використовувати оператор using (), як можна було б очікувати з одноразовим об'єктом. Однак, як зазначав @Brian, це рішення містить перегонну умову в тому, що Держава може не бути винною, коли вона перевіряється, але може бути до моменту виклику Close (), і в такому випадку все ще відбувається CommunicationException.

Отже, щоб обійти це, я застосував рішення, яке поєднує найкраще з обох світів.

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

2
Хіба не ризиковано використовувати оператор "Спробуйте-нарешті" (або синтаксичний цукор - "використання () {}") з некерованими ресурсами? Справа в точці, якщо параметр "Закрити" не вдається, виняток не вловлюється і, нарешті, може не запуститися. Крім того, якщо в остаточному заяві є виняток, це може замаскувати інші винятки. Я думаю, що саме тому Try-Catch віддається перевазі.
Zack Jannsen

Зак, не ясно на вашому об’єкті; що я пропускаю? Якщо метод Close викидає виняток, остаточний блок буде виконаний перед викидом винятку. Правильно?
Патрік Шалапський

1
@jmoreno, я скасував вашу редакцію. Якщо ви помітите, у методі взагалі немає блоку спіймання. Ідея полягає в тому, що будь-який виняток, що трапляється (навіть у підсумку), слід кидати, а не мовчки ловити.
Метт Девіс

5
@MattDavis Навіщо взагалі потрібен successпрапор? Чому ні try { Close(); } catch { Abort(); throw; }?
Костянтин Спірін

А як щодо спроби / лову Close(); success = true;? Я б не хотів, щоб виняток було викинуто, якби я міг успішно перервати його в остаточному блоці. Я б хотів, щоб виняток було викинуто лише в тому випадку, якщо Abort () не вдався до цього випадку. Таким чином, try / catch приховав би потенційний виняток з перегонів і все ж дозволить вам перервати з'єднання в остаточному блоці.
goku_da_master

32

Я написав функцію вищого порядку, щоб вона працювала правильно. Ми використовували це в кількох проектах, і, здається, це чудово працює. Ось так потрібно було робити все з самого початку, без парадигми "використання" тощо.

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

Ви можете телефонувати так:

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

Це майже так, як у вашому прикладі. У деяких проектах ми пишемо сильно набрані допоміжні методи, тому ми закінчуємо писати такі речі, як "Wcf.UseFooService (f => f ...)".

Я вважаю це досить елегантним, усі речі враховані. Чи є певна проблема, з якою ви стикалися?

Це дозволяє підключати інші чудові функції. Наприклад, на одному веб-сайті сайт ідентифікує службу від імені зареєстрованого користувача. (На сайті немає власних даних.) Написавши власний помічник методу "UseService", ми можемо налаштувати фабрику каналів так, як ми хочемо, і т. Д. Ми також не зобов'язані використовувати створені проксі-сервери - будь-який інтерфейс буде робити .


Я отримую виняток: Властивість Address на ChannelFactory.Endpoint була нульовою. Кінцева точка ChannelFactory повинна мати вказану дійсну адресу . Що таке GetCachedFactoryметод?
Маршалл

28

Це рекомендований Microsoft спосіб обробляти дзвінки клієнта WCF:

Детальніше дивіться: Очікувані винятки

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

Додаткова інформація Так багато людей, схоже, задають це питання на WCF, що Microsoft навіть створила спеціальний зразок, щоб продемонструвати, як поводитися з винятками:

c: \ WF_WCF_Samples \ WCF \ Basic \ Клієнт \ Очікувані Прийняття \ CS \ клієнт

Завантажте зразок: C # або VB

Враховуючи, що існує стільки проблем, пов’язаних із використанням оператора , (нагрітого?) Внутрішні дискусії та теми з цього питання, я не збираюся витрачати свій час на те, щоб стати кодовим кодом і знайти більш чистий шлях. Я просто висмоктую його та впроваджую клієнтів WCF цим багатослівним (ще надійним) способом для моїх серверних додатків.

Необов’язкові додаткові збої в лові

Багато винятків випливають із цього питання, CommunicationExceptionі я не думаю, що більшість цих винятків слід повторити. Я перебрався через кожен виняток на MSDN і знайшов короткий список винятків, які можна повторити (крім TimeOutExceptionвище). Повідомте мене, якщо я пропустив виняток, який слід повторити.

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

Потрібно сказати, що це небагато щоденного коду для написання. На даний момент я віддаю перевагу цій відповіді , і не бачу жодних "хакків" у цьому коді, які можуть спричинити проблеми в дорозі.


1
Чи все ще викликає проблеми з зразка? Я спробував запустити проект UseUsing (VS2013), але рядок з "Hope this code wasn't important, because it might not happen."досі виконується ...
janv8000

14

Нарешті я знайшов кілька твердих кроків до чистого вирішення цієї проблеми.

Цей спеціальний інструмент розширює WCFProxyGenerator, щоб забезпечити проксі-сервер, що обробляє винятки. Він генерує додатковий проксі, ExceptionHandlingProxy<T>який називається успадковується ExceptionHandlingProxyBase<T>- останній з яких реалізує м'ясо функціональності проксі. У результаті ви можете використовувати проксі-сервер за замовчуванням, який успадковує ClientBase<T>або ExceptionHandlingProxy<T>який інкапсулює управління періодом життя фабрики та каналу. ExceptionHandlingProxy поважає ваш вибір у діалоговому вікні Додати службу довідки стосовно асинхронних методів та типів колекції.

У Codeplex є проект під назвою Генератор проксі-файлів WCF Proxy Generator . В основному він встановлює новий спеціальний інструмент для Visual Studio 2008, потім використовує цей інструмент для створення нового проксі-сервісу (Додати службну довідку) . Він має приємну функціональність для вирішення несправних каналів, тайм-аутів та безпечного утилізації. Тут є чудове відео під назвою ExceptionHandlingProxyWrapper, що пояснює, як саме це працює.

Ви можете спокійно використовувати Usingоператор ще раз, і якщо канал вийшов з ладу за будь-яким запитом (TimeoutException або CommunicationException), Обгортка повторно ініціалізує несправний канал і повторно поставить запит. Якщо це не вдасться, він викличе Abort()команду і розпоряджається проксі-сервером і скидає виняток. Якщо служба викине FaultExceptionкод, вона припинить виконання, і проксі-сервер буде скасовано безпечно, викинувши правильний виняток, як очікувалося.


@Shimmy Status Beta. Дата: 11 липня 2009 р. Мішель Бустаманте . Мертвий проект?
Кікенет

11

На основі відповідей Марка Гравелла, МайклаGG та Метта Девіса наші розробники придумали наступне:

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

Приклад використання:

string result = string.Empty;

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

Він максимально наближений до синтаксису "використання", вам не потрібно повертати фіктивне значення під час виклику недійсного методу, і ви можете робити кілька дзвінків до служби (і повертати кілька значень) без використання кортежів.

Крім того, ви можете використовувати це з ClientBase<T>нащадками замість ChannelFactory за бажанням.

Спосіб розширення піддається, якщо розробник хоче замість нього вручну розпоряджатися проксі / каналом.


Використовувати це має сенс, якщо я використовую PoolingDuplex і не перериваю зв’язок після дзвінка, щоб служба мого клієнта могла жити навіть кілька днів і обробляти зворотні виклики сервера. Наскільки я розумію рішення, яке тут обговорюється, має сенс для одного дзвінка за сеанс?
sll

@sll - це для закриття з'єднання відразу після повернення дзвінка (один дзвінок за сеанс).
TrueWill

@cacho Зробити DisposeSafelyприватним - це, безумовно, варіант, і уникнути плутанини. Можливо, є випадки використання, коли хтось хотів би зателефонувати йому безпосередньо, але я не можу придумати їх одночасно.
TrueWill

@truewill тільки для документації, також важливо згадати, що цей метод є безпечним для потоків, правда?
Cacho Santa

1
На мій погляд, найбільш правильним рішенням було б: 1) Виконати схему Close / Abort без перегонів 2) Виправити ситуацію, коли операція служби кидає винятки; асинхронні винятки, такі як ThreadAbortException https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/
Kiquenet

8

@Marc Gravell

Чи не було б нормально використовувати це:

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

Або те ж саме (Func<T, TResult>)у випадкуService<IOrderService>.Use

Це полегшить повернення змінних.


2
+1 @MarcGravell Я думаю, що і ваша відповідь "могла б зробити краще": P (а дія може бути реалізована з точки зору функцій з нульовим поверненням). Вся ця сторінка - це безлад - я б сформулював уніфікований і прокоментував би дуппи, якби я передбачив використовувати WCF будь-який час цього десятиліття ...
Рубен Бартелінк

7

Що це?

Це CW версія прийнятої відповіді, але з (що я вважаю завершеним) обробкою винятків включено.

Прийнята відповідь посилається на цей веб-сайт, якого вже немає . Щоб позбавити вас від неприємностей, я включаю сюди найбільш відповідні частини. Крім того, я трохи змінив його, щоб включити обробку для повторних винятків, щоб обробити ці примхливі мережеві очікування.

Просте користування WCF клієнтом

Щойно ви генеруєте клієнтський проксі, це все, що вам потрібно для його реалізації.

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

ServiceDelegate.cs

Додайте цей файл до свого рішення. Жодних змін у цьому файлі не потрібно, якщо ви не хочете змінити кількість повторень чи які винятки ви хочете обробити.

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: Я зробив цю публікацію вікі спільноти. Я не збиратиму "бали" з цієї відповіді, але віддаю перевагу, якщо ви погоджуєтесь із впровадженням або редагуєте її, щоб покращити її.


Я не впевнений, що згоден з вашою характеристикою цієї відповіді. Це версія CW з додаванням вашої ідеї обробки винятків .
Джон Сондерс

@JohnSaunders - Правда (моя концепція обробки винятків). Повідомте мене про будь-які винятки, яких я пропускаю або неправильно поводжу.
goodguys_activate

Що стосується змінної успіху? Його потрібно додати до вихідного коду: якщо (успіх) повернення; ??
Кікенет

Якщо перший виклик кидає і 2-й успіх mostRecentEx не буде нульовим, тому ви кидаєте виняток, який не вдалося здійснити 5 повторних спроб. чи я щось пропускаю? Я не бачу, де ви очистите найбільшRecentEx, якщо в 2-й, 3-й, 4-й чи 5-й спроби вдалося. Також не бачите, щоб повернення не вдалося. Мені тут щось не вистачає, але цей код не працюватиме завжди 5 разів, якщо не буде викинуто жодного винятку?
Барт Калікто

@Bart - Я додав success == falseу фінал, якщо заява
goodguys_activate

7

Нижче наведена вдосконалена версія джерела з питань, розширена для кешування кількох фабрик каналів та спроби пошуку кінцевої точки у файлі конфігурації за назвою контракту.

Він використовує .NET 4 (конкретно: протиріччя, LINQ, var):

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1
Навіщо використовувати UseServiceDelegate<T>замість Action<T>?
Майк Майер

1
Єдиною причиною, на яку я можу подумати, що оригінальний автор це зробив, це мати сильно набраний делегат, який розробник знає, що належить викликати службу. Але, наскільки я бачу, Action<T>працює так само добре.
Jesse C. Slicer

5

Така оболонка буде працювати:

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

Це має дозволяти вам писати код на зразок:

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

Звичайно, обгортка може набрати більше винятків, якщо цього потрібно, але принцип залишається тим самим.


Я пам’ятаю дискусію з приводу того, що утилізацію не викликають за певних умов ... внаслідок чого відбувається витік пам'яті під WC / WCF.
goodguys_activate

Я не впевнений, що це призвело до витоку пам'яті, але проблема полягає в цьому. Коли ви звертаєтесь Disposeдо IChannel, він може викинути виняток, якщо канал знаходиться у несправному стані, це проблема, оскільки Microsoft вказує, що Disposeніколи не повинен викидати. Отже, те, що наведений вище код - це обробка справи, коли Closeвикидає виняток. Якщо Abortкидки, це може бути щось серйозно не так. Я написав про це в блозі минулого грудня: blog.tomasjansson.com/2010/12/disposible-wcf-client-wrapper
Томаш Янссон,

4

Я використовував динамічний проксі Castle, щоб вирішити проблему Dispose (), а також здійснив автоматичне оновлення каналу, коли він знаходиться у непридатному стані. Для цього ви повинні створити новий інтерфейс, який успадковує ваш контракт на послугу та IDisposable. Динамічний проксі реалізує цей інтерфейс і обертає канал WCF:

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

Мені це подобається, оскільки ви можете надавати послуги WCF, не споживаючи потреби в турботах про будь-які деталі WCF. І немає доданого шару, як у інших рішень.

Подивіться на код, він насправді досить простий: WCF Dynamic Proxy


4

Використовуйте метод розширення:

public static class CommunicationObjectExtensions
{
    public static TResult MakeSafeServiceCall<TResult, TService>(this TService client, Func<TService, TResult> method) where TService : ICommunicationObject
    {
        TResult result;

        try
        {
            result = method(client);
        }
        finally
        {
            try
            {
                client.Close();
            }
            catch (CommunicationException)
            {
                client.Abort(); // Don't care about these exceptions. The call has completed anyway.
            }
            catch (TimeoutException)
            {
                client.Abort(); // Don't care about these exceptions. The call has completed anyway.
            }
            catch (Exception)
            {
                client.Abort();
                throw;
            }
        }

        return result;
    }
}

4

Якщо вам не потрібен IoC або ви використовуєте автогенерований клієнт (довідник служби), ви можете просто скористатися обгорткою для управління закриттям і дозволити GC взяти клієнтську базу, коли вона знаходиться в безпечному стані, що не викидає жодного винятку. GC зателефонує Dispose in serviceclient, і він зателефонує Close. Оскільки він закритий, він не може завдати шкоди. Я використовую це без проблем у виробничому коді.

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

Потім, коли ви звертаєтесь до сервера, ви створюєте клієнт і використовуєте usingв автовідключенні:

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

    Ws.Open();

    Ws.Test();
}

3

Підсумок

Використовуючи методи, описані у цій відповіді, можна споживати послугу WCF у використанні блоку із наступним синтаксисом:

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

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

Звичайно, ви можете ще більше адаптувати це, щоб досягти більш стислої моделі програмування, характерної для вашої ситуації - але справа в тому, що ми можемо створити реалізацію IMyServiceканалу, який правильно реалізує одноразову схему.


Деталі

Усі відповіді, що надані до цього часу, стосуються проблеми подолання "помилки" в реалізації каналу WCF IDisposable. Відповідь, яка, здається, пропонує найбільш стисну модель програмування (що дозволяє використовувати usingблок для розпорядження некерованими ресурсами), це ця - де проксі модифікований для реалізації IDisposableз помилковою реалізацією. Проблема такого підходу полягає в ремонтопридатності - нам доведеться знову реалізувати цю функціональність для будь-якого проксі, який ми використовуємо. На варіації цієї відповіді ми побачимо, як ми можемо використовувати композицію, а не успадкування, щоб зробити цю техніку загальною.

Перша спроба

Здійснюється реалізація різних IDisposableваріантів реалізації, але заради аргументу ми використаємо адаптацію тієї, яку використовує прийнятий на даний момент відповідь .

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

Озброївшись вищевказаними класами, ми можемо зараз писати

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

Це дозволяє нам споживати нашу послугу за допомогою usingблоку:

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

Зробити це загальним

Все, що ми зробили поки, - це переформулювати рішення Томаса . Що заважає цьому коду бути загальним, це той факт, що ProxyWrapperклас повинен бути повторно реалізований для кожного контракту на обслуговування, який ми хочемо. Зараз ми розглянемо клас, який дозволяє нам динамічно створювати цей тип за допомогою 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();
    }
}

З нашим новим класом помічників ми можемо зараз писати

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

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

Зауважте, що ви також можете використовувати ту саму техніку (з незначними модифікаціями) для автоматично сформованих клієнтів, що передають у спадок ClientBase<>(замість того, щоб використовувати ChannelFactory<>), або якщо ви хочете використовувати іншу реалізацію IDisposableдля закриття свого каналу.


2

Мені подобається такий спосіб закриття з'єднання:

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

1

Я написав простий базовий клас, який справляється з цим. Він доступний у вигляді пакету NuGet і досить простий у використанні.

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

Будь-які оновлення для VS2013-.net 4.5.1? будь-які варіанти для повтору, як-от stackoverflow.com/a/9370880/206730 ? -
Кікенет

@Kiquenet Я більше не працюю над WCF. Якщо ви надішлите мені запит на отримання, я можу його об'єднати та оновити пакет.
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();
            }
        }
    }
}

Так це дозволяє красиво писати зворотні заяви:

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

1

Я хотів би додати реалізацію Сервісу з відповіді Марка Гравелла для випадку використання ServiceClient замість 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

Для тих, хто цікавиться, ось переклад VB.NET прийнятої відповіді (нижче). Я трохи вдосконалив це для стислості, поєднуючи деякі поради інших у цій темі.

Я визнаю, що це поза темою для початкових тегів (C #), але оскільки мені не вдалося знайти версію VB.NET цього прекрасного рішення, я припускаю, що і інші будуть шукати. Переклад Ламбда може бути дещо складним, тому я хотів би врятувати комусь неприємності.

Зауважте, що саме ця реалізація забезпечує можливість налаштування ServiceEndpointчасу виконання.


Код:

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

Використання:

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

Наша архітектура системи часто використовує рамку Unity IoC для створення примірників ClientBase, тому немає впевненого способу примусити інших розробників навіть використовувати using{}блоки. Для того, щоб зробити це максимально нерозумним, я створив цей спеціальний клас, який розширює ClientBase, і займається закриттям каналу в розпорядженні або завершенням у випадку, якщо хтось прямо не розпоряджається створеним Unity екземпляром.

Є також речі, які потрібно було зробити в конструкторі, щоб налаштувати канал для користувацьких облікових даних та інших матеріалів, так що це теж тут ...

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

Тоді клієнт може просто:

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

І абонент може зробити будь-що з цього:

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

Ви ніколи не використовуєте параметр, який
розміщується

@Chad - Я дотримувався загальної схеми дизайну Finalize / Dispose Microsoft: msdn.microsoft.com/en-us/library/b1yfkh5e%28VS.71%29.aspx Це правда, що я не використовую цю змінну, тому що я не Вам не потрібно робити різного очищення між нормальним розпорядженням та фіналізацією. Можна переписати, щоб просто завершити виклик Dispose () та перемістити код з Dispose (bool) у Dispose ().
CodingWithSpike

Фіналізатори додають накладні витрати і не є детермінованими. Я уникаю їх, коли це можливо. Ви можете використовувати автоматичні фабрики Unity для введення делегатів і введення їх у блоки, або (краще) приховати поведінку служби створення / виклику / розпорядження за методом на вбудованому інтерфейсі. Кожен дзвінок до залежності створює проксі, називає його та розпоряджається ним.
TrueWill

0

Я нагадав кілька відповідей у ​​цій публікації та налаштував її відповідно до моїх потреб.

Мені хотілося можливість зробити щось із клієнтом WCF, перш ніж використовувати його так, як 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;
    }
}

Ось клас помічників:

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

І я можу використовувати його як:

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

Що ж стосується конструктора клієнтів, що використовує прив'язку та кінцеву позицію? TClient (зв'язування, закінчення)
Кіквенет

0

У мене є власна обгортка для каналу, який реалізує Dispose так:

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

Це, здається, працює добре і дозволяє використовувати блок використання.


0

Наведений нижче помічник дозволяє викликати voidі недійсні методи. Використання:

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

Сам клас:

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

Замініть клієнтське розпорядження () Dispose () без необхідності генерування проксі-класу на основі ClientBase, також без необхідності керування створенням каналів та кешування ! (Зауважте, що WcfClient не є класом ABSTRACT і базується на 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

Мій метод цього був у створенні спадкового класу, який явно реалізує IDisposable. Це корисно для людей, які використовують gui, щоб додати посилання на службу (Додати службну довідку). Я просто опускаю цей клас у проект, роблячи посилання на службу, і використовую його замість клієнта за замовчуванням:

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

Примітка: Це просто проста реалізація розпорядження, ви можете реалізувати більш складну логіку розпорядження, якщо вам подобається.

Потім ви можете замінити всі ваші дзвінки, здійснені звичайним клієнтом служби, безпечними клієнтами, наприклад:

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

Мені подобається це рішення, оскільки воно не вимагає від мене доступу до визначень інтерфейсу, і я можу використовувати using оператор так, як я б очікував, дозволяючи моєму коду виглядати більш-менш однаково.

Вам все одно доведеться обробляти винятки, які можуть бути викладені, як зазначено в інших коментарях у цій темі.


-2

Ви також можете використовувати a DynamicProxyдля розширення Dispose()методу. Таким чином ви можете зробити щось на кшталт:

using (var wrapperdProxy = new Proxy<yourProxy>())
{
   // Do whatever and dispose of Proxy<yourProxy> will be called and work properly.
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.