Create dynamic WCF Clients without any configuration/service references

The other day I wanted to do some simple perf tests against a bunch of different wcf services that I am working on.  I wrote a little test tool to do this but I had some problems with my test tool.  My test application was very tightly coupled to the services.  For each service I had to add a service reference.  The ide auto generates some code for each service reference so each time something changed I had to update the service reference in the  ide.  A recompile for a service update :(  On top of that the services are dependent on a ton of configuration setting in the app config. 

I wanted the ability to test any service without any configuration or dependencies on ide generated code.  The test application had to be generic enough to work with any wcf service.  I wanted the application to work the following way : user picks an assembly, we scan the assembly for interfaces that have a service contract attribute, look for types that implement the contract interface, give the user a selection of the services found, user scripts up service calls to single or multiple services, clicks run, we spawn the services on a random endpoint on the localhost, create the client, and start hammering the service.  No configuration or ide generated code required is the goal.

I was able to get everything work in a couple hours but the code is little bit sloppy :(  I am not going to share the entire application but only the code that creates the wcf client.  Maybe later if I have a chance to clean up the rest of the code I will post it here too ;)

In my application since the service and client existed in the same process we already have the service contract type for the client.  This is not exactly dynamic.  Next time we will look at generating a wcf client based off a wsdl.  In this example though the contract must already be know.

Where should I start?  I started by adding a service reference the normal way and observing what the ide does.  Bunch of stuff in the config and some auto generated code.  The ide creates a class called ClientBase<> which takes a generic type parameter called TChannel.  The parameter needs to be an interface that has some service method attributes.  ClientBase<T> implements the TChannel interface and wraps calls to a TChannel Channel property.  We can easily generate this class using reflection.emit. 

To do this I made a little utility class with a static method that takes the contract type, binding, and endpoint as parameters and returns an object that can be used to communicate with the service.  The signature for our helper method is below.

        public static ICommunicationObject GetClient(Type contractType, Binding binding,
                EndpointAddress address)
       

Lets look at some of the code we will use to generate the client.  We are going to create a new type that inherits from ClientBase<TChannel> with TChannel being the contractType that was passed in.  We will loop through the methods in the interface and add them to our new type.  The new methods will simply wrap calls made to the Channel property.  The ChannelBase has several constructors but we will only be supporting the constructor that takes a binding and endpoint address.  This is all we really need.  The code for generating ClientBase<TChannel> in below.

ILGenerator ilGen = null;

            // Create the type and add the interface.
            TypeBuilder typeBuilder = builder.DefineType("TestClient_" + contractType.Name, TypeAttributes.Class | TypeAttributes.Public, clientBaseType);
            typeBuilder.AddInterfaceImplementation(contractType);

            ConstructorInfo constructorInfo = clientBaseType.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { typeof(Binding), typeof(EndpointAddress) }, null);
            ConstructorBuilder constructorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, new Type[] { typeof(Binding), typeof(EndpointAddress) });
            ilGen = constructorBuilder.GetILGenerator();
            ilGen.Emit(OpCodes.Ldarg_0);
            ilGen.Emit(OpCodes.Ldarg_1);
            ilGen.Emit(OpCodes.Ldarg_2);
            ilGen.Emit(OpCodes.Callvirt, constructorInfo);
            ilGen.Emit(OpCodes.Ret);

            // Get the interface methods
            MethodInfo[] interfaceMethods = contractType.GetMethods();
            MethodInfo channelGetMethod = clientBaseType.GetProperty("Channel", BindingFlags.Instance | BindingFlags.NonPublic).GetGetMethod(true);
            MethodBuilder methodBuilder = null;
            Type[] methodParmeterTypes = null;

 

            // Loop through all the methods.
            for (int i = 0; i < interfaceMethods.Length; i++)
            {
                // Get the types
                methodParmeterTypes = Array.ConvertAll<ParameterInfo, Type>(interfaceMethods[i].GetParameters(), p => p.ParameterType);

                // Create method in our wrapper type.
                methodBuilder = typeBuilder.DefineMethod(
                    interfaceMethods[i].Name,
                    MethodAttributes.Public | MethodAttributes.Virtual,
                    CallingConventions.HasThis,
                    interfaceMethods[i].ReturnType,
                    methodParmeterTypes
                        );
                ilGen = methodBuilder.GetILGenerator();

                // Push the channel property value onto the stack
                ilGen.Emit(OpCodes.Ldarg_0);
                ilGen.Emit(OpCodes.Callvirt, channelGetMethod);

                // Load the method parameters on the stack
                for (int x = 0; x < methodParmeterTypes.Length; x++)
                {
                    switch (x + 1)
                    {
                        case 1:
                            ilGen.Emit(OpCodes.Ldarg_1);
                            break;
                        case 2:
                            ilGen.Emit(OpCodes.Ldarg_2);
                            break;
                        case 3:
                            ilGen.Emit(OpCodes.Ldarg_3);
                            break;
                        default:
                            ilGen.Emit(OpCodes.Ldarg_S, x);
                            break;
                    }
                }

                // Call the method.
                ilGen.EmitCall(OpCodes.Callvirt, interfaceMethods[i], null);
                // Return the value
                ilGen.Emit(OpCodes.Ret);

                // Mark the method.
                typeBuilder.DefineMethodOverride(methodBuilder, interfaceMethods[i]);

            }

 

To speed things up we will use some of the techniques we talked about in previous posts and create a factory object to create these clients.  Our factory interface is below.  We will gen some il for the factories and store them in a hash table using the contract type as the hash key.

    public interface IClientFactory
    {
        object Create(Binding binding, EndpointAddress address);
    }

This il for our factory is very simple.  We create a new type that implements IClientFactory and have it return a new instance of the new ClientBase<> type we just defined.

 

            // Create the type and add the interface
            TypeBuilder typeBuilder = modBuilder.DefineType(wrapperType.Name + "Factory", TypeAttributes.Public | TypeAttributes.Class);
            typeBuilder.AddInterfaceImplementation(typeof(IClientFactory));

            // Create the method.
            MethodBuilder methodBuilder =
            typeBuilder.DefineMethod("Create", MethodAttributes.Public | MethodAttributes.Virtual,
                CallingConventions.HasThis, typeof(object), new Type[] { typeof(Binding), typeof(EndpointAddress) });
           
            // Get the constructor for our new wrapper type
            ConstructorInfo info = wrapperType.GetConstructor(new Type[] { typeof(Binding), typeof(EndpointAddress) });

            // Gen the il
            ILGenerator gen = methodBuilder.GetILGenerator();
            gen.Emit(OpCodes.Ldarg_1);
            gen.Emit(OpCodes.Ldarg_2);
            gen.Emit(OpCodes.Newobj, info);
            gen.Emit(OpCodes.Ret);

            // Mark the method.
            MethodInfo createMethod = typeof(IClientFactory).GetMethod("Create");
            typeBuilder.DefineMethodOverride(methodBuilder, createMethod);

            // Return the type.
            return typeBuilder.CreateType();

The code for WCF client factory is attached.  I am sorry I did not have a chance to make a sample application this time around.  The code should be self explanatory though.  Next time we will look at generating on WCF client off a wsdl.  Peace :)

Share/Save/Bookmark

ClientFactory.cs