Background

ChannelAdam WCF Library — Version 1 Documentation

Background

The Issues

The issues faced by .NET developers are explained in depth in the article How To Call WCF Services Properly, where I researched and highlighted the typical problems with using WCF clients (such as memory and connection leaks), and concluded that the most correct solution for consuming WCF services would:

  • Perform the Close/Abort pattern without a race condition;
  • Handle the situation when the service operation throws exceptions;
  • Handle the situations when both the Close and Abort methods throw exceptions; and
  • Handle asynchronous exceptions such as the ThreadAbortException.

Below is the verbose sample code that is required for correctly using a .NET WCF client, performing exception handling and correctly performing the Close/Abort pattern. Unfortunately, due to the way Microsoft designed WCF, if you want to use a WCF service client cleanly without connection or memory leaks, this is the type of extensive code that must be written for every service operation call.

  1SampleServiceClient client = null;
  2
  3try
  4{
  5    client = new SampleServiceClient();
  6
  7    var response = client.SampleOperation(1234);
  8
  9    // Do some business logic
 10}
 11catch (FaultException<MyCustomException>)
 12{
 13    // Do some business logic for this SOAP Fault Exception
 14}
 15catch (FaultException)
 16{
 17    // Do some business logic for this SOAP Fault Exception
 18}
 19catch (CommunicationException)
 20{
 21    // Catch this expected exception so it is not propagated further.
 22    // Perhaps write this exception out to log file for gathering statistics...
 23}
 24catch (TimeoutException)
 25{
 26    // Catch this expected exception so it is not propagated further.
 27    // Perhaps write this exception out to log file for gathering statistics...
 28}
 29catch (Exception)
 30{
 31    // An unexpected exception that we don't know how to handle.
 32    // Perhaps write this exception out to log file for support purposes...
 33    throw;
 34}
 35finally
 36{
 37    // This will:
 38    // - be executed if any exception was thrown above in the 'try' (including ThreadAbortException); and
 39    // - ensure that CloseOrAbortServiceChannel() itself will not be interrupted by a ThreadAbortException
 40    //   (since it is executing from within a 'finally' block)
 41    CloseOrAbortServiceChannel(client);
 42
 43    // Unreference the client
 44    client = null;
 45}
 46
 47
 48private void CloseOrAbortServiceChannel(ICommunicationObject communicationObject)
 49{
 50    bool isClosed = false;
 51
 52    if (communicationObject == null || communicationObject.State == CommunicationState.Closed)
 53    {
 54        return;
 55    }
 56
 57    try 
 58    {
 59        if (communicationObject.State != CommunicationState.Faulted)
 60        {
 61            communicationObject.Close();
 62            isClosed = true;
 63        }
 64    }
 65    catch (CommunicationException)
 66    {
 67        // Catch this expected exception so it is not propagated further.
 68        // Perhaps write this exception out to log file for gathering statistics...
 69    }
 70    catch (TimeoutException)
 71    {
 72        // Catch this expected exception so it is not propagated further.
 73        // Perhaps write this exception out to log file for gathering statistics...
 74    }
 75    catch (Exception)
 76    {
 77        // An unexpected exception that we don't know how to handle.
 78        // Perhaps write this exception out to log file for support purposes...
 79        throw;
 80    }
 81    finally
 82    {
 83        // If State was Faulted or any exception occurred while doing the Close(), then do an Abort()
 84        if (!isClosed)
 85        {
 86            AbortServiceChannel(communicationObject);
 87        }
 88    }
 89}
 90
 91
 92private static void AbortServiceChannel(ICommunicationObject communicationObject)
 93{
 94    try
 95    {
 96        communicationObject.Abort();
 97    }
 98    catch (Exception)
 99    {
100        // An unexpected exception that we don't know how to handle.
101        // If we are in this situation:
102        // - we should NOT retry the Abort() because it has already failed and there is nothing to suggest it could be successful next time
103        // - the abort may have partially succeeded
104        // - the actual service call may have been successful
105        //
106        // The only thing we can do is hope that the channel's resources have been released.
107        // Do not rethrow this exception because the actual service operation call might have succeeded
108        // and an exception closing the channel should not stop the client doing whatever it does next.
109        //
110        // Perhaps write this exception out to log file for gathering statistics and support purposes...
111    }
112}

