Creating a dynamic proxy generator – Part 1 – Creating the Assembly builder, Module builder and cach
Posted
by SeanMcAlinden
on ASP.net Weblogs
See other posts from ASP.net Weblogs
or by SeanMcAlinden
Published on Thu, 18 Mar 2010 21:17:08 GMT
Indexed on
2010/03/18
21:31 UTC
Read the original article
Hit count: 1162
c#
|di
|Inversion of Control
|ASP.NET MVC
|ioc
|FLUENT INTERFACE
|Design by contract
|.NET MVC HELPERS
|ASPECT ORIENTATED PROGRAM
|DYNAMIC PROXY
|aop
I’ve recently started a project with a few mates to learn the ins and outs of Dependency Injection, AOP and a number of other pretty crucial patterns of development as we’ve all been using these patterns for a while but have relied totally on third part solutions to do the magic.
We thought it would be interesting to really get into the details by rolling our own IoC container and hopefully learn a lot on the way, and you never know, we might even create an excellent framework.
The open source project is called Rapid IoC and is hosted at http://rapidioc.codeplex.com/
One of the most interesting tasks for me is creating the dynamic proxy generator for enabling Aspect Orientated Programming (AOP).
In this series of articles, I’m going to track each step I take for creating the dynamic proxy generator and I’ll try my best to explain what everything means - mainly as I’ll be using Reflection.Emit to emit a fair amount of intermediate language code (IL) to create the proxy types at runtime which can be a little taxing to read.
It’s worth noting that building the proxy is without a doubt going to be slightly painful so I imagine there will be plenty of areas I’ll need to change along the way.
Anyway lets get started…
Part 1 - Creating the Assembly builder, Module builder and caching mechanism
Part 1 is going to be a really nice simple start, I’m just going to start by creating the assembly, module and type caches.
The reason we need to create caches for the assembly, module and types is simply to save the overhead of recreating proxy types that have already been generated, this will be one of the important steps to ensure that the framework is fast… kind of important as we’re calling the IoC container ‘Rapid’ – will be a little bit embarrassing if we manage to create the slowest framework.
The Assembly builder
The assembly builder is what is used to create an assembly at runtime, we’re going to have two overloads, one will be for the actual use of the proxy generator, the other will be mainly for testing purposes as it will also save the assembly so we can use Reflector to examine the code that has been created.
Here’s the code:
- using System;
- using System.Reflection;
- using System.Reflection.Emit;
- namespace Rapid.DynamicProxy.Assembly
- {
- /// <summary>
- /// Class for creating an assembly builder.
- /// </summary>
- internal static class DynamicAssemblyBuilder
- {
- #region Create
- /// <summary>
- /// Creates an assembly builder.
- /// </summary>
- /// <param name="assemblyName">Name of the assembly.</param>
- public static AssemblyBuilder Create(string assemblyName)
- {
- AssemblyName name = new AssemblyName(assemblyName);
- AssemblyBuilder assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(
- name, AssemblyBuilderAccess.Run);
- DynamicAssemblyCache.Add(assembly);
- return assembly;
- }
- /// <summary>
- /// Creates an assembly builder and saves the assembly to the passed in location.
- /// </summary>
- /// <param name="assemblyName">Name of the assembly.</param>
- /// <param name="filePath">The file path.</param>
- public static AssemblyBuilder Create(string assemblyName, string filePath)
- {
- AssemblyName name = new AssemblyName(assemblyName);
- AssemblyBuilder assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(
- name, AssemblyBuilderAccess.RunAndSave, filePath);
- DynamicAssemblyCache.Add(assembly);
- return assembly;
- }
- #endregion
- }
- }
So hopefully the above class is fairly explanatory, an AssemblyName is created using the passed in string for the actual name of the assembly.
An AssemblyBuilder is then constructed with the current AppDomain and depending on the overload used, it is either just run in the current context or it is set up ready for saving.
It is then added to the cache.
- using System.Reflection.Emit;
- using Rapid.DynamicProxy.Exceptions;
- using Rapid.DynamicProxy.Resources.Exceptions;
- namespace Rapid.DynamicProxy.Assembly
- {
- /// <summary>
- /// Cache for storing the dynamic assembly builder.
- /// </summary>
- internal static class DynamicAssemblyCache
- {
- #region Declarations
- private static object syncRoot = new object();
- internal static AssemblyBuilder Cache = null;
- #endregion
- #region Adds a dynamic assembly to the cache.
- /// <summary>
- /// Adds a dynamic assembly builder to the cache.
- /// </summary>
- /// <param name="assemblyBuilder">The assembly builder.</param>
- public static void Add(AssemblyBuilder assemblyBuilder)
- {
- lock (syncRoot)
- {
- Cache = assemblyBuilder;
- }
- }
- #endregion
- #region Gets the cached assembly
- /// <summary>
- /// Gets the cached assembly builder.
- /// </summary>
- /// <returns></returns>
- public static AssemblyBuilder Get
- {
- get
- {
- lock (syncRoot)
- {
- if (Cache != null)
- {
- return Cache;
- }
- }
- throw new RapidDynamicProxyAssertionException(AssertionResources.NoAssemblyInCache);
- }
- }
- #endregion
- }
- }
The cache is simply a static property that will store the AssemblyBuilder (I know it’s a little weird that I’ve made it public, this is for testing purposes, I know that’s a bad excuse but hey…)
There are two methods for using the cache – Add and Get, these just provide thread safe access to the cache.
The Module Builder
The module builder is required as the create proxy classes will need to live inside a module within the assembly.
Here’s the code:
- using System.Reflection.Emit;
- using Rapid.DynamicProxy.Assembly;
- namespace Rapid.DynamicProxy.Module
- {
- /// <summary>
- /// Class for creating a module builder.
- /// </summary>
- internal static class DynamicModuleBuilder
- {
- /// <summary>
- /// Creates a module builder using the cached assembly.
- /// </summary>
- public static ModuleBuilder Create()
- {
- string assemblyName = DynamicAssemblyCache.Get.GetName().Name;
- ModuleBuilder moduleBuilder = DynamicAssemblyCache.Get.DefineDynamicModule
- (assemblyName, string.Format("{0}.dll", assemblyName));
- DynamicModuleCache.Add(moduleBuilder);
- return moduleBuilder;
- }
- }
- }
As you can see, the module builder is created on the assembly that lives in the DynamicAssemblyCache, the module is given the assembly name and also a string representing the filename if the assembly is to be saved.
It is then added to the DynamicModuleCache.
- using System.Reflection.Emit;
- using Rapid.DynamicProxy.Exceptions;
- using Rapid.DynamicProxy.Resources.Exceptions;
- namespace Rapid.DynamicProxy.Module
- {
- /// <summary>
- /// Class for storing the module builder.
- /// </summary>
- internal static class DynamicModuleCache
- {
- #region Declarations
- private static object syncRoot = new object();
- internal static ModuleBuilder Cache = null;
- #endregion
- #region Add
- /// <summary>
- /// Adds a dynamic module builder to the cache.
- /// </summary>
- /// <param name="moduleBuilder">The module builder.</param>
- public static void Add(ModuleBuilder moduleBuilder)
- {
- lock (syncRoot)
- {
- Cache = moduleBuilder;
- }
- }
- #endregion
- #region Get
- /// <summary>
- /// Gets the cached module builder.
- /// </summary>
- /// <returns></returns>
- public static ModuleBuilder Get
- {
- get
- {
- lock (syncRoot)
- {
- if (Cache != null)
- {
- return Cache;
- }
- }
- throw new RapidDynamicProxyAssertionException(AssertionResources.NoModuleInCache);
- }
- }
- #endregion
- }
- }
The DynamicModuleCache is very similar to the assembly cache, it is simply a statically stored module with thread safe Add and Get methods.
The DynamicTypeCache
To end off this post, I’m going to create the cache for storing the generated proxy classes.
I’ve spent a fair amount of time thinking about the type of collection I should use to store the types and have finally decided that for the time being I’m going to use a generic dictionary.
This may change when I can actually performance test the proxy generator but the time being I think it makes good sense in theory, mainly as it pretty much maintains it’s performance with varying numbers of items – almost constant (0)1.
Plus I won’t ever need to loop through the items which is not the dictionaries strong point.
Here’s the code as it currently stands:
- using System;
- using System.Collections.Generic;
- using System.Security.Cryptography;
- using System.Text;
- namespace Rapid.DynamicProxy.Types
- {
- /// <summary>
- /// Cache for storing proxy types.
- /// </summary>
- internal static class DynamicTypeCache
- {
- #region Declarations
- static object syncRoot = new object();
- public static Dictionary<string, Type> Cache = new Dictionary<string, Type>();
- #endregion
- /// <summary>
- /// Adds a proxy to the type cache.
- /// </summary>
- /// <param name="type">The type.</param>
- /// <param name="proxy">The proxy.</param>
- public static void AddProxyForType(Type type, Type proxy)
- {
- lock (syncRoot)
- {
- Cache.Add(GetHashCode(type.AssemblyQualifiedName), proxy);
- }
- }
- /// <summary>
- /// Tries the type of the get proxy for.
- /// </summary>
- /// <param name="type">The type.</param>
- /// <returns></returns>
- public static Type TryGetProxyForType(Type type)
- {
- lock (syncRoot)
- {
- Type proxyType;
- Cache.TryGetValue(GetHashCode(type.AssemblyQualifiedName), out proxyType);
- return proxyType;
- }
- }
- #region Private Methods
- private static string GetHashCode(string fullName)
- {
- SHA1CryptoServiceProvider provider = new SHA1CryptoServiceProvider();
- Byte[] buffer = Encoding.UTF8.GetBytes(fullName);
- Byte[] hash = provider.ComputeHash(buffer, 0, buffer.Length);
- return Convert.ToBase64String(hash);
- }
- #endregion
- }
- }
As you can see, there are two public methods, one for adding to the cache and one for getting from the cache. Hopefully they should be clear enough, the Get is a TryGet as I do not want the dictionary to throw an exception if a proxy doesn’t exist within the cache.
Other than that I’ve decided to create a key using the SHA1CryptoServiceProvider, this may change but my initial though is the SHA1 algorithm is pretty fast to put together using the provider and it is also very unlikely to have any hashing collisions. (there are some maths behind how unlikely this is – here’s the wiki if you’re interested http://en.wikipedia.org/wiki/SHA_hash_functions)
Anyway, that’s the end of part 1 – although I haven’t started any of the fun stuff (by fun I mean hairpulling, teeth grating Relfection.Emit style fun), I’ve got the basis of the DynamicProxy in place so all we have to worry about now is creating the types, interceptor classes, method invocation information classes and finally a really nice fluent interface that will abstract all of the hard-core craziness away and leave us with a lightning fast, easy to use AOP framework.
Hope you find the series interesting.
All of the source code can be viewed and/or downloaded at our codeplex site - http://rapidioc.codeplex.com/
Kind Regards,
Sean.
© ASP.net Weblogs or respective owner