Каков наилучший обходной путь для проблемы использования блока клиентом WCF?

StackOverflow https://stackoverflow.com/questions/573872

Вопрос

Мне нравится создавать экземпляры клиентов службы 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 заблокируйте снова, не опасаясь замаскировать исключение сбойного состояния.

Итак, есть ли еще какие-нибудь ошибки, на которые мне следует обратить внимание при использовании этих обходных путей?Кто-нибудь придумал что-нибудь лучше?

Это было полезно?

Решение

На самом деле, хотя я в блоге (видеть Ответ Люка), Я думаю этот лучше, чем моя оболочка 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

Другие советы

Учитывая выбор между решением, предлагаемым 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();
    }
}

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

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, мы можем настроить фабрику каналов так, как мы хотим, и т. д.Мы также не обязаны использовать сгенерированные прокси — подойдет любой интерфейс.

Это рекомендуемый 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\client

Загрузите образец:С# или ВБ

Учитывая, что существует так много проблем с использованием оператора using, (горячие?) Внутренние дискуссии и потоки По этому вопросу я не собираюсь тратить время, пытаясь стать кодовым ковбоем и найти более чистый способ.Я просто смирюсь с этим и реализую клиенты 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
}

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

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

Этот специальный инструмент расширяет WCFProxyGenerator, предоставляя прокси-сервер обработки исключений.Он генерирует дополнительный прокси под названием ExceptionHandlingProxy<T> который наследует ExceptionHandlingProxyBase<T> - последний из которых реализует основную часть функционала прокси.В результате вы можете использовать прокси-сервер по умолчанию, который наследует ClientBase<T> или ExceptionHandlingProxy<T> который инкапсулирует управление временем существования фабрики каналов и канала.ExceptionHandlingProxy учитывает ваш выбор в диалоговом окне «Добавить ссылку на службу» в отношении асинхронных методов и типов коллекций.

Кодплекс есть проект под названием Обработка исключений Генератор прокси WCF.По сути, он устанавливает новый пользовательский инструмент в Visual Studio 2008, а затем использует этот инструмент для создания нового прокси-сервера службы. (Добавить ссылку на услугу).Он имеет несколько хороших функций для работы с неисправными каналами, тайм-аутами и безопасным удалением.Здесь есть отличное видео под названием Обработка исключенийProxyWrapper объясняя, как именно это работает.

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

Основываясь на ответах Марка Гравелла, 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));

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

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

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

@Марк Гравелл

Разве не было бы нормально использовать это:

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

Это облегчит возврат переменных.

Что это?

Это CW-версия принятого ответа, но с включенной обработкой исключений (что я считаю завершенной).

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

Простое использование клиента WCF

После того, как вы создадите свой прокси на стороне клиента, это все, что вам нужно для его реализации.

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

Сервисделегате.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 );
       }

    }
}

ПС:Я сделал этот пост вики-сообществом.Я не буду набирать «баллы» за этот ответ, но предпочитаю, чтобы вы проголосовали за него, если согласны с реализацией, или отредактировали его, чтобы сделать его лучше.

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

Он использует .NET 4 (в частности:контравариантность, 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.");
    }
}

Такая оболочка будет работать:

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.

Конечно, оболочка может перехватывать и больше исключений, если это необходимо, но принцип остается тем же.

Я использовал динамический прокси-сервер 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

Если вам не нужен МОК или используете автоматически сгенерированный клиент (Справочник по сервису), тогда вы можете просто использовать оболочку для управления закрытием и позволить ГК возьмите клиентскую базу, когда она находится в безопасном состоянии, не вызывающем никаких исключений.GC вызовет Dispose в сервисном клиенте, и это вызовет 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();
}

Используйте метод расширения:

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

Краткое содержание

Используя методы, описанные в этом ответе, можно использовать службу 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 закрыть свой канал.

Мне нравится такой способ закрытия соединения:

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

Я написал простой базовый класс который с этим справляется.Он доступен как 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);
    }
}
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); 
}); 

Я хотел бы добавить реализацию Сервиса из Ответ Марка Грэвелла в случае использования 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();
            }
        }
    }
}

Для тех, кто заинтересован, вот перевод VB.NET принятого ответа (ниже).Для краткости я немного усовершенствовал его, объединив некоторые советы других авторов в этой теме.

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

Обратите внимание, что эта конкретная реализация предоставляет возможность настройки 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

В нашей системной архитектуре часто используется Единство МОК framework для создания экземпляров 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);
        }
    }
}

Я упомянул несколько ответов на этот пост и настроил его в соответствии со своими потребностями.

Мне нужна была возможность что-то сделать с клиентом 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));

У меня есть собственная оболочка для канала, которая реализует 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;
        }
}

Кажется, это работает хорошо и позволяет использовать блок using.

Следующий помощник позволяет вызывать 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;
        });
    }
}

Переопределить Dispose() клиента без необходимости создания прокси-класса на основе ClientBase, а также без необходимости управлять созданием и кэшированием каналов!(Обратите внимание, что WcfClient не является АБСТРАКТНЫМ классом и основан на 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();
    }
}

Мой метод сделать это заключался в создании унаследованного класса, который явно реализует IDisposable.Это полезно для людей, которые используют графический интерфейс для добавления ссылки на службу (Добавить ссылку на службу).Я просто добавляю этот класс в проект, создавая ссылку на сервис, и использую его вместо клиента по умолчанию:

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 заявление, как я и ожидал, позволяя моему коду выглядеть более или менее одинаково.

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

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

using (var wrapperdProxy = new Proxy<yourProxy>())
{
   // Do whatever and dispose of Proxy<yourProxy> will be called and work properly.
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top