次の方法で共有


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

Comments

  • Anonymous
    December 15, 2008
    Hmm...If you were not worried about creating from the wsdl then you could instead use generics for this. All you need is a generic wrapper around ClientBase<T> that exposes the Channel. WCFClient<T> : ClientBase<T> { T Channel { get{ return base.xyz} } } Now, that is generic enough that when you find a service contract in the dll, you can create a client specifically for it (and even do your own overrides into the constructor if you want).

  • Anonymous
    December 15, 2008
    The 'case 0:' statement the following section of code is unreachable.... // Load the method parameters on the stack for (int x = 0; x < methodParmeterTypes.Length; x++) { switch (x + 1) {  case 0:   ilGen.Emit(OpCodes.Ldarg_0);   break;

  • Anonymous
    December 16, 2008
    Whatis about creating WCF client based on WSDL?

  • Anonymous
    December 28, 2008
    Hi Sorry it took my so long to respond.  I was really sick and then we had a really bad snow storm here and I did not have time to check the blog. Tim W you are correct.  ClientBase<TChannel> is abstract and Channel property is protected.  You can create a class that inherits from ClientBase<T> and exposes the Channel property.  I like playing with reflection emit though so I went the long way :P Phil Bolduc sorry I should have noticed that.  I updated the code.  Good eye ;) Evgeny there is an exe called SvcUtilthat comes with .net 3.0 sdk.  This program uses CodeDom to generate service reference code from wsdl.  I was going to look at this code in reflector and make something similar that will use reflection emit instead to generate the contract interface and any complex types for a service.  I have not got around to this yet. Thanks Ziad

  • Anonymous
    July 31, 2009
    I am interested in seeing the whole application. Have you published it somewhere?

  • Anonymous
    August 17, 2010
    Wouldn't it be all simpler to use the ChannelFactory class? What would be the difference for your original need?