Using IDisposable on WCF Proxies (or any ICommunicationObject implementation)

by casperOne 5. December 2009 15:27

When you add a service reference in Visual Studio (or use the ServiceModel Metadata Utility Tool (Svcutil.exe)) it generates a proxy for you which derives from the ClientBase<TChannel> class.  This class implements the IDisposable interface, the implementation of which is basically a call to the Close method on the ICommunicationObject implementation.

The problem is that when calling Close if an exception is thrown because of a problem with the channel (in the form of a CommunicationException) or because of a timeout (in the form of a TimeoutException), or any other exception, the ClientBase<TChannel> isn’t properly disposed of.

Because of this, Microsoft has a recommended pattern for the closing of implementations of ICommunicationObject:

try
{
    ...
    client.Close();
}
catch (CommunicationException e)
{
    ...
    client.Abort();
}
catch (TimeoutException e)
{
    ...
    client.Abort();
}
catch (Exception e)
{
    ...
    client.Abort();
    throw;
}

(taken from the section of the MSDN documentation titled “Avoiding Problems with the Using Statement”, located at http://msdn.microsoft.com/en-us/library/aa355056.aspx)

Obviously, this prevents one from using the using statement to easily manage their proxy instance and can get pretty tedious if you are using proxies in multiple areas of your code.  It would be ideal to encapsulate this to some degree.

To that end, I present the CommunicationObjectExtensions class:

//////////////////////////////////////////////////
///
/// Licensed under Creative Commons Attribution 3.0 
/// United States License:
/// 
/// http://creativecommons.org/licenses/by/3.0/us/
/// 
/// This specified that you must attribute the work in the 
/// manner specified by the author or licensor
/// (but not in any way that suggests that they endorse you
/// or your use of the work).
/// 
/// To that end, a simple acknowledgement of my
/// (Nicholas Paldino) creation of the original
/// work will suffice.
/// 
//////////////////////////////////////////////////

// The shorthand for types in namespaces that are used in this file.
using System;
using System.Diagnostics;
using System.ServiceModel;

// The namespace that types in this file are a part of.
namespace Casper.ServiceModel.Extensions
{
    //////////////////////////////////////////////////
    ///
    /// <author>Nicholas Paldino</author>
    /// <created>12/5/2009 2:21:11 PM</created>
    /// <summary>Provides extension methods for <see cref="ICommunicationObject"/>
    /// implementations.</summary>
    ///
    //////////////////////////////////////////////////
    public static class CommunicationObjectExtensions
    {
        //////////////////////////////////////////////////
        ///
        /// <author>Nicholas Paldino</author>
        /// <created>12/5/2009 2:21:11 PM</created>
        /// <summary>A structure which will take a <see cref="ICommunicationObject"/>
        /// instance and return an <see cref="IDisposable"/> implementation
        /// which will implement the close/abort pattern outlined here:
        /// http://msdn.microsoft.com/en-us/library/aa355056.aspx.</summary>
        /// <remarks>This structure enables the use of WCF clients (or any
        /// <see cref="ICommunicationObject"/> implementation really)
        /// in using statements.</remarks>
        ///
        //////////////////////////////////////////////////
        private struct DisposableCommunicationObjectToken : IDisposable
        {
            /// <summary>The <see cref="ICommunicationObject"/> that is to be closed/aborted
            /// of in the call to <see cref="Dispose"/>.</summary>
            private readonly ICommunicationObject client;

            //////////////////////////////////////////////////
            ///
            /// <author>Nicholas Paldino</author>
            /// <created>12/5/2009 2:21:11 PM</created>
            /// <summary>Creates an instance of the 
            /// <see cref="DisposableCommunicationObjectToken"/>.</summary>
            /// <param name="obj">The <see cref="ICommunicationObject"/>
            /// to apply the pattern to.</param>
            ///
            //////////////////////////////////////////////////
            internal DisposableCommunicationObjectToken(ICommunicationObject obj)
            {
                // The obj is not null.
                Debug.Assert(obj != null);

                // Store the obj.
                this.client = obj;
            }

            //////////////////////////////////////////////////
            ///
            /// <author>Nicholas Paldino</author>
            /// <created>12/5/2009 2:21:11 PM</created>
            /// <summary>Called when the instance is disposed.</summary>
            ///
            //////////////////////////////////////////////////
            public void Dispose()
            {
                // If the obj is null, throw an exception.
                if (client == null)
                {
                    // Throw the exception.
                    throw new InvalidOperationException(
                        "The DisposableCommunicationObjectToken structure " +
                        "was created with the default constructor.");
                }

                // Try to close.
                try
                {
                    // Close.
                    client.Close();
                }
                catch (CommunicationException)
                {
                    // Abort if there is a communication exception.
                    client.Abort();
                }
                catch (TimeoutException)
                {
                    // Abort if there is a timeout exception.
                    client.Abort();
                }
                catch (Exception)
                {
                    // Abort in the face of any other exception.
                    client.Abort();

                    // Rethrow.
                    throw;
                }
            }
        }

        //////////////////////////////////////////////////
        ///
        /// <author>Nicholas Paldino</author>
        /// <created>12/5/2009 2:21:11 PM</created>
        /// <summary>Takes an <see cref="ICommunicationObject"/>
        /// implementation and returns an <see cref="IDisposable"/>
        /// implementation which can be used in a using statement, while
        /// executing the proper cleanup procedure outlined here:
        /// http://msdn.microsoft.com/en-us/library/aa355056.aspx.</summary>
        /// <param name="obj">The <see cref="ICommunicationObject"/>
        /// implementation.</param>
        /// <returns>An <see cref="IDisposable"/> implementation which will
        /// close the <see cref="ICommunicationObject"/> implementation
        /// in <paramref name="obj"/> properly.</returns>
        ///
        //////////////////////////////////////////////////
        //
        // TODO: Add parameters here and on DisposableCommunicationObjectToken
        // TODO: of type Predicate<CommunicationException> and
        // TODO: Preidcate<TimeoutException> which will allow for
        // TODO: injected code which will allow for cleanup that the user specifies.
        // TODO: The return value from the predicate would determine if the exception
        // TODO: is rethrown.
        public static IDisposable AsDisposable(this ICommunicationObject obj)
        {
            // Return a new instance of the DisposableCommunicationObjectToken.
            return new DisposableCommunicationObjectToken(obj);
        }
    }
}
Given an implementation of ICommunicationObject (or anything that implements it), you can use it in a using statement like so:
// Note that you don't have
// to declare the client
// parameter as ICommunicationObject
ICommunicationObject client = <new client code>;

using (client.AsDisposable())
{
    // Your code here.
}

Note that there are suggestions on how to improve the functionality, but the boilerplate code is there to be expanded upon.

This code is licensed under the Creative Commons Attribution 3.0 United States License.

Tags: , , , , ,

programming

About the author

I'm just another guy who is trying to harness the power of the internet to feed his ego by projecting myself onto the masses.

Definitely consider this site a work-in-progress until further notice.