This is an old sample I worked out a few months back. Upon further review, I could probably have done something with XSLT and deserialization, but it was a concious effort to use reflection to emit some code. You can skip the background if you want to get straight to code, ya lazy jerks ;-)
So, without further ado:
Business Problem: Custom data needs to be visible to the user in a grid. No customization of code, pure data configuration.
Assumptions: Use .NET WinForms stock controls. There is an existing business/entity object framework that should be altered as little as possible (ideal being no modifications at all). Object tree looks as such:
- Person has a name.
- Person has an address.
- Person has business specific properties that are constant throughout all scenarios.
- Person will have custom key/value pairs that are constructed from data at run time (therefore, we have a custom object we'll call "CustomEntity" that is basically an object holding key/value pairs).
If you want to slap this heirarchical structure on top of a DataGridView, that ain't gonna happen. I know there are a number of third-party controls that purport to do this (I love you too, Infragistics) but that's not possible in this scenario. So, if I can't build a fancy control easily, the alternate approach is to build a generic object (little g, but we'll get to that in a sec) to bind to a stock control.
I know I may want to do this again, so let's abstract this with some sort of factory we'll call "TypeFactory." In real-life I'll put a constraint on this class to make sure I can only do this with certain classes that have the custom collection driving this need . . .
So, an example of typical usage would be as such://for demo/reference's sakepublic interface ICustomEntityHolder{//structure to hold custom dataList <>CustomEntityCollection{get; set;} //method to lookup matching entry and return valueobject GetCustomEntityValue(string key);//method to save or overwrite key/value pairvoid SetCustomEntityValue(string key, object value);//need some way to seed the new type's instance with datavoid SeedObjectData(object seed);}public class CustomEntity{public string Key;public object Value;}public class Person : ICustomEntityHolder{public string Name;public string Address;public ListCustomEntityCollection; }public static Type CreateExtendedType <>(T baseType) where T : ICustomEntityHolder
{
string assemblyNameValue = typeof(T).Name + "ExtendedEmit";AssemblyName assemblyName = new AssemblyName();
assemblyName.Name = assemblyNameValue;AssemblyBuilder ab = Thread.GetDomain().DefineDynamicAssembly(assemblyName,AssemblyBuilderAccess.Run);ModuleBuilder moduleBuilder = ab.DefineDynamicModule(assemblyName.Name);
//create a type that derives from base T
TypeBuilder typeBuilder =
moduleBuilder.DefineType(
assemblyName.Name,
TypeAttributes.Class TypeAttributes.Public,
typeof(T));//base T needs to have a constructor that takes a copy of itself
//TODO: best way to seed new object's data?
ConstructorBuilder constructorBuilder =
typeBuilder.DefineConstructor(MethodAttributes.Public,CallingConventions.Standard,new Type[0]);ILGenerator ilGen = constructorBuilder.GetILGenerator();
ilGen.Emit(OpCodes.Ldarg_0); //this arg
ilGen.Emit(OpCodes.Call, typeof(T).GetConstructor(new Type[0]));
ilGen.Emit(OpCodes.Ret);//iterate through the collection and create a public property on the//extended object that accesses the underlying collectionforeach(CustomEntity entity in baseType.CustomEntityCollection{
FieldBuilder fb = typeBuilder.DefineField("_" + entity.Key,typeof(object),FieldAttributes.Private);
PropertyBuilder pb = typeBuilder.DefineProperty(entity.Key,PropertyAttributes.HasDefault,typeof(object),Type.EmptyTypes);
ILGenerator propILGen;
//create a getter that calls the underlying custom entity getter
MethodBuilder propGet = typeBuilder.DefineMethod("get" + entity.Key,MethodAttributes.Public,typeof(object),Type.EmptyTypes);
propILGen = propGet.GetILGenerator();
MethodInfo customGetter = typeof(T).GetMethod("GetCustomEntityValue",new Type[] { typeof(string) });
propILGen.Emit(OpCodes.Ldarg_0);
propILGen.Emit(OpCodes.Ldstr, entity.Key);
propILGen.Emit(OpCodes.Call, customGetter);
propILGen.Emit(OpCodes.Ret);
pb.SetGetMethod(propGet);
//create a setter that calls the underlying custom entity setterMethodBuilder propSet = typeBuilder.DefineMethod("set" + entity.Key,MethodAttributes.Public,null,new Type[] { typeof(object) });
propILGen = propSet.GetILGenerator();
MethodInfo customSetter = typeof(T).GetMethod("SetCustomEntityValue",
new Type[] { typeof(string), typeof(object) });
propILGen.Emit(OpCodes.Ldarg_0);
propILGen.Emit(OpCodes.Ldstr, entity.Key);
propILGen.Emit(OpCodes.Ldarg_1);
propILGen.Emit(OpCodes.Call, customSetter);
propILGen.Emit(OpCodes.Ret);
pb.SetSetMethod(propSet);
}
Type extendedType = typeBuilder.CreateType();
return extendedType;
}
Person person = new Person();
Type extendedPersonType = TypeFactory.CreateExtendedType(person);
object extendedPerson = Activator.CreateInstance(extendedPersonType);
//yes, you could do this through the ICustomEntityHolder interface
MethodInfo seedDataMethod =
extendedPersonType.GetMethod("SeedObjectData");
if (seedDataMethod != null)
{seedDataMethod.Invoke(extendedPerson, new object[] { person});}As a result you get an instance of a new type, preserving the existing properties, but with new properties representing each of the custom entity objects in that collection. You could take this object, or a collection thereof, and bind to a DataGridView. Overworked? Perhaps. Cool? Definitely.
No comments:
Post a Comment