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

Мне нравится создавать экземпляры моих клиентов службы 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 proxy); public static class Service { public static ChannelFactory _channelFactory = new ChannelFactory(""); public static void Use(UseServiceDelegate codeBlock) { IClientChannel proxy = (IClientChannel)_channelFactory.CreateChannel(); bool success = false; try { codeBlock((T)proxy); proxy.Close(); success = true; } finally { if (!success) { proxy.Abort(); } } } } - public delegate void UseServiceDelegate(T proxy); public static class Service { public static ChannelFactory _channelFactory = new ChannelFactory(""); public static void Use(UseServiceDelegate codeBlock) { IClientChannel proxy = (IClientChannel)_channelFactory.CreateChannel(); bool success = false; try { codeBlock((T)proxy); proxy.Close(); success = true; } finally { if (!success) { proxy.Abort(); } } } } 

Что позволяет:

 Service.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 блока без опасности маскировки исключения из-за ошибки.

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

    26 Solutions collect form web for “Каков наилучший способ обхода проблемы WCF с использованием “ `WCF?”

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

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

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

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

     int newOrderId = 0; // need a value for definite assignment Service.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(Func code) { var chanFactory = GetCachedFactory(); 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 \ клиент

    Загрузите образец: C # или VB

    Учитывая, что существует так много вопросов, связанных с использованием заявления (нагретого?) Внутренние обсуждения и темы по этому вопросу, я не собираюсь тратить время на то, чтобы стать ковбоем кода и найти более чистый способ. Я просто высасываю его и внедряю WCF-клиентам этот подробный (но надежный) способ для моих серверных приложений.

    Дополнительные дополнительные сбои для улова

    Множество исключений вытекает из CommunicationException и я не думаю, что большинство этих исключений следует повторить. Я провалился через каждое исключение в MSDN и нашел короткий список исключаемых повторов исключений (в дополнение к TimeOutException выше). Дайте мне знать, если я пропустил исключение, которое следует повторить.

      // The following is typically thrown on the client when a channel is terminated due to the server closing the connection. catch (ChannelTerminatedException cte) { secureSecretService.Abort(); // todo: Implement delay (backoff) and retry } // The following is thrown when a remote endpoint could not be found or reached. The endpoint may not be found or // reachable because the remote endpoint is down, the remote endpoint is unreachable, or because the remote network is unreachable. catch (EndpointNotFoundException enfe) { secureSecretService.Abort(); // todo: Implement delay (backoff) and retry } // The following exception that is thrown when a server is too busy to accept a message. catch (ServerTooBusyException stbe) { secureSecretService.Abort(); // todo: Implement delay (backoff) and retry } 

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

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

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

    Codeplex имеет проект под названием Exception Handling Proxy Generator . Он в основном устанавливает новый настраиваемый инструмент для Visual Studio 2008, а затем использует этот инструмент для создания нового прокси-сервера службы (Добавить ссылку на службу) . У этого есть хорошая функциональность, чтобы справиться с неисправными каналами, тайм-аутами и безопасным удалением. Здесь есть отличное видео, которое называется ExceptionHandlingProxyWrapper, объясняющее, как это работает.

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

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

     public static class UsingServiceClient { public static void Do(TClient client, Action 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 вместо ChannelFactory, если хотите.

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

    @Marc Gravell

    Не было бы пользы в этом:

     public static TResult Using(this T client, Func work) where T : ICommunicationObject { try { var result = work(client); client.Close(); return result; } catch (Exception e) { client.Abort(); throw; } } 

    Или, то же самое (Func) в случае Service.Use

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

    Что это?

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

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

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

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

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

    ServiceDelegate.cs

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

     public delegate void UseServiceDelegate(T proxy); public static class Service { public static ChannelFactory _channelFactory = new ChannelFactory(""); public static void Use(UseServiceDelegate 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 ); } } } - public delegate void UseServiceDelegate(T proxy); public static class Service { public static ChannelFactory _channelFactory = new ChannelFactory(""); public static void Use(UseServiceDelegate 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 (в частности: контравариантность, LINQ, var ):

     ///  /// Delegate type of the service method to perform. ///  /// The service proxy. /// The type of service to use. internal delegate void UseServiceDelegate(T proxy); ///  /// Wraps using a WCF service. ///  /// The type of service to use. internal static class Service { ///  /// A dictionary to hold looked-up endpoint names. ///  private static readonly IDictionary cachedEndpointNames = new Dictionary(); ///  /// A dictionary to hold created channel factories. ///  private static readonly IDictionary> cachedFactories = new Dictionary>(); ///  /// Uses the specified code block. ///  /// The code block. internal static void Use(UseServiceDelegate 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(); } } } ///  /// Gets the channel factory. ///  /// The channel factory. private static ChannelFactory GetChannelFactory() { lock (cachedFactories) { var endpointName = GetEndpointName(); if (cachedFactories.ContainsKey(endpointName)) { return cachedFactories[endpointName]; } var factory = new ChannelFactory(endpointName); cachedFactories.Add(endpointName, factory); return factory; } } ///  /// Gets the name of the endpoint. ///  /// The name of the endpoint. 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().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."); } } - ///  /// Delegate type of the service method to perform. ///  /// The service proxy. /// The type of service to use. internal delegate void UseServiceDelegate(T proxy); ///  /// Wraps using a WCF service. ///  /// The type of service to use. internal static class Service { ///  /// A dictionary to hold looked-up endpoint names. ///  private static readonly IDictionary cachedEndpointNames = new Dictionary(); ///  /// A dictionary to hold created channel factories. ///  private static readonly IDictionary> cachedFactories = new Dictionary>(); ///  /// Uses the specified code block. ///  /// The code block. internal static void Use(UseServiceDelegate 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(); } } } ///  /// Gets the channel factory. ///  /// The channel factory. private static ChannelFactory GetChannelFactory() { lock (cachedFactories) { var endpointName = GetEndpointName(); if (cachedFactories.ContainsKey(endpointName)) { return cachedFactories[endpointName]; } var factory = new ChannelFactory(endpointName); cachedFactories.Add(endpointName, factory); return factory; } } ///  /// Gets the name of the endpoint. ///  /// The name of the endpoint. 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().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."); } } 

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

     public class ServiceClientWrapper : IDisposable { private ServiceType _channel; public ServiceType Channel { get { return _channel; } } private static ChannelFactory _channelFactory; public ServiceClientWrapper() { if(_channelFactory == null) // Given that the endpoint name is the same as FullName of contract. _channelFactory = new ChannelFactory(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()) { var request = ... response = clientWrapper.Channel.MyServiceCall(request); } // Use your response object. 

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

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

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

    Мне это нравится, поскольку вы можете внедрять службы WCF без необходимости беспокоиться о каких-либо деталях WCF. И нет никаких дополнительных трещин, как и другие решения.

    Посмотрите на код, на самом деле это довольно просто: WCF Dynamic Proxy

    Если вам не нужен IoC или вы используете автогенерированный клиент (Service Reference), тогда вы можете просто использовать оболочку для управления закрытием и позволить GC взять клиентскую базу, когда она находится в безопасном состоянии, которое не будет вызывать никаких исключений. GC вызовет Dispose в serviceclient, и это вызовет Close . Так как он закрыт, он не может нанести никакого ущерба. Я использую это без проблем в производственном коде.

     public class AutoCloseWcf : IDisposable { private ICommunicationObject CommunicationObject; public AutoDisconnect(ICommunicationObject CommunicationObject) { this.CommunicationObject = CommunicationObject; } public void Dispose() { if (CommunicationObject == null) return; try { if (CommunicationObject.State != CommunicationState.Faulted) { CommunicationObject.Close(); } else { CommunicationObject.Abort(); } } catch (CommunicationException ce) { CommunicationObject.Abort(); } catch (TimeoutException toe) { CommunicationObject.Abort(); } catch (Exception e) { CommunicationObject.Abort(); //Perhaps log this } finally { CommunicationObject = null; } } } 

    Затем, когда вы обращаетесь к серверу, вы создаете клиента и using в autodisconect:

     var Ws = new ServiceClient("netTcpEndPointName"); using (new AutoCloseWcf(Ws)) { Ws.Open(); Ws.Test(); } 

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

     public static class CommunicationObjectExtensions { public static TResult MakeSafeServiceCall(this TService client, Func 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(""); var serviceHelper = new ServiceHelper(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(); } } 

    Вооружившись вышеуказанными classами, мы теперь можем написать

     public class ServiceHelper { private readonly ChannelFactory _channelFactory; public ServiceHelper(ChannelFactory channelFactory ) { _channelFactory = channelFactory; } public IMyService CreateChannel() { var channel = _channelFactory.CreateChannel(); var channelDisposer = new ProxyDisposer(channel as IClientChannel); return new ProxyWrapper(channel, channelDisposer); } } 

    Это позволяет нам использовать нашу услугу с using блока using :

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

    Создание этого общего

    Все, что мы сделали до сих пор, – это переформулировать решение Томаса . Что мешает ProxyWrapper этот код, так это тот факт, что class ProxyWrapper должен быть повторно реализован для каждого ProxyWrapper контракта. Теперь мы рассмотрим class, который позволяет нам динамически создавать этот тип с помощью IL:

     public class ServiceHelper { private readonly ChannelFactory _channelFactory; private static readonly Func _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)channelCreatorMethod.CreateDelegate(typeof(Func)); } public ServiceHelper(ChannelFactory 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(); } } 

    With our new helper class we can now write

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

    Note that you could also use the same technique (with slight modifications) for auto-generated clients inheriting for ClientBase<> (instead of using ChannelFactory<> ), or if you want to use a different implementation of IDisposable to close your channel.

    I like this way of closing connection:

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

    I have written a simple base class that handles this. It’s available as a NuGet package and it’s quite easy to use.

     //MemberServiceClient is the class generated by SvcUtil public class MemberServiceManager : ServiceClientBase { 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 { public static ChannelFactory ChannelFactory = new ChannelFactory("*"); public static TReturn Use(Func 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(); } } } } 

    So it allows to write return statements nicely:

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

    I’d like to add implementation of Service from Marc Gravell’s answer for case of using ServiceClient instead of ChannelFactory.

     public interface IServiceConnector { void Connect(Action clientUsage); TResult Connect(Func channelUsage); } internal class ServiceConnector : IServiceConnector where TServiceInterface : class where TService : ClientBase, TServiceInterface, new() { public TResult Connect(Func channelUsage) { var result = default(TResult); Connect(channel => { result = channelUsage(channel); }); return result; } public void Connect(Action 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(); } } } } 

    For those interested, here’s a VB.NET translation of the accepted answer (below). I’ve refined it a bit for brevity, combining some of the tips by others in this thread.

    I admit it’s off-topic for the originating tags (C#), but as I wasn’t able to find a VB.NET version of this fine solution I assume that others will be looking as well. The Lambda translation can be a bit tricky, so I’d like to save someone the trouble.

    Note that this particular implementation provides the ability to configure the ServiceEndpoint at runtime.


    Код:

     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 

    Our system architecture often uses the Unity IoC framework to create instances of ClientBase so there’s no sure way to enforce that the other developers even use using{} blocks. In order to make it as fool-proof as possible, I made this custom class that extends ClientBase, and handles closing down the channel on dispose, or on finalize in case someone doesn’t explicitly dispose of the Unity created instance.

    There is also stuff that needed to be done in the constructor to set up the channel for custom credentials and stuff, so that’s in here too…

     public abstract class PFServer2ServerClientBase : ClientBase, 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; } } } 

    Then a client can simply:

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

    And the caller can do any of these:

     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()) { return t.TestMethod(value); } } } 

    I referred few answers on this post and customized it as per my needs.

    I wanted the ability to do something with WCF client before using it so the DoSomethingWithClient() method.

     public interface IServiceClientFactory { T DoSomethingWithClient(); } public partial class ServiceClient : IServiceClientFactory { public ServiceClient DoSomethingWithClient() { var client = this; // do somthing here as set client credentials, etc. //client.ClientCredentials = ... ; return client; } } 

    Here is the helper class:

     public static class Service where TClient : class, ICommunicationObject, IServiceClientFactory, new() { public static TReturn Use(Func 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(); } } } } 

    And I can use it as:

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

    I have my own wrapper for a channel which implements Dispose as follows:

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

    This seems to work well and allows a using block to be used.

    The following helper allows to call void and non-void methods. Применение:

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

    The class itself is:

     public class WcfInvoker where TService : ICommunicationObject { readonly Func _clientFactory; public WcfInvoker(Func clientFactory) { _clientFactory = clientFactory; } public T Invoke(Func action) { var client = _clientFactory(); try { var result = action(client); client.Close(); return result; } catch { client.Abort(); throw; } } public void Invoke(Action action) { Invoke(client => { action(client); return null; }); } } 

    Override the client’s Dispose() without the need to generate a proxy class based on ClientBase, also without the need to manage channel creation and caching ! (Note that WcfClient is not an ABSTRACT class and is based on ClientBase)

     // No need for a generated proxy class //using (WcfClient orderService = new WcfClient()) //{ // results = orderService.GetProxy().PlaceOrder(input); //} public class WcfClient : ClientBase, 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(); } } 

    My method of doing this has been to create an inherited class that explicitly implements IDisposable. This is useful for folks who use the gui to add the service reference ( Add Service Reference ). I just drop this class in the project making the service reference and use it instead of the default client:

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

    Note: This is just a simple implementation of dispose, you can implement more complex dispose logic if you like.

    You can then replace all your calls made with the regular service client with the safe clients, like this:

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

    I like this solution as it does not require me to have access to the Interface definitions and I can use the using statement as I would expect while allowing my code to look more or less the same.

    You will still need to handle the exceptions which can be thrown as pointed out in other comments in this thread.

    You could also use a DynamicProxy to extend the Dispose() method. This way you could do something like:

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

    Как изменить кодировку символов в текстовом файле в OpenOffice.org writer?

    Приложение YouTube для Android Play Video Intent

    Могу ли я использовать свою Linux-ящику как сервер «System Imaging»? (То есть, Norton Ghost Server?)

    Получение 2 IP-адресов на одной сетевой карте с использованием DHCP

    Могу ли я установить 64-разрядную версию Windows 7 на VirtualBox, работающую на 32-разрядной ОС Windows 7?

    Все окна на экране одновременно в Windows XP

    Отправить предварительно отформатированный текст в Skype

    Обновление Dell Studio 1555 до ssd, но cd-drive не работает

    Слияние двух шрифтов

    Экран Windows 8.1 Black

    Как обновить вложенные свойства состояния в React

    Как я могу переназначить окна и клавиши alt в OS X?

    Как я могу менять цвета терминала GNOME каждый раз, когда он запускается?

    Почему tracert показывает частный IP-адрес сразу после моего маршрутизатора, хотя у него есть общедоступный IP-адрес?

    Создайте гиперссылку из URL и заголовка в Excel

    Давайте будем гением компьютера.