Tallan's Technology Blog

Tallan's Top Technologists Share Their Thoughts on Today's Technology Challenges

Recover from a WCF Service Fault, Part 2 (Generic ServiceClientFactory Class)

After finding the simple solution for handling WCF Service Faults in the original post, I figured it should be relatively trivial to find a generic solution to this problem if you’re using similar WCF clients in a project and want to reset ALL of them on a channel fault.  I developed a generic ServiceClientFactory class that will generate a WCF Service Client instance from a generic “GetClient” function and will automatically handle resetting of faulted channels.

Class Implementation Source Code:

/* Service Client Factory
 * Author: Michael Gerety, Senior Consultant, Tallan, Inc.
 * Description: A generic service client factory that automatically
 *              resets faulted channels for WCF services that have
 *              endpoints and behaviors defined in web/app.config files.
 */

using System;
using System.Collections.Generic;
using System.ServiceModel;
namespace Tallan
{
    /// <summary>
    /// Singleton factory class for WCF Services.
    /// Creates service clients based on interface type and automatically
    /// resets faulted channels.
    /// </summary>
    public class ServiceClientFactory
    {
        private readonly Dictionary<Type, object> factories;
        private static ServiceClientFactory instance;

        private ServiceClientFactory()
        {
            factories = new Dictionary<Type, object>();
        }

        /// <summary>
        /// Retrieves a service client for the interface specified in generic parameter.
        /// </summary>
        /// <typeparam name="T">Interface type to use for Service Client creation.</typeparam>
        /// <returns>Service client instance for specified interface.</returns>
        public T GetClient<T>()
        {
            var genericType = typeof(T);
            Type serviceClientType;
            if (genericType.IsInterface)
            {
                serviceClientType = GetClientType(genericType);

                if (serviceClientType == null)
                    return default(T);

                var client = Activator.CreateInstance(serviceClientType);
                if (!(client is ICommunicationObject))
                {
                    client = null;
                    return (T)client;

                }
                (client as ICommunicationObject).Faulted += Channel_Faulted<T>;

                if (!factories.ContainsKey(typeof(T)))
                {
                    var prop = serviceClientType.GetProperty("ChannelFactory");
                    var factory = prop.GetValue(client, null);
                    factories.Add(typeof(T), factory);
                }
                return (T)client;
            }
            return default(T);
        }


        #region Reflection Utilities
        private static Type GetClientType(Type type)
        {
            var assy = type.Assembly;
            var serviceModelAssy = typeof(ChannelFactory).Assembly;
            var clientBaseType = serviceModelAssy.GetType("System.ServiceModel.ClientBase`1").MakeGenericType(type);

            foreach (var classType in assy.GetTypes())
            {
                if (classType.IsClass && type.IsAssignableFrom(classType))
                {
                    if (classType.IsSubclassOf(clientBaseType))
                        return classType;
                }
            }

            return null;
        }

        #endregion

        /// <summary>
        /// Event handler for ClientBase.Faulted event.
        /// </summary>
        /// <typeparam name="T">Interface type of service</typeparam>
        /// <param name="sender">ClientBase instance</param>
        /// <param name="e">Event Args</param>
        private void Channel_Faulted<T>(object sender, EventArgs e)
        {
            ((ICommunicationObject)sender).Abort();
            var factory = (ChannelFactory<T>)factories[typeof(T)];
            factory.CreateChannel();
        }

        /// <summary>
        /// Returns the singleton instance of ServiceClientFactory.
        /// </summary>
        /// <returns>Singleton instance of ServiceClientFactory</returns>
        public static ServiceClientFactory GetFactory()
        {
            if (instance == null)
            {
                instance = new ServiceClientFactory();
            }
            return instance;
        }
    }
}

 

Sample Usage:

//Get instance of ServiceClientFactory
            var factory = ServiceClientFactory.GetFactory();
            var client = factory.GetClient<IAuthenticateService>();
            try
            {
                client.AuthenticateUser("joe", "bob");
            }
            catch (Exception)
            {
                //Handle Exception
            }

            //channel isn't in faulted state, you can re-execute.
            client.AuthenticateUser("joe", "bob1");

 

This was thrown together and proven to work for my purposes.  If anyone has any suggestions about how to improve this utility class, please feel free to comment or contact me with suggestions.

9 Comments. Leave new

Robert Sullivan
September 15, 2009 6:49 pm

Thanks for the above code, it is well written and easy to understand, and was exactly what I was looking for!

I do have one thought if I may…

