I've been working on an application that has a number of forms that need to be laid out based on configuration data in the database. This enables the application to meet the requirements of many different customers. Laying out the forms based on the configuration data is relatively trivial - it is just a matter of writing the code that normally ends up in the .designer file. However, because there could be any number of controls on the form, binding an object to them is a little more tricky. I approached this problem by creating a class that describes a control that will added to the form together with a boolean, integer and string value field that is used to initialize the control and capture the users input based on the type of control. I had a list of these objects representing all of the controls that needed to be rendered on the form, to bind the controls to these objects, I needed an adapter which I created at runtime.
Effectively, the adapter was pretty straight forward. It required a constructor which would take a list of the control classes described above. Then it would have a series of properties which would get and set values in the list, for example:
public Adapter
{
private IList<POWPageControl> _controls;
public Adapter(IList<POWPageControl> controls)
{
_controls = controls;
}
public Property1
{
get { return _controls[0].StringValue; }
set { _controls[0].StringValue = value; }
}
public Property2
{
get { return _controls[1].BoolValue; }
set { _controls[1].BoolValue = value; }
}
}
In order to prevent a memory leak, I added the following code to the views constructor - it checks that I haven't already created the assembly that will contain the dynamic code, before I create it:
private const string ADAPTER_ASSEMBLY_NM = "DynamicPOWAdapterAssembly";
private const string ADAPTER_MODULE_NM = "DynamicPOWAdapterModule";
private AssemblyBuilder _adapterAssemBuild;
public POWEditView()
{
AppDomain adapterDomain = AppDomain.CurrentDomain;
AssemblyName adapterAssemNm = new AssemblyName(ADAPTER_ASSEMBLY_NM);
foreach (Assembly assembly in adapterDomain.GetAssemblies())
{
AssemblyName assemNm = assembly.GetName();
if (assemNm.FullName.StartsWith(adapterAssemNm.FullName)
&& assembly is AssemblyBuilder)
{
_adapterAssemBuild = assembly as AssemblyBuilder;
break;
}
}
if (_adapterAssemBuild == null)
_adapterAssemBuild = adapterDomain.DefineDynamicAssembly
(adapterAssemNm, AssemblyBuilderAccess.Run);
InitializeComponent();
}
Once you have an assembly for the dynamic class, there are then a series of other steps to go through - create a module, create the type, add the properties to the type and then create the get
and set
methods for the properties:
private object CreateAdapter(POWPage page)
{
Type adapterType = _adapterAssemBuild.GetType(page.AdapterTypeName);
if (adapterType == null)
adapterType = CreateAdapterType(page);
return Activator.CreateInstance(adapterType, new object[] { page.Controls });
}
private Type CreateAdapterType(POWPage page)
{
ModuleBuilder adapterModBuild =
_adapterAssemBuild.DefineDynamicModule(ADAPTER_MODULE_NM + page.Guid);
TypeBuilder adapterTypeBuild =
adapterModBuild.DefineType(page.AdapterTypeName, TypeAttributes.Public);
FieldBuilder ctrlsFieldBuild =
adapterTypeBuild.DefineField(
page.AdapterCtrlsFieldNm,
typeof(IList<POWPageControl>),
FieldAttributes.Private);
GenerateConstructor(adapterTypeBuild, ctrlsFieldBuild);
for (int i = 0; i < page.Controls.Count; i++)
{
POWPageControl control = page.Controls[i];
if (control.ControlType != POWPageControlTypeEnum.Label)
GenerateProperty(adapterTypeBuild, ctrlsFieldBuild, i, control);
}
return adapterTypeBuild.CreateType();
}
private void GenerateConstructor(TypeBuilder adapterTypeBuild,
FieldBuilder ctrlsFieldBuild)
{
ConstructorInfo baseCtor = typeof(object).GetConstructor(new Type[] { });
ConstructorBuilder adapterCtor =
adapterTypeBuild.DefineConstructor(
MethodAttributes.Public,
CallingConventions.Standard,
new Type[] { ctrlsFieldBuild.FieldType });
ILGenerator ctorIL = adapterCtor.GetILGenerator();
ctorIL.Emit(OpCodes.Ldarg_0);
ctorIL.Emit(OpCodes.Call, baseCtor);
ctorIL.Emit(OpCodes.Ldarg_0);
ctorIL.Emit(OpCodes.Ldarg_1);
ctorIL.Emit(OpCodes.Stfld, ctrlsFieldBuild);
ctorIL.Emit(OpCodes.Ret);
}
private void GenerateProperty(TypeBuilder adapterTypeBuild,
FieldBuilder ctrlsFieldBuild,
int controlIndex,
POWPageControl control)
{
Type propertyType = GetPropertyType(control);
PropertyBuilder propertyBuild =
adapterTypeBuild.DefineProperty(
control.ValuePropertyName,
PropertyAttributes.None,
propertyType,
null);
MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.HideBySig;
propertyBuild.SetGetMethod(CreatePropertyGet(adapterTypeBuild,
ctrlsFieldBuild, controlIndex, control, getSetAttr, propertyType));
propertyBuild.SetSetMethod(CreatePropertySet(adapterTypeBuild,
ctrlsFieldBuild, controlIndex, control, getSetAttr, propertyType));
}
private Type GetPropertyType(POWPageControl control)
{
Type type = null;
switch (control.ControlType)
{
case POWPageControlTypeEnum.ComboBox:
case POWPageControlTypeEnum.RadioButton:
type = typeof(long?);
break;
case POWPageControlTypeEnum.TextBox:
type = typeof(string);
break;
case POWPageControlTypeEnum.CheckBox:
type = typeof(bool?);
break;
default:
throw new NotSupportedException("Unexpected control type");
}
return type;
}
private MethodBuilder CreatePropertyGet(TypeBuilder adapterTypeBuild,
FieldBuilder ctrlsFieldBuild,
int controlIndex,
POWPageControl control,
MethodAttributes getSetAttr,
Type propertyType)
{
MethodBuilder getMethBuild =
adapterTypeBuild.DefineMethod(
"get_" + control.ValuePropertyName,
getSetAttr,
propertyType,
null);
ILGenerator getMethIL = getMethBuild.GetILGenerator();
System.Reflection.Emit.Label label = getMethIL.DefineLabel();
getMethIL.DeclareLocal(propertyType);
getMethIL.Emit(OpCodes.Nop);
getMethIL.Emit(OpCodes.Ldarg_0);
getMethIL.Emit(OpCodes.Ldfld, ctrlsFieldBuild);
getMethIL.Emit(OpCodes.Ldc_I4, controlIndex);
getMethIL.Emit(OpCodes.Callvirt,
ctrlsFieldBuild.FieldType.GetMethod("get_Item"));
getMethIL.Emit(OpCodes.Callvirt,
typeof(POWPageControl).GetMethod(GetGetValueProperty(control)));
getMethIL.Emit(OpCodes.Stloc_0);
getMethIL.Emit(OpCodes.Br_S, label);
getMethIL.MarkLabel(label);
getMethIL.Emit(OpCodes.Ldloc_0);
getMethIL.Emit(OpCodes.Ret);
return getMethBuild;
}
private MethodBuilder CreatePropertySet(TypeBuilder adapterTypeBuild,
FieldBuilder ctrlsFieldBuild,
int controlIndex,
POWPageControl control,
MethodAttributes getSetAttr,
Type propertyType)
{
MethodBuilder setMethBuild =
adapterTypeBuild.DefineMethod(
"set_" + control.ValuePropertyName,
getSetAttr,
null,
new Type[] { propertyType });
ILGenerator setMethIL = setMethBuild.GetILGenerator();
setMethIL.Emit(OpCodes.Nop);
setMethIL.Emit(OpCodes.Ldarg_0);
setMethIL.Emit(OpCodes.Ldfld, ctrlsFieldBuild);
setMethIL.Emit(OpCodes.Ldc_I4, controlIndex);
setMethIL.Emit(OpCodes.Callvirt,
ctrlsFieldBuild.FieldType.GetMethod("get_Item"));
setMethIL.Emit(OpCodes.Ldarg_1);
setMethIL.Emit(OpCodes.Callvirt,
typeof(POWPageControl).GetMethod(GetSetValueProperty(control)));
setMethIL.Emit(OpCodes.Nop);
setMethIL.Emit(OpCodes.Ret);
return setMethBuild;
}
private string GetGetValueProperty(POWPageControl control)
{
return "get_" + GetValueProperty(control);
}
private string GetSetValueProperty(POWPageControl control)
{
return "set_" + GetValueProperty(control);
}
private string GetValueProperty(POWPageControl control)
{
string property = null;
switch (control.ControlType)
{
case POWPageControlTypeEnum.ComboBox:
case POWPageControlTypeEnum.RadioButton:
property = "IntValue";
break;
case POWPageControlTypeEnum.TextBox:
property = "StringValue";
break;
case POWPageControlTypeEnum.CheckBox:
property = "BoolValue";
break;
default:
throw new NotSupportedException("Unexpected control type");
}
return property;
}
The one thing that proved invaluable when creating this code was ildasm.exe. For example, I kept on getting a ProgramException
when I ran my code and couldn't see what was wrong with my code. Knocking together the example adapter above and running it through ildasm soon showed me that the problem was because I was usingLdind_I4
instead of Ldc_I4
.