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


мне нравится создавать экземпляры моих клиентов службы WCF в пределах using блок, как это в значительной степени стандартный способ использования ресурсов, которые реализуют IDisposable:

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

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

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

26 380

26 ответов:

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

Это почти как у вас в Примере. В некоторых проектах мы пишем строго типизированные вспомогательные методы, поэтому мы в конечном итоге пишем такие вещи, как "ФОС.UseFooService(f=>f...)".

Я нахожу его довольно элегантным, учитывая все обстоятельства. Есть конкретная проблема, с которой вы столкнулись?

Это позволяет подключать другие отличные функции. Например, на одном сайте Сайт аутентифицируется в Службе от имени зарегистрированного пользователя. (Сайт не имеет учетных данных сам по себе.) Путем писать наш собственный хелпер метода "UseService", мы можем установить фабрику канала путь мы хотим, etc. Мы также не обязаны использовать сгенерированный прокси -- любой интерфейс подойдет.

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

Скачать образец: C# или В. Б.

учитывая, что есть так много вопросов С использованием оператора,(обогрев?) Внутренние обсуждения и threads в этом вопросе я не собираюсь тратить свое время, пытаясь стать кодовым ковбоем и найти более чистый способ. Я просто буду сосать его и реализовывать клиенты 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 учитывает выбранные параметры в диалоговом окне Добавить ссылку на службу в отношении асинхронных методов и типов коллекций.

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

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

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

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 при желании.

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

@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

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

что это?

Это версия 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: Я сделал этот пост Вики сообщества. Я не буду собирать "очки" от это ответ, но вы предпочитаете его озвучить, если согласны с реализацией, или отредактировать его, чтобы сделать его лучше.

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

он использует .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.");
    }
}

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

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

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

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 в блоке using со следующим синтаксисом:

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

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

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


подробности

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

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

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

обратите внимание, что этот конкретный реализация предоставляет возможность настроить 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{} блоки. Чтобы сделать его максимально надежным, я сделал этот пользовательский класс, который расширяет клиентскую базу и обрабатывает закрытие канала на dispose или на finalize в случае, если кто-то явно не утилизирует созданный экземпляр 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. Это полезно для людей, которые используют графический интерфейс для добавления ссылки на службу (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 заявление, как я и ожидал, позволяя моему коду выглядеть более или менее одинаково.

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

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

using (var wrapperdProxy = new Proxy<yourProxy>())
{
   // Do whatever and dispose of Proxy<yourProxy> will be called and work properly.
}