Как лучше всего обойти проблему с клиентом 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

На самом деле, хотя я веду блог (см . Ответ Люка ), я думаю, что это лучше, чем моя IDisposable оболочка. Типичный код:

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

(редактировать по комментариям)

Так как Useвозвращает void, самый простой способ обработки возвращаемых значений - через захваченную переменную:

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) { ... }
Крис
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) Обработать асинхронные исключения, такие как 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-Наконец» (или синтаксический сахар - «using () {}») с неуправляемыми ресурсами? Например, если опция «Закрыть» не срабатывает, исключение не перехватывается и, наконец, может не работать. Кроме того, если в операторе finally есть исключение, оно может маскировать другие исключения. Я думаю, именно поэтому Try-Catch предпочтительнее.
Зак Яннсен
Зак, не ясно на вашем объекте; что мне не хватает? Если метод Close генерирует исключение, блок finally будет выполняться до того, как будет сгенерировано исключение. Правильно?
Патрик Салапски
1
@jmoreno, я отменил твою правку. Если вы заметите, в методе нет блока catch. Идея состоит в том, что любое исключение, которое происходит (даже в конечном итоге), должно быть выброшено, а не перехвачено.
Мэтт Дэвис
5
@MattDavis Зачем тебе вообще successфлаг? Почему нет try { Close(); } catch { Abort(); throw; }?
Константин Спирин
Как насчет того, чтобы сделать попытку Close(); success = true;? Я бы не хотел, чтобы было выброшено исключение, если бы я мог успешно прервать его в блоке finally. Я бы хотел, чтобы исключение было выдано только в случае сбоя Abort () в этом случае. Таким образом, try / catch скрыл бы потенциальное исключение состояния гонки и все же позволил бы вам прервать () соединение в блоке finally.
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», мы можем настроить фабрику каналов так, как нам хочется, и т. Д. Мы также не обязаны использовать сгенерированные прокси - любой интерфейс подойдет ,

MichaelGG
источник
Я получаю исключение: свойство 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 \ Client \ ExpectedExceptions \ 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
}

По общему признанию, это - немного мирского кода, чтобы написать. В настоящее время я предпочитаю этот ответ , и не вижу в этом коде «хаков», которые могут вызвать проблемы в будущем.

goodguys_activate
источник
1
Код из примера все еще вызывает проблемы? Я попытался запустить проект UsingUsing (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 . Он в основном устанавливает новый пользовательский инструмент в Visual Studio 2008, а затем использует этот инструмент для создания нового прокси службы (Добавить ссылку на службу) . Он имеет некоторые хорошие функции для работы с неисправными каналами, тайм-аутами и безопасным удалением. Здесь есть отличное видео под названием ExceptionHandlingProxyWrapper, объясняющее, как именно это работает.

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

Нил
источник
@Shimmy Status Beta. Дата: суббота, 11 июля 2009 года, Мишель Бустаманте . Мертвый проект?
Kiquenet
11

Основываясь на ответах Марка Гравелла, MichaelGG и Мэтта Дэвиса, наши разработчики придумали следующее:

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

Он максимально приближен к синтаксису «using», вам не нужно возвращать фиктивное значение при вызове метода void, и вы можете сделать несколько вызовов службы (и вернуть несколько значений), не используя кортежи.

Кроме того, вы можете использовать это с ClientBase<T>потомками вместо ChannelFactory при желании.

Метод расширения предоставляется, если разработчик хочет вместо этого вручную удалить прокси / канал.

TrueWill
источник
Использование этого имеет смысл, если я использую PoolingDuplex и не закрываю соединение после вызова, чтобы моя клиентская служба могла жить даже несколько дней и обрабатывать обратные вызовы сервера. Насколько я понимаю, решение, которое обсуждается здесь, имеет смысл для одного вызова за сеанс?
SLL
@sll - это для закрытия соединения сразу после возврата вызова (один вызов за сеанс).
TrueWill
@cacho Сделать DisposeSafelyприватным, безусловно, вариант, и это поможет избежать путаницы. Могут быть случаи использования, когда кто-то захочет позвонить напрямую, но я не могу придумать один случайный прием.
TrueWill
@truewill только для документации, также важно упомянуть, что этот метод является потокобезопасным, верно?
Cacho Santa
1
На мой взгляд, наиболее правильным решением было бы: 1) Выполнить шаблон Close / Abort без условия гонки 2) Обработать ситуацию, когда сервисная операция выдает исключения 3) Обработать ситуации, когда оба метода Close и Abort генерируют исключения 4) Обработать асинхронные исключения, такие как 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

Это сделает возврат переменных проще.

pangular
источник
2
+1 @MarcGravell Я думаю, что ваш ответ «мог бы быть лучше»: P (и действие можно реализовать в виде Func с нулевым возвратом). Вся эта страница - беспорядок - я бы сформулировал единую и прокомментировал бы ошибки, если бы я планировал использовать 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: я сделал это сообщение вики сообщества. Я не буду набирать «баллы» из этого ответа, но предпочитаю, чтобы вы проголосовали за него, если вы согласны с реализацией, или измените его, чтобы сделать его лучше.