ChannelAdam WCF Library Makes It Easy

The ChannelAdam WCF Library is an API for working with WCF clients, that significantly reduces the amount of that code that developers have to write. It allows you to forget about the complex subtleties of WCF clients, channels and communication states, and lets you just use your service client in a way that is very similar to how you probably are doing it now - but without the leaks!

When you use the ChannelAdam WCF Library’s ServiceConsumer, you know that you can keep calling service operations through the ServiceConsumer and it will ‘just work’ and not leak connections or memory - no matter what happens!

The ChannelAdam WCF Library’s ServiceConsumer hides the inner complexity of service channels. For example, if a service channel / communication object / service client moves into a Fault state, the ServiceConsumer will correctly perform the Close/Abort pattern and dispose of the channel. The next time a request is made through the ServiceConsumer, a new channel is created under the covers to seamlessly process the request.

Background

ChannelAdam WCF Library — Version 2 Documentation

Background

The Issues

The issues faced by .NET developers are explained in depth in the article How To Call WCF Services Properly, where I researched and highlighted the typical problems with using WCF clients (such as memory and connection leaks), and concluded that the most correct solution for consuming WCF services would:

  • Perform the Close/Abort pattern without a race condition;
  • Handle the situation when the service operation throws exceptions;
  • Handle the situations when both the Close and Abort methods throw exceptions; and
  • Handle asynchronous exceptions such as the ThreadAbortException.

Below is the verbose sample code that is required for correctly using a .NET WCF client, performing exception handling and correctly performing the Close/Abort pattern. Unfortunately, due to the way Microsoft designed WCF, if you want to use a WCF service client cleanly without connection or memory leaks, this is the type of extensive code that must be written for every service operation call.

  1SampleServiceClient client = null;
  2
  3try
  4{
  5    client = new SampleServiceClient();
  6
  7    var response = client.SampleOperation(1234);
  8
  9    // Do some business logic
 10}
 11catch (FaultException<MyCustomException>)
 12{
 13    // Do some business logic for this SOAP Fault Exception
 14}
 15catch (FaultException)
 16{
 17    // Do some business logic for this SOAP Fault Exception
 18}
 19catch (CommunicationException)
 20{
 21    // Catch this expected exception so it is not propagated further.
 22    // Perhaps write this exception out to log file for gathering statistics...
 23}
 24catch (TimeoutException)
 25{
 26    // Catch this expected exception so it is not propagated further.
 27    // Perhaps write this exception out to log file for gathering statistics...
 28}
 29catch (Exception)
 30{
 31    // An unexpected exception that we don't know how to handle.
 32    // Perhaps write this exception out to log file for support purposes...
 33    throw;
 34}
 35finally
 36{
 37    // This will:
 38    // - be executed if any exception was thrown above in the 'try' (including ThreadAbortException); and
 39    // - ensure that CloseOrAbortServiceChannel() itself will not be interrupted by a ThreadAbortException
 40    //   (since it is executing from within a 'finally' block)
 41    CloseOrAbortServiceChannel(client);
 42
 43    // Unreference the client
 44    client = null;
 45}
 46
 47
 48private void CloseOrAbortServiceChannel(ICommunicationObject communicationObject)
 49{
 50    bool isClosed = false;
 51
 52    if (communicationObject == null || communicationObject.State == CommunicationState.Closed)
 53    {
 54        return;
 55    }
 56
 57    try 
 58    {
 59        if (communicationObject.State != CommunicationState.Faulted)
 60        {
 61            communicationObject.Close();
 62            isClosed = true;
 63        }
 64    }
 65    catch (CommunicationException)
 66    {
 67        // Catch this expected exception so it is not propagated further.
 68        // Perhaps write this exception out to log file for gathering statistics...
 69    }
 70    catch (TimeoutException)
 71    {
 72        // Catch this expected exception so it is not propagated further.
 73        // Perhaps write this exception out to log file for gathering statistics...
 74    }
 75    catch (Exception)
 76    {
 77        // An unexpected exception that we don't know how to handle.
 78        // Perhaps write this exception out to log file for support purposes...
 79        throw;
 80    }
 81    finally
 82    {
 83        // If State was Faulted or any exception occurred while doing the Close(), then do an Abort()
 84        if (!isClosed)
 85        {
 86            AbortServiceChannel(communicationObject);
 87        }
 88    }
 89}
 90
 91
 92private static void AbortServiceChannel(ICommunicationObject communicationObject)
 93{
 94    try
 95    {
 96        communicationObject.Abort();
 97    }
 98    catch (Exception)
 99    {
100        // An unexpected exception that we don't know how to handle.
101        // If we are in this situation:
102        // - we should NOT retry the Abort() because it has already failed and there is nothing to suggest it could be successful next time
103        // - the abort may have partially succeeded
104        // - the actual service call may have been successful
105        //
106        // The only thing we can do is hope that the channel's resources have been released.
107        // Do not rethrow this exception because the actual service operation call might have succeeded
108        // and an exception closing the channel should not stop the client doing whatever it does next.
109        //
110        // Perhaps write this exception out to log file for gathering statistics and support purposes...
111    }
112}

