I want to follow-up on yesterday’s post about the proxy generator with more details about how the code was created and what it does. First, I want to recap the problem I was trying to solve. With .NET web services and WCF, you use a client to connect to a server that implements an interface defining a service contract over a communications channel. As an example, let’s say your service contract does some simple math:
[ServiceContract] public interface IMathContract : IMathContractBase { [OperationContract] int Add( int a, int b ); [OperationContract] int Subtract( int a, int b ); [OperationContract] int Multiply( int a, int b ); [OperationContract] int Divide( int a, int b ); }
Pretty simple, right? The server’s implementation of the contract is pretty obvious:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)] public class MathService : IMathContract { public int Add( int a, int b ) { return a + b; } public int Subtract( int a, int b ) { return a - b; } public int Multiply( int a, int b ) { return a * b; } public int Divide( int a, int b ) { if ( b == 0 ) throw new DivideByZeroException(); return a / b; } }
The only thing to note here is that if we call Divide with 0 as the denominator, we’re going to get a DivideByZeroException.
Our client is going to connect to the server via a communications channel. Assuming we’ve set up the server’s address and whatnot in the configuration file as “MathEndpoint”, the code to do some basic math looks like this:
ChannelFactory<IMathContract> factory = new ChannelFactory<IMathContract>( "MathEndpoint" ); IMathContract channel = factory.CreateChannel(); Console.WriteLine( "1 + 2 = " + channel.Add( 1, 2 ) ); Console.WriteLine( "4 - 1 = " + channel.Subtract( 4, 1 ) ); Console.WriteLine( "6 / 3 = " + channel.Divide( 6, 3 ) ); Console.WriteLine( "5 * 2 = " + channel.Multiply( 5, 2 ) );
Let’s say we’re worried about that DivideByZero exception so we guard against it.
ChannelFactory<IMathContract> factory = new ChannelFactory<IMathContract>( "MathEndpoint" ); IMathContract channel = factory.CreateChannel(); Console.WriteLine( "1 + 2 = " + channel.Add( 1, 2 ) ); Console.WriteLine( "4 - 1 = " + channel.Subtract( 4, 1 ) ); try { Console.WriteLine( "6 / 3 = " + channel.Divide( 6, 0 ) ); // DivideByZeroException } catch ( Exception ) { } Console.WriteLine( "5 * 2 = " + channel.Multiply( 5, 2 ) ); // CommunicationObjectFaultedException!
What happens? Well, even though we caught the exception, having any exception at all put our channel in the Faulted stage and so hen we call the next method, we get a CommunicationObjectFaultedException. Specifically, we get the message:
System.ServiceModel.CommunicationObjectFaultedException: The communication object, System.ServiceModel.Channels.ServiceChannel, cannot be used for communication because it is in the Faulted state.
If we want to keep using our service we have to abort the channel and create another. The fact is, an exception could happen at any time. Here it’s the result of bad input but it could be network timeouts or anything else. We don’t want to wrap every call to our service in a try…catch block capable of resetting our channel. Instead, we want to create a fault-tolerant wrapper or proxy around it.
Here’s a simplified proxy:
public class FaultSafeProxy : IMathContract { private readonly ChannelFactory<IMathContract> _factory; private IMathContract _channel; public FaultSafeProxy( string endpoint ) { this._factory = new ChannelFactory<IMathContract>( endpoint ); } #region Channel Management private void Abort() { if ( null == this._channel ) return; IServiceChannel serviceChannel = (IServiceChannel)this._channel; serviceChannel.Abort(); this._channel = null; } private void Close() { if ( null == this._channel ) return; IServiceChannel serviceChannel = (IServiceChannel)this._channel; serviceChannel.Close(); this._channel = null; } private IMathContract GetChannel() { if ( null == this._channel ) { this._channel = this._factory.CreateChannel(); } return this._channel; } #endregion #region IMathContract public int Add( int a, int b ) { try { return this.GetChannel().Add( a, b ); } catch ( Exception ) { this.Abort(); throw; } } // … #endregion }
Not bad, right? If we use our proxy we can call our methods safely:
FaultSafeProxy proxy = new FaultSafeProxy( "MathEndpoint" ); Console.WriteLine( "1 + 2 = " + proxy.Add( 1, 2 ) ); Console.WriteLine( "4 - 1 = " + proxy.Subtract( 4, 1 ) ); try { Console.WriteLine( "6 / 3 = " + proxy.Divide( 6, 0 ) ); // DivideByZeroException } catch ( Exception ) { } Console.WriteLine( "5 * 2 = " + proxy.Multiply( 5, 2 ) ); // Works!
So that all being said, our project might have dozens of interfaces and each interface has dozens of methods. How do we want to generate our proxies? We have a few options:
1. By Hand
We could hand-code a proxy for each interface in our project. This has a lot of drawbacks:
- It’s tedious
- Every time we add an interface we have to code another proxy
- Every time we add, remove, or change a method we have to change our proxy.
- If we ever wanted to change how our proxies worked (to add logging, say), we’d have to go through each class and update it by hand.
It’s pretty obvious that you don’t want to do this if you can avoid it.
2. Statically Generated Code
We could write an EXE that uses reflection to pull our service contracts out of our DLL and generate proxy classes for them. So that we don’t have to know the proxy types ahead of time we could also generate some sort of proxy factory that, given an interface type, could return the correct proxy type. This isn’t bad, but it has some drawbacks too:
- We would have to run this tool every time we added, removed, or changed a method or interface.
- Our project would have a lot of generated proxy classes that developers would have to know to ignore.
There’s still one better way, and the topic of this discussion:
3. Dynamically Generated Code
It would be best if we could just say “I want a proxy for IMyServiceContract” and have one handed back no matter what our service contract is. No extra classes. Nothing to maintain. Just a perfect proxy every time. That’s what we’re going to do here.
CIL
It’s pretty well known that .NET languages get compiled down to CIL, the Common Intermediate Language (formerly known as MSIL, or the Microsoft Intermediate Language). It’s less well known that you can actually generate CIL at run time creating whole assemblies in memory to be used just while your code is executing. So how do we do it? The first thing to do is to get an idea of the CIL that you want to generate. First we create a prototype of the class that we want to dynamically create. Our hand-coded proxy, above is a good example. When I compiled that code I got a DLL. Using the IL disassembler tool (which is part of the Microsoft Windows SDK and on my computer was found at C:\Program Files (x86)\Microsoft SDKs\Windows\v8.1A\bin\NETFX 4.5.1 Tools\ildasm.exe) we can have a look.
You’re going to want to look at the CIL for the class, constructor, and all the methods in your prototype class. After that, you’re going to start building dynamic code.
Step 1: Create an assembly builder
// Create our dynamic assembly name and version AssemblyName assemblyName = new AssemblyName( ASSEMBLY_NAME ); assemblyName.Version = new Version( 1, 0, 0, 0 ); // Create the assembly builder, specifying whether we'll want to only run the assembly or run and save it AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly( assemblyName, // AssemblyBuilderAccess.Run AssemblyBuilderAccess.RunAndSave );
Note “Run” vs. “RunAndSave“. If you use “RunAndSave” your assembly will be created on disk, which is useful for debugging. In your final code, you’ll want to just use “Run“.
Step 2: Create a module builder
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule( assemblyBuilder.GetName().Name, assemblyBuilder.GetName().Name + ".mod" // Note: A module file name is necessary in order to save );
Step 3: Create a type builder
// Define our custom type, which implements the given interface and IDisposable TypeBuilder typeBuilder = moduleBuilder.DefineType( GenerateTypeName(), TypeAttributes.Class, typeof ( Object ), interfacesToImplement // type[] );
Here we specify our type name, the fact that it’s a class descended from Object, and the interfaces we want to implement.
Step 4: Add members
Now we just want to go through and add our members as we would to create the CIL we saw in the disassembler. Let’s look at our GetChannel method. In C#, it looks like this:
private IMathContract GetChannel() { if ( null == this._channel ) { this._channel = this._factory.CreateChannel(); } return this._channel; }]
In IL, it looks like this:
.method private hidebysig instance class TestProxyGenerator.Prototype.IMathContract GetChannel() cil managed { // Code size 47 (0x2f) .maxstack 2 .locals init ([0] class TestProxyGenerator.Prototype.IMathContract CS$1$0000, [1] bool CS$4$0001) IL_0000: nop IL_0001: ldnull IL_0002: ldarg.0 IL_0003: ldfld class TestProxyGenerator.Prototype.IMathContract TestProxyGenerator.Prototype.FaultSafeProxy::_channel IL_0008: ceq IL_000a: ldc.i4.0 IL_000b: ceq IL_000d: stloc.1 IL_000e: ldloc.1 IL_000f: brtrue.s IL_0024 IL_0011: nop IL_0012: ldarg.0 IL_0013: ldarg.0 IL_0014: ldfld class [System.ServiceModel]System.ServiceModel.ChannelFactory`1<class TestProxyGenerator.Prototype.IMathContract> TestProxyGenerator.Prototype.FaultSafeProxy::_factory IL_0019: callvirt instance !0 class [System.ServiceModel]System.ServiceModel.ChannelFactory`1<class TestProxyGenerator.Prototype.IMathContract>::CreateChannel() IL_001e: stfld class TestProxyGenerator.Prototype.IMathContract TestProxyGenerator.Prototype.FaultSafeProxy::_channel IL_0023: nop IL_0024: ldarg.0 IL_0025: ldfld class TestProxyGenerator.Prototype.IMathContract TestProxyGenerator.Prototype.FaultSafeProxy::_channel IL_002a: stloc.0 IL_002b: br.s IL_002d IL_002d: ldloc.0 IL_002e: ret } // end of method FaultSafeProxy::GetChannel
So how do we create that? Let’s go bit by bit. Start with a MethodBuilder and ILGenerator.
MethodBuilder methodBuilder = typeBuilder.DefineMethod( "GetChannel", MethodAttributes.Private | MethodAttributes.HideBySig, CallingConventions.Standard, typeof( T ), new Type[0] ); ILGenerator il = methodBuilder.GetILGenerator(); // Declare local variables il.DeclareLocal( typeof( T ) ); il.DeclareLocal( typeof( bool ) );
All the IL_00XX values are like GOTO labels and we can define them up front.
// Define some labels ahead of time Label label0024 = il.DefineLabel(); Label label002D = il.DefineLabel();
Use reflection to get some of the methods outside of our class, in this case ChannelFactory’s CreateChannel method.
// Get some methods ahead of time MethodInfo channelFactoryCreateChannelMethodInfo = typeof( ChannelFactory<T> ).GetMethod( "CreateChannel", new Type[0] );
Define our locals. This corresponds to
.locals init ([0] class TestProxyGenerator.Prototype.IMathContract CS$1$0000, [1] bool CS$4$0001)
il.DeclareLocal( typeof( T ) ); il.DeclareLocal( typeof( bool ) );
Then we emit the code that checks if our channel exists
IL_0000: nop IL_0001: ldnull IL_0002: ldarg.0 IL_0003: ldfld class TestProxyGenerator.Prototype.IMathContract TestProxyGenerator.Prototype.FaultSafeProxy::_channel IL_0008: ceq IL_000a: ldc.i4.0 IL_000b: ceq IL_000d: stloc.1 IL_000e: ldloc.1 IL_000f: brtrue.s IL_0024
il.Emit( OpCodes.Nop ); il.Emit( OpCodes.Ldnull ); il.Emit( OpCodes.Ldarg_0 ); il.Emit( OpCodes.Ldfld, this._channelField ); il.Emit( OpCodes.Ceq ); il.Emit( OpCodes.Ldc_I4_0 ); il.Emit( OpCodes.Ceq ); il.Emit( OpCodes.Stloc_1 ); il.Emit( OpCodes.Ldloc_1 ); il.Emit( OpCodes.Brtrue_S, label0024 );
If it doesn’t, we call the factory method to create it:
IL_0011: nop IL_0012: ldarg.0 IL_0013: ldarg.0 IL_0014: ldfld class [System.ServiceModel]System.ServiceModel.ChannelFactory`1<class TestProxyGenerator.Prototype.IMathContract> TestProxyGenerator.Prototype.FaultSafeProxy::_factory IL_0019: callvirt instance !0 class [System.ServiceModel]System.ServiceModel.ChannelFactory`1<class TestProxyGenerator.Prototype.IMathContract>::CreateChannel() IL_001e: stfld class TestProxyGenerator.Prototype.IMathContract TestProxyGenerator.Prototype.FaultSafeProxy::_channel IL_0024: ldarg.0 IL_0025: ldfld class TestProxyGenerator.Prototype.IMathContract TestProxyGenerator.Prototype.FaultSafeProxy::_channel IL_002a: stloc.0 IL_002b: br.s IL_002d
il.Emit( OpCodes.Nop ); il.Emit( OpCodes.Ldarg_0 ); il.Emit( OpCodes.Ldarg_0 ); il.Emit( OpCodes.Ldfld, this._factoryField ); il.Emit( OpCodes.Callvirt, channelFactoryCreateChannelMethodInfo ); il.Emit( OpCodes.Stfld, this._channelField ); il.Emit( OpCodes.Nop ); il.MarkLabel( label0024 ); il.Emit( OpCodes.Ldarg_0 ); il.Emit( OpCodes.Ldfld, this._channelField ); il.Emit( OpCodes.Stloc_0 ); il.Emit( OpCodes.Br_S, label002D );
And finally, we return the channel:
IL_002b: br.s IL_002d IL_002d: ldloc.0 IL_002e: ret
il.MarkLabel( label002D );il.Emit( OpCodes.Ldloc_0 );il.Emit( OpCodes.Ret );
I’m not going to lie. This can be a real pain in the ass to go through and debug. You’ll have to do this for each method you want to generate. Because you’re doing this dynamically, you’ll find yourself using reflection to iterate through the methods that you want to create.If you want to save your assembly to debug your type, this is the time to do it:
assemblyBuilder.Save( assemblyBuilder.GetName().Name + ".dll" );
Step 5: Create the Type and Object
Once you’re done with the TypeBuilder, you’re going to want to create an instance of your new type and that’s easy enough to do:
Type generatedType = typeBuilder.CreateType(); object instance = Activator.CreateInstance( generatedType, endpoint, userName, password );
And that’s it. You’ve got a dynamically generated type. If you made your type implement an interface, like a service contract in our example, you can just cast it to the interface and use the methods on it.