LamonteCristo
источник
Я не уверен, что согласен с вашей характеристикой этого ответа. Это версия CW с добавленной вами идеей обработки исключений .
Джон Сондерс
@JohnSaunders - True (моя концепция обработки исключений). Дайте мне знать о любых исключениях, которые я пропускаю или неправильно обращаюсь.
goodguys_activate
Как насчет переменной успеха? Необходимо добавить к исходному коду: if (success) return; ??
Kiquenet
Если первый вызов сбрасывается, а второй завершается успешно, MostRecentEx не будет иметь значение null, поэтому вы в любом случае генерируете исключение, которое не прошло 5 попыток. или я что-то упустил? Я не вижу, где вы очищаете большинство RecentEx, если на 2-й, 3-й, 4-й или 5-й попытке это удалось. Также не вижу возврата к успеху. Я должен что-то здесь упустить, но этот код не будет работать всегда 5 раз, если не выдается исключение?
Барт Каликсто
@Bart - я добавил success == falseв окончательный оператор if
goodguys_activate
7

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

Он использует .NET 4 (в частности: 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.");
    }
}
Джесси С. Слайсер
источник
1
Почему использовать UseServiceDelegate<T>вместо Action<T>?
Майк Майер
1
Единственная причина, по которой я могу думать, что первоначальный автор сделал это, заключалась в том, чтобы иметь строго типизированный делегат, который, как знал бы разработчик, относится к вызову службы. Но, насколько я вижу, Action<T>работает так же хорошо.
Джесси С. Слайсер
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.

Оболочка, конечно, может поймать больше исключений, если это требуется, но принцип остается тем же.

Томас Янссон
источник
Я помню дискуссию о том, что Dispose не вызывается при определенных условиях ... что приводит к утечке памяти с WCF.
goodguys_activate
Я не уверен, что это привело к утечке памяти, но проблема в этом. Когда вы вызываете DisposeIChannel, он может выдать исключение, если канал находится в неисправном состоянии, это проблема, так как 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

Джей Дуглас
источник
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 или вы используете автоматически сгенерированный клиент (Service Reference), тогда вы можете просто использовать оболочку для управления закрытием и позволить GC принять клиентскую базу, когда она находится в безопасном состоянии, которое не вызовет никаких исключений. GC вызовет Dispose в 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в autodisconect:

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

    Ws.Open();

    Ws.Test();
}
Луис Фелипе
источник
3

Резюме

Используя методы, описанные в этом ответе, можно использовать службу WCF в блоке using со следующим синтаксисом:

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);
    }
}
Ufuk Hacıoğulları
источник
Какие-либо обновления для VS2013-.net 4.5.1? какие-либо варианты для Retry, как stackoverflow.com/a/9370880/206730 ? -
Kiquenet
@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

Я хотел бы добавить реализацию Service из ответа Марка Гравелла для случая использования 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();
            }
        }
    }
}
PSsam
источник
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
InteXX
источник
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);
        }
    }
}
CodingWithSpike
источник
Вы никогда не используете удаление параметров в вашем методе
Dispose
@Chad - я следовал общему шаблону Microsoft « Завершение / удаление» : msdn.microsoft.com/en-us/library/b1yfkh5e%28VS.71%29.aspx Правда, я не использую переменную, потому что я не Не нужно делать какую-либо другую очистку между обычным расположением и финализацией. Можно было бы переписать, чтобы просто вызвать Finalize для вызова Dispose () и переместить код из Dispose (bool) в Dispose ().
CodingWithSpike
Финализаторы добавляют накладные расходы и не являются детерминированными. Я избегаю их, когда это возможно. Вы можете использовать автоматические фабрики Unity для внедрения делегатов и помещения их в использующие блоки, или (лучше) скрыть поведение службы create / call / dispose позади метода на внедренном интерфейсе. Каждый вызов зависимости создает прокси, вызывает его и избавляется от него.
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 (связывание, окончание)
Kiquenet
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. Это полезно для людей, которые используют графический интерфейс для добавления ссылки на сервис (Add Service Reference). Я просто добавляю этот класс в проект, создавая ссылку на службу, и использую ее вместо клиента по умолчанию:

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

Примечание: это просто простая реализация dispose, вы можете реализовать более сложную логику dispose, если хотите.

Затем вы можете заменить все ваши звонки, сделанные обычным сервисным клиентом, на безопасные клиенты, например:

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

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

Вам все еще нужно будет обработать исключения, которые могут быть выброшены, как указано в других комментариях в этой теме.

Александр Альберт
источник
-2

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

using (var wrapperdProxy = new Proxy<yourProxy>())
{
   // Do whatever and dispose of Proxy<yourProxy> will be called and work properly.
}
Ури Абрамсон
источник