Creating a dynamic proxy generator – Part 1 – Creating the Assembly builder, Module builder and caching mechanism
For the latest code go to http://rapidioc.codeplex.com/
Before getting too involved in generating the proxy, I thought it would be worth while going through the intended design, this is important as the next step is to start creating the constructors for the proxy.
Each proxy derives from a specified type
The proxy has a corresponding constructor for each of the base type constructors
The proxy has overrides for all methods and properties marked as Virtual on the base type
For each overridden method, there is also a private method whose sole job is to call the base method.
For each overridden method, a delegate is created whose sole job is to call the private method that calls the base method.
The following class diagram shows the main classes and interfaces involved in the interception process.
I’ll go through each of them to explain their place in the overall proxy.
IProxy Interface
The proxy implements the IProxy interface for the sole purpose of adding custom interceptors.
This allows the created proxy interface to be cast as an IProxy and then simply add Interceptors by calling it’s AddInterceptor method.
This is done internally within the proxy building process so the consumer of the API doesn’t need knowledge of this.
IInterceptor Interface
The IInterceptor interface has one method: Handle.
The handle method accepts a IMethodInvocation parameter which contains methods and data for handling method interception.
Multiple classes that implement this interface can be added to the proxy.
Each method override in the proxy calls the handle method rather than simply calling the base method.
How the proxy fully works will be explained in the next section MethodInvocation.
IMethodInvocation Interface & MethodInvocation class
The MethodInvocation will contain one main method and multiple helper properties.
Continue Method
The method Continue() has two functions hidden away from the consumer.
When Continue is called, if there are multiple Interceptors, the next Interceptors Handle method is called.
If all Interceptors Handle methods have been called, the Continue method then calls the base class method.
Properties
The MethodInvocation will contain multiple helper properties including at least the following:
Method Name (Read Only)
Method Arguments (Read and Write)
Method Argument Types (Read Only)
Method Result (Read and Write) – this property remains null if the method return type is void
Target Object (Read Only)
Return Type (Read Only)
DefaultInterceptor class
The DefaultInterceptor class is a simple class that implements the IInterceptor interface.
Here is the code:
DefaultInterceptor
namespace Rapid.DynamicProxy.Interception
{
/// <summary>
/// Default interceptor for the proxy.
/// </summary>
/// <typeparam name="TBase">The base type.</typeparam>
public class DefaultInterceptor<TBase> : IInterceptor<TBase> where TBase : class
{
/// <summary>
/// Handles the specified method invocation.
/// </summary>
/// <param name="methodInvocation">The method invocation.</param>
public void Handle(IMethodInvocation<TBase> methodInvocation)
{
methodInvocation.Continue();
}
}
}
This is automatically created in the proxy and is the first interceptor that each method override calls.
It’s sole function is to ensure that if no interceptors have been added, the base method is still called.
Custom Interceptor Example
A consumer of the Rapid.DynamicProxy API could create an interceptor for logging when the FirstName property of the User class is set.
Just for illustration, I have also wrapped a transaction around the methodInvocation.Coninue() method.
This means that any overriden methods within the user class will run within a transaction scope.
MyInterceptor
public class MyInterceptor : IInterceptor<User<int, IRepository>>
{
public void Handle(IMethodInvocation<User<int, IRepository>> methodInvocation)
{
if (methodInvocation.Name == "set_FirstName")
{
Logger.Log("First name seting to: " + methodInvocation.Arguments[0]);
}
using (TransactionScope scope = new TransactionScope())
{
methodInvocation.Continue();
}
if (methodInvocation.Name == "set_FirstName")
{
Logger.Log("First name has been set to: " + methodInvocation.Arguments[0]);
}
}
}
Overridden Method Example
To show a taster of what the overridden methods on the proxy would look like, the setter method for the property FirstName used in the above example would look something similar to the following (this is not real code but will look similar):
set_FirstName
public override void set_FirstName(string value)
{
set_FirstNameBaseMethodDelegate callBase =
new set_FirstNameBaseMethodDelegate(this.set_FirstNameProxyGetBaseMethod);
object[] arguments = new object[] { value };
IMethodInvocation<User<IRepository>> methodInvocation =
new MethodInvocation<User<IRepository>>(this, callBase, "set_FirstName", arguments, interceptors);
this.Interceptors[0].Handle(methodInvocation);
}
As you can see, a delegate instance is created which calls to a private method on the class, the private method calls the base method and would look like the following:
calls base setter
private void set_FirstNameProxyGetBaseMethod(string value)
{
base.set_FirstName(value);
}
The delegate is invoked when methodInvocation.Continue() is called within an interceptor.
The set_FirstName parameters are loaded into an object array.
The current instance, delegate, method name and method arguments are passed into the methodInvocation constructor (there will be more data not illustrated here passed in when created including method info, return types, argument types etc.)
The DefaultInterceptor’s Handle method is called with the methodInvocation instance as it’s parameter.
Obviously methods can have return values, ref and out parameters etc. in these cases the generated method override body will be slightly different from above. I’ll go into more detail on these aspects as we build them.
Conclusion
I hope this has been useful, I can’t guarantee that the proxy will look exactly like the above, but at the moment, this is pretty much what I intend to do.
Always worth downloading the code at http://rapidioc.codeplex.com/ to see the latest.
There will also be some tests that you can debug through to help see what’s going on.
Cheers,
Sean.