I have a service client with multiple endpoints defined in web/app.config. Using the GetClient() method will cause an exception to be thrown because WCF can’t decide for me which endpoint to use. I edited the above method and added a new one as follows:

}

///
/// Retrieves a service client for the interface specified in generic parameter.
///
/// Interface type to use for Service Client creation.
/// Service client instance for specified interface.
public T GetClient()
{
// Passing null causes reflection to use the default constructor of the Service Client (This works if only one endpoint is defined for the service)
return GetClient(null);
}

///
/// Retrieves a service client for the interface specified in generic parameter.
///
/// Interface type to use for Service Client creation.
/// Object array of arguments to pass to the Service Client’s constuctor
/// Service client instance for specified interface.
/// The ‘ values must match the order and type of a constructor for the Service Client.
public T GetClient(params object[] constructorArgs)
{
var genericType = typeof(T);
Type serviceClientType;
if (genericType.IsInterface)
{
serviceClientType = GetClientType(genericType);

if (serviceClientType == null)
return default(T);

// Pass the supplied constructor arguments to the CreateInstance method
var client = Activator.CreateInstance(serviceClientType, constructorArgs);
if (!(client is ICommunicationObject))
{
client = null;
return (T)client;

}
(client as ICommunicationObject).Faulted += Channel_Faulted;

if (!factories.ContainsKey(typeof(T)))
{
var prop = serviceClientType.GetProperty(“ChannelFactory”);
var factory = prop.GetValue(client, null);
factories.Add(typeof(T), factory);
}
return (T)client;
}
return default(T);
}

For my personal situation, I only need to pass the endpointConfigurationName as a constructor parameter, but I think this implementation will allow for any of the service client’s constructors to be called.

I’ve tested it in a limited fashion, but wanted to see if you had any comments/concerns.

Once again thanks for pointing me in the right direction.

@Robert:
I’m glad the code was able to help you out.

I hadn’t thought about your issue as the services I was using this for are pretty simple, however your solution seems to work nicely. I may add something like this into the next iteration.

Thanks for submitting your additions to it!

Thanks you very much for this wonderfull piece of code. I was looking for a solution to this dreaded issue (faulted commmunication state) when i found this article. We are now adding some error reporting. If you are interessted in seeing the revised version, just let me kow and i’ll send it to you when we have it.

Kind regards,

Michel

Thanks for this code! I was creating something like this myself but you fixed it nicely!

Their is one situation i worry about:
-Channel is created
-Channel faults
-Event is fired to recover the channel
-Appication tries to use the channel (before its recreated)
-Channel is recreated

Is their a good way to get around this?

Secondly I cannot get this work with non-generated proxy’s.
They don’t implement ‘System.ServiceModel.ClientBase’.

Another thing I don’t understand:
private void Channel_Faulted(object sender, EventArgs e)
{
((ICommunicationObject)sender).Abort();
var factory = (ChannelFactory)factories[typeof(T)];
factory.CreateChannel();
}

If the factory creates a new channel like this, is the referenced channel refreshed? I don’t see how the new channel updates the faulted one.

Nice code. Thanks.

But what about DuplexClientBase? Does the following version match?
private static Type GetProxyType(Type type)
{
var assy = type.Assembly;
var serviceModelAssy = typeof(ChannelFactory).Assembly;
var clientBaseType = serviceModelAssy.GetType(“System.ServiceModel.ClientBase`1″).MakeGenericType(type);
var duplexClientBaseType = serviceModelAssy.GetType(“System.ServiceModel.DuplexClientBase`1″).MakeGenericType(type);

foreach (var classType in assy.GetTypes())
{
if (classType.IsClass && type.IsAssignableFrom(classType))
{
if (classType.IsSubclassOf(clientBaseType) || classType.IsSubclassOf(clientBaseType))
return classType;
}
}

return null;
}

if (classType.IsSubclassOf(clientBaseType) || classType.IsSubclassOf(duplexClientBaseType))

sorry :)

Hi. Thank you for the code.
Still, i am having a problem.
To generate a fault, i kill the app that is hosting the servicehost and launch it again.
When invoke the service using the proxy your code generated, i see it going to the Channel_Faulted method.
The proxy class tough, stays in a Closed state so when i invoke it again it fails.
Thank for any lights on this.
Telmo

You can avoid the problem altogether by calling the services in a slightly different way if you are currently using a “Using” statement to wrap you service calls. See: http://www.codeproject.com/Tips/197531/Do-not-use-using-for-WCF-Clients for details

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

\\\