ChannelAdam WCF Library Makes It Easy

The ChannelAdam WCF Library is an API for working with WCF clients, that significantly reduces the amount of that code that developers have to write. It allows you to forget about the complex subtleties of WCF clients, channels and communication states, and lets you just use your service client in a way that is very similar to how you probably are doing it now - but without the leaks!

When you use the ChannelAdam WCF Library’s ServiceConsumer, you know that you can keep calling service operations through the ServiceConsumer and it will ‘just work’ and not leak connections or memory - no matter what happens!

The ChannelAdam WCF Library’s ServiceConsumer hides the inner complexity of service channels. For example, if a service channel / communication object / service client moves into a Fault state, the ServiceConsumer will correctly perform the Close/Abort pattern and dispose of the channel. The next time a request is made through the ServiceConsumer, a new channel is created under the covers to seamlessly process the request.

Basic Usage

ChannelAdam WCF Library — Version 1 Documentation

Basic Usage

Create a ServiceConsumer with the ServiceConsumerFactory

The first thing you need to understand is that you consume your service operations through the usage of the ChannelAdam.ServiceModel.ServiceConsumer class.

The second thing you need to understand is that the best way to create the ServiceConsumer is by using the Create method on the ChannelAdam.ServiceModel.ServiceConsumerFactory.

In order to use the ServiceConsumerFactory.Create method, simply pass in as a parameter a factory method for creating the service client.

For example:

1IServiceConsumer<IFakeService> service = ServiceConsumerFactory.Create<IFakeService>(() => new FakeServiceClient()));

The ServiceConsumerFactory.Create method returns a IServiceConsumer<TServiceInterface> type. In all further examples, I will use the var keyword instead - such as:

1var service = ServiceConsumerFactory.Create<IFakeService>(() => new FakeServiceClient()));

Alternatively, you can pass in as a string parameter the endpoint configuration name from the service client section of your application’s .config file. For example:

1var service = ServiceConsumerFactory.Create<IFakeService>("BasicHttpBinding_IFakeService")); 

Four Ways to Clean Up the ServiceConsumer

There are four ways in which the ServiceConsumer can be cleaned up:

  1. Implicitly via the Using statement;
  2. Explicitly with the Close method;
  3. Explicitly with the Dispose method; or
  4. Automatically by the Garbage Collector.

1. The Using Statement

The recommended and easiest way to use the ServiceConsumer is with the using statement - because it is a well behaved implementation of IDisposable (unlike Microsoft’s service clients!). The service channel will automatically be correctly disposed and closed or aborted, no matter what happens.

1using (var service = ServiceConsumerFactory.Create<IFakeService>(() => new FakeServiceClient())))
2{
3    ...
4}

2. The Close Method

The ServiceConsumer and underlying channel can be closed immediately by calling the Close method on the ServiceConsumer instance.

1service.Close();

If you want to, you still can immediately call another service operation via that same ServiceConsumer instance, as under the covers, a new service channel will be created seamlessly and automatically.

3. The Dispose Method

The ServiceConsumer and underlying channel can be disposed of immediately by calling the Dispose method on the ServiceConsumer instance.

1service.Dispose();

As per standard IDisposable concepts, you should not attempt to reuse the ServiceConsumer after disposing it.

4. Garbage Collection

If you happen to accidently forget to wrap your ServiceConsumer instance in a Using statement or call the Close method, rest assured that the ServiceConsumer will still be cleaned up! When the ServiceConsumer instance goes out of scope, the .NET Garbage Collector will [eventually] kick in and call its destructor/finalise method, thus correctly cleaning up the service channel and preventing any leaks.

Since there is an indeterminate amount of time before the Garbage Collector kicks in, I would recommend the Using statement approach, as it minimises the number of open channels and other resources currently in use, over the lifetime of your application.

How To Call a Service Operation

There are two ways in which to call a service operation via the ServiceConsumer:

  1. Via the Operations property; or
  2. Via the Consume method.

The Operations Property

The Operations property exposes the interface of your service. It can be used just like the normal service client that you already would be familiar with.

Below is an example of how to call the AddIntegers operation via the Operations property.

1using (var service = ServiceConsumerFactory.Create<IFakeService>(() => new FakeServiceClient())))
2{
3    try
4    {
5        int actualValue = service.Operations.AddIntegers(1, 1); 
6        ...
7    }
8    ...
9}

And below is an example of how to do some exception handling with it.

 1using (var service = ServiceConsumerFactory.Create<IFakeService>(() => new FakeServiceClient())))
 2{
 3    try
 4    {
 5        int actualValue = service.Operations.AddIntegers(1, 1); 
 6        ...
 7    }
 8    catch (FaultException<MyCustomException> fe)
 9    {
10        Console.WriteLine("Service operation threw a custom fault: " + fe.ToString());
11    }
12    catch (FaultException fe)
13    {
14        Console.WriteLine("Service operation threw a fault: " + fe.ToString());
15    }
16    catch (Exception ex)
17    {
18        Console.WriteLine("An unexpected error occurred while calling the service operation: " + ex.ToString());
19    }
20}

There is no need to catch a CommunicationException or TimeOutException and try to perform the Close/Abort pattern, as the underlying channel is dealt with automatically for you by the ServiceConsumer.

The Consume Method

As an alternative to the Operations property, you can call a service operation via the Consume method on the ServiceConsumer instance.

The Consume method returns an instance of IOperationResult<TReturnTypeOfYourOperation> which wraps the result of the operation. This interface has the following helpful properties:

  • TReturnTypeOfYourOperation Value - which gives you access to the return value from the operation;
  • bool HasException - whether there was an exception thrown by the service operation;
  • bool HasNoException - whether there was not an exception thrown by the service operation;
  • bool HasFaultException - whether an exception of type FaultException was thrown by the service operation;
  • bool HasFaultException<OfThisType> - whether an exception of FaultException<OfThisType> was thrown by the service operation; and
  • Exception Exception - the exception that was thrown by the service operation.

Below is an example of how to call the AddIntegers operation via the Consume method.

 1using (var service = ServiceConsumerFactory.Create<IFakeService>(() => new FakeServiceClient())))
 2{
 3	IOperationResult<int> result = service.Consume(operation => operation.AddIntegers(1, 1));
 4
 5    if (result.HasNoException)
 6    {
 7        Console.WriteLine("Result was: " + result.Value);
 8        ...
 9    }
10    ...
11}

And below is an example of how to do some exception handling with IOperationResult.

 1using (var service = ServiceConsumerFactory.Create<IFakeService>(() => new FakeServiceClient())))
 2{
 3    var result = service.Consume(operation => operation.AddIntegers(1, 1));
 4
 5    if (result.HasNoException)
 6    {
 7        Console.WriteLine("Result was: " + result.Value);
 8    }
 9    else
10    {
11        if (result.HasFaultExceptionOfType<MyCustomException>())
12        {
13            Console.WriteLine("Service operation threw a custom fault: " + result.Exception.ToString());
14        }
15        else if (result.HasFaultException)
16        {
17            Console.WriteLine("Service operation threw a fault: " + result.Exception.ToString());
18        }
19        else
20        {
21            Console.WriteLine("An unexpected error occurred while calling the service operation: " + result.Exception.ToString());
22        }
23    }
24}

Basic Usage

ChannelAdam WCF Library — Version 2 Documentation

Basic Usage

Create a ServiceConsumer with the ServiceConsumerFactory

The first thing you need to understand is that you consume your service operations through the usage of the ChannelAdam.ServiceModel.ServiceConsumer class.

The second thing you need to understand is that the best way to create the ServiceConsumer is by using the Create method on the ChannelAdam.ServiceModel.ServiceConsumerFactory.

In order to use the ServiceConsumerFactory.Create method, simply pass in as a parameter a factory method for creating the service client.

For example:

1IServiceConsumer<IFakeService> service = ServiceConsumerFactory.Create<IFakeService>(() => new FakeServiceClient()));

The ServiceConsumerFactory.Create method returns a IServiceConsumer<TServiceInterface> type. In all further examples, I will use the var keyword instead - such as:

1var service = ServiceConsumerFactory.Create<IFakeService>(() => new FakeServiceClient()));

Alternatively, you can pass in as a string parameter the endpoint configuration name from the service client section of your application’s .config file. For example:

1var service = ServiceConsumerFactory.Create<IFakeService>("BasicHttpBinding_IFakeService")); 

Four Ways to Clean Up the ServiceConsumer

There are four ways in which the ServiceConsumer can be cleaned up:

  1. Implicitly via the Using statement;
  2. Explicitly with the Close method;
  3. Explicitly with the Dispose method; or
  4. Automatically by the Garbage Collector.

1. The Using Statement

The recommended and easiest way to use the ServiceConsumer is with the using statement - because it is a well behaved implementation of IDisposable (unlike Microsoft’s service clients!). The service channel will automatically be correctly disposed and closed or aborted, no matter what happens.

1using (var service = ServiceConsumerFactory.Create<IFakeService>(() => new FakeServiceClient())))
2{
3    ...
4}

2. The Close Method

The ServiceConsumer and underlying channel can be closed immediately by calling the Close method on the ServiceConsumer instance.

1service.Close();

If you want to, you still can immediately call another service operation via that same ServiceConsumer instance, as under the covers, a new service channel will be created seamlessly and automatically.

3. The Dispose Method

The ServiceConsumer and underlying channel can be disposed of immediately by calling the Dispose method on the ServiceConsumer instance.

1service.Dispose();

As per standard IDisposable concepts, you should not attempt to reuse the ServiceConsumer after disposing it.

4. Garbage Collection

If you happen to accidently forget to wrap your ServiceConsumer instance in a Using statement or call the Close method, rest assured that the ServiceConsumer will still be cleaned up! When the ServiceConsumer instance goes out of scope, the .NET Garbage Collector will [eventually] kick in and call its destructor/finalise method, thus correctly cleaning up the service channel and preventing any leaks.

Since there is an indeterminate amount of time before the Garbage Collector kicks in, I would recommend the Using statement approach, as it minimises the number of open channels and other resources currently in use, over the lifetime of your application.

How To Call a Service Operation

There are two ways in which to call a service operation via the ServiceConsumer:

  1. Via the Operations property; or
  2. Via the Consume method.

The Operations Property

The Operations property exposes the interface of your service. It can be used just like the normal service client that you already would be familiar with.

Below is an example of how to call the AddIntegers operation via the Operations property.

1using (var service = ServiceConsumerFactory.Create<IFakeService>(() => new FakeServiceClient())))
2{
3    try
4    {
5        int actualValue = service.Operations.AddIntegers(1, 1); 
6        ...
7    }
8    ...
9}

And below is an example of how to do some exception handling with it.

 1using (var service = ServiceConsumerFactory.Create<IFakeService>(() => new FakeServiceClient())))
 2{
 3    try
 4    {
 5        int actualValue = service.Operations.AddIntegers(1, 1); 
 6        ...
 7    }
 8    catch (FaultException<MyCustomException> fe)
 9    {
10        Console.WriteLine("Service operation threw a custom fault: " + fe.ToString());
11    }
12    catch (FaultException fe)
13    {
14        Console.WriteLine("Service operation threw a fault: " + fe.ToString());
15    }
16    catch (Exception ex)
17    {
18        Console.WriteLine("An unexpected error occurred while calling the service operation: " + ex.ToString());
19    }
20}

There is no need to catch a CommunicationException or TimeOutException and try to perform the Close/Abort pattern, as the underlying channel is dealt with automatically for you by the ServiceConsumer.

The Consume Method

As an alternative to the Operations property, you can call a service operation via the Consume method on the ServiceConsumer instance.

The Consume method returns an instance of IOperationResult<TReturnTypeOfYourOperation> which wraps the result of the operation. This interface has the following helpful properties:

  • TReturnTypeOfYourOperation Value - which gives you access to the return value from the operation;
  • bool HasException - whether there was an exception thrown by the service operation;
  • bool HasNoException - whether there was not an exception thrown by the service operation;
  • bool HasFaultException - whether an exception of type FaultException was thrown by the service operation;
  • bool HasFaultException<OfThisType> - whether an exception of FaultException<OfThisType> was thrown by the service operation; and
  • Exception Exception - the exception that was thrown by the service operation.

Below is an example of how to call the AddIntegers operation via the Consume method.

 1using (var service = ServiceConsumerFactory.Create<IFakeService>(() => new FakeServiceClient())))
 2{
 3	IOperationResult<int> result = service.Consume(operation => operation.AddIntegers(1, 1));
 4
 5    if (result.HasNoException)
 6    {
 7        Console.WriteLine("Result was: " + result.Value);
 8        ...
 9    }
10    ...
11}

And below is an example of how to do some exception handling with IOperationResult.

 1using (var service = ServiceConsumerFactory.Create<IFakeService>(() => new FakeServiceClient())))
 2{
 3    var result = service.Consume(operation => operation.AddIntegers(1, 1));
 4
 5    if (result.HasNoException)
 6    {
 7        Console.WriteLine("Result was: " + result.Value);
 8    }
 9    else
10    {
11        if (result.HasFaultExceptionOfType<MyCustomException>())
12        {
13            Console.WriteLine("Service operation threw a custom fault: " + result.Exception.ToString());
14        }
15        else if (result.HasFaultException)
16        {
17            Console.WriteLine("Service operation threw a fault: " + result.Exception.ToString());
18        }
19        else
20        {
21            Console.WriteLine("An unexpected error occurred while calling the service operation: " + result.Exception.ToString());
22        }
23    }
24}

If you generated async Task methods with your service client, and if you call those async Task methods with Consume(), the task will be executed immediately/synchronously.

The ConsumeAsync Method

Use ConsumeAsync() to asynchronously execute async Task methods on your service client. The syntax is the same as the Consume() example above.

Sample Usage Code

For more sample code, please see SampleUsage.cs.

Channel Close Trigger Strategies

ChannelAdam WCF Library — Version 1 Documentation

Channel Close Trigger Strategies

Overview

When an exception occurs while calling the service operation, the ServiceConsumer follows a specific strategy to determine whether the exception and/or current channel state is severe enough to necessitate and trigger the closing/aborting of the service channel.

These “Service Channel Close Trigger Strategies” are an implementation of the interface IServiceChannelCloseTriggerStrategy. There is one method in this interface:

1bool ShouldCloseChannel(ICommunicationObject channel, Exception exception);

If the method ShouldCloseChannel returns true, then the ServiceConsumer will perform the Close/Abort pattern.

The Out-Of-The-Box Strategy

Out of the box, the DefaultServiceChannelCloseTriggerStrategy is used. This strategy is very simple, effective and safe. It triggers a close to occur if the exception is anything but a FaultException, regardless of the state of the service channel. It is highly probable that the channel is no longer usable in the case where there was any other type of exception.

Use Your Own

If you find that this strategy does not suit your need, you can create your own class that implements that interface and specify your class as one of the overloads of the Create method on the ServiceConsumerFactory. Alternatively, you can directly set the property ChannelCloseTriggerStrategy on the ServiceConsumer instance itself.

Channel Close Trigger Strategies

ChannelAdam WCF Library — Version 2 Documentation

Channel Close Trigger Strategies

Overview

When an exception occurs while calling the service operation, the ServiceConsumer follows a specific strategy to determine whether the exception and/or current channel state is severe enough to necessitate and trigger the closing/aborting of the service channel.

These “Service Channel Close Trigger Strategies” are an implementation of the interface IServiceChannelCloseTriggerStrategy. There is one method in this interface:

1bool ShouldCloseChannel(ICommunicationObject channel, Exception exception);

If the method ShouldCloseChannel returns true, then the ServiceConsumer will perform the Close/Abort pattern.

The Out-Of-The-Box Strategy

Out of the box, the DefaultServiceChannelCloseTriggerStrategy is used. This strategy is very simple, effective and safe. It triggers a close to occur if the exception is anything but a FaultException, regardless of the state of the service channel. It is highly probable that the channel is no longer usable in the case where there was any other type of exception.

Use Your Own

If you find that this strategy does not suit your need, you can create your own class that implements that interface and specify your class as one of the overloads of the Create method on the ServiceConsumerFactory. Alternatively, you can directly set the property ChannelCloseTriggerStrategy on the ServiceConsumer instance itself.

ChannelAdam WCF Library

Overview

The ChannelAdam WCF Library is an open source (Apache License 2.0) full .NET Framework code library that handles the subtleties of consuming services using Microsoft’s Windows Communication Foundation (WCF) library - including correctly performing the Close/Abort pattern and preventing memory and server connection leaks.

The issues faced by .NET developers are explained in depth in the article How To Call WCF Services Properly, where I researched and highlighted the typical problems with using WCF clients (such as memory and connection leaks), and concluded that the most correct solution for consuming WCF services would:

  • Perform the Close/Abort pattern without a race condition;
  • Handle the situation when the service operation throws exceptions;
  • Handle the situations when both the Close and Abort methods throw exceptions; and
  • Handle asynchronous exceptions such as the ThreadAbortException.

The ChannelAdam WCF Library is designed to make your life as a developer easier when consuming WCF services, by abstracting away the complexities of service channel state transitions and the odd behaviour of the Microsoft WCF libraries that do not comply with even their own design guidelines.

In doing this, the ChannelAdam WCF ServiceConsumer once and for all prevents memory and connection leaks, gives developers the ability to plug in their own strategies for handling different situations (such as logging exceptions), and provides a built-in mechanism to retry service operation calls when transient faults occur.

Documentation

Current Version

Old Versions

Exception Behaviour Strategies

ChannelAdam WCF Library — Version 1 Documentation

Exception Behaviour Strategies

Overview

As you know, the ServiceConsumer automatically handles the clean up of service channels. If an exception occurs at any point throughout the usage of the channel, the ServiceConsumer will catch it and close the channel if necessary. In some cases, for example, if an exception occurs while attempting to close the channel, the ServiceConsumer does not want to propagate the exception back to the calling code, as it is not the caller’s concern.

In order to be a good citizen and prevent the ServiceConsumer from swallowing exceptions, the concept of the “Exception Behaviour Strategy” was born. As I expect could be the case, many organisations for instance will want to use a logging library and write out the exceptions to a log file somewhere.

You Are In Control

Whenever an exception occurs throughout the lifetime of the ServiceConsumer, a corresponding method on the configured Exception Behaviour Strategy class is invoked, thus allowing the exception to be logged and statistics recorded for an organisation’s support purposes.

Each Exception Behaviour Strategy class implements the interface IServiceConsumerExceptionBehaviourStrategy. This interface contains the following methods:

  • PerformFaultExceptionBehaviour - the behaviour to perform when a fault exception occurs while the service operation is called;
  • PerformCommunicationExceptionBehaviour - the behaviour to perform when a communication exception occurs while the service operation is called;
  • PerformTimeoutExceptionBehaviour - the behaviour to perform when a timeout exception occurs while the service operation is called;
  • PerformUnexpectedExceptionBehaviour - the behaviour to perform when an unexpected exception occurs while the service operation is called;
  • PerformCloseCommunicationExceptionBehaviour - the behaviour to perform when a communication exception occurs during a close;
  • PerformCloseTimeoutExceptionBehaviour - the behaviour to perform when a timeout exception occurs during a close;
  • PerformCloseUnexpectedExceptionBehaviour - the behaviour to perform when an unexpected exception occurs during a close;
  • PerformAbortExceptionBehaviour - the behaviour to perform when an exception occurs during an abort; and
  • PerformDestructorExceptionBehaviour - the behaviour to perform when an exception occurs during a destructor/Finalize method.

Out-Of-The-Box Exception Behaviour Strategies

The ChannelAdam.Wcf Library comes with the following Exception Behaviour Strategies:

  • NullServiceConsumerExceptionBehaviourStrategy - which does not write out any exception details anywhere;
  • StandardOutServiceConsumerExceptionBehaviourStrategy - which writes out all exceptions to the Standard Error stream; and
  • StandardErrorServiceConsumerExceptionBehaviourStrategy - which writes out all exceptions to the Standard Out stream.

By default, the ServiceConsumerFactory configures each ServiceConsumer with the StandardErrorServiceConsumerExceptionBehaviourStrategy.

Use Your Own

To use your own strategy, simply create a class that implements the interface IServiceConsumerExceptionBehaviourStrategy.

There are three ways to change the Exception Behaviour Strategy that is assigned when you create a ServiceConsumer:

  1. Specify a different Exception Behaviour Strategy on one of the overloads of the Create method on the ServiceConsumerFactory;
  2. Directly set the property ChannelCloseTriggerStrategy on the ServiceConsumer instance itself; or
  3. Change the default - in some bootstrap code, set the static property ServiceConsumerFactory.DefaultExceptionBehaviourStrategy to your desired strategy.

From then on, that strategy will be used by default for all created ServiceConsumer classes.

Exception Behaviour Strategies

ChannelAdam WCF Library — Version 2 Documentation

Exception Behaviour Strategies

Overview

As you know, the ServiceConsumer automatically handles the clean up of service channels. If an exception occurs at any point throughout the usage of the channel, the ServiceConsumer will catch it and close the channel if necessary. In some cases, for example, if an exception occurs while attempting to close the channel, the ServiceConsumer does not want to propagate the exception back to the calling code, as it is not the caller’s concern.

In order to be a good citizen and prevent the ServiceConsumer from swallowing exceptions, the concept of the “Exception Behaviour Strategy” was born. As I expect could be the case, many organisations for instance will want to use a logging library and write out the exceptions to a log file somewhere.

You Are In Control

Whenever an exception occurs throughout the lifetime of the ServiceConsumer, a corresponding method on the configured Exception Behaviour Strategy class is invoked, thus allowing the exception to be logged and statistics recorded for an organisation’s support purposes.

Each Exception Behaviour Strategy class implements the interface IServiceConsumerExceptionBehaviourStrategy. This interface contains the following methods:

  • PerformFaultExceptionBehaviour - the behaviour to perform when a fault exception occurs while the service operation is called;
  • PerformCommunicationExceptionBehaviour - the behaviour to perform when a communication exception occurs while the service operation is called;
  • PerformTimeoutExceptionBehaviour - the behaviour to perform when a timeout exception occurs while the service operation is called;
  • PerformUnexpectedExceptionBehaviour - the behaviour to perform when an unexpected exception occurs while the service operation is called;
  • PerformCloseCommunicationExceptionBehaviour - the behaviour to perform when a communication exception occurs during a close;
  • PerformCloseTimeoutExceptionBehaviour - the behaviour to perform when a timeout exception occurs during a close;
  • PerformCloseUnexpectedExceptionBehaviour - the behaviour to perform when an unexpected exception occurs during a close;
  • PerformAbortExceptionBehaviour - the behaviour to perform when an exception occurs during an abort;
  • PerformDestructorExceptionBehaviour - the behaviour to perform when an exception occurs during a destructor/Finalize method; and
  • PerformRetryPolicyAttemptExceptionBehaviour - the behaviour to perform when a retry policy is in use and an exception has occurred. This method also provides you with the count of the current retry attempt.

Out-Of-The-Box Exception Behaviour Strategies

The ChannelAdam.Wcf Library comes with the following Exception Behaviour Strategies:

  • NullServiceConsumerExceptionBehaviourStrategy - which does not write out any exception details anywhere;
  • StandardOutServiceConsumerExceptionBehaviourStrategy - which writes out all exceptions to the Standard Error stream; and
  • StandardErrorServiceConsumerExceptionBehaviourStrategy - which writes out all exceptions to the Standard Out stream.

By default, the ServiceConsumerFactory configures each ServiceConsumer with the StandardErrorServiceConsumerExceptionBehaviourStrategy.

Use Your Own

To use your own strategy, simply create a class that implements the interface IServiceConsumerExceptionBehaviourStrategy.

There are three ways to change the Exception Behaviour Strategy that is assigned when you create a ServiceConsumer:

  1. Specify a different Exception Behaviour Strategy on one of the overloads of the Create method on the ServiceConsumerFactory;
  2. Directly set the property ChannelCloseTriggerStrategy on the ServiceConsumer instance itself; or
  3. Change the default - in some bootstrap code, set the static property ServiceConsumerFactory.DefaultExceptionBehaviourStrategy to your desired strategy.

From then on, that strategy will be used by default for all created ServiceConsumer classes.