Here’s a useful helper to build a dynamic wrapper / facade class from an arbitrary collection of properties. The wrapper implements INotifiyPropertyChanged so can be used with two-way databinding in WPF, Silverlight and ASP.NET (probably other places too):
/// <summary> /// IDynamicPropertyProvider interface /// </summary> public interface IDynamicPropertyProvider { object GetPropertyValue( string name ); void SetPropertyValue( string name, object value ); event PropertyChangedEventHandler ValueChanged; } /// <summary> /// DynamicTypeFactory class /// </summary> public class DynamicTypeFactory { #region DynamicBase class /// <summary> /// Base for dynamic class /// </summary> public class DynamicBase : INotifyPropertyChanged, IDisposable { private IDynamicPropertyProvider provider; public DynamicBase( IDynamicPropertyProvider provider ) { this.provider = provider; if ( provider != null ) provider.ValueChanged += OnPropertyChanged; } protected object GetValue( string name ) { return provider.GetPropertyValue( name ); } protected void SetValue( string name, object value ) { provider.SetPropertyValue( name, value ); } private void OnPropertyChanged( object sender, PropertyChangedEventArgs e ) { if ( PropertyChanged != null ) PropertyChanged( this, e ); } #region INotifyPropertyChanged Members public event PropertyChangedEventHandler PropertyChanged; #endregion #region IDisposable Members public void Dispose() { if ( provider != null ) provider.ValueChanged -= OnPropertyChanged; } #endregion } #endregion /// <summary> /// Creates the type. /// </summary> /// <param name="name">The name.</param> /// <param name="properties">The properties.</param> /// <returns></returns> public static Type CreateType( string name, IEnumerable<string> properties ) { // Define dynamic assembly var assemblyName = new AssemblyName( "DynamicAssembly" ); var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly( assemblyName, AssemblyBuilderAccess.Run ); var module = assemblyBuilder.DefineDynamicModule( "DynamicModule" ); // Define type var typeBuilder = module.DefineType( name, TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.AutoClass | TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit | TypeAttributes.AutoLayout, typeof( DynamicBase ) ); var baseConstructor = typeof( DynamicBase ).GetConstructor( new[] { typeof( IDynamicPropertyProvider ) } ); // Define constructor var constructor = typeBuilder.DefineConstructor( MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, CallingConventions.Standard, new[] { typeof( IDynamicPropertyProvider ) } ); var ilGen = constructor.GetILGenerator(); ilGen.Emit( OpCodes.Ldarg_0 ); ilGen.Emit( OpCodes.Ldarg_1 ); ilGen.Emit( OpCodes.Call, baseConstructor ); ilGen.Emit( OpCodes.Ret ); // Prepare to define properties var getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig; var getMethodInfo = typeof( DynamicBase ).GetMethod( "GetValue", BindingFlags.NonPublic | BindingFlags.Instance ); var setMethodInfo = typeof( DynamicBase ).GetMethod( "SetValue", BindingFlags.NonPublic | BindingFlags.Instance ); foreach ( var property in properties ) { // Define property get method var getMethod = typeBuilder.DefineMethod( "get_" + property, getSetAttr, typeof( object ), Type.EmptyTypes ); ilGen = getMethod.GetILGenerator(); ilGen.Emit( OpCodes.Ldarg_0 ); ilGen.Emit( OpCodes.Ldstr, property ); ilGen.Emit( OpCodes.Callvirt, getMethodInfo ); ilGen.Emit( OpCodes.Ret ); // Define property set method var setMethod = typeBuilder.DefineMethod( "set_" + property, getSetAttr, null, new[] { typeof( object ) } ); ilGen = setMethod.GetILGenerator(); ilGen.Emit( OpCodes.Ldarg_0 ); ilGen.Emit( OpCodes.Ldstr, property ); ilGen.Emit( OpCodes.Ldarg_1 ); ilGen.Emit( OpCodes.Callvirt, setMethodInfo ); ilGen.Emit( OpCodes.Ret ); // Define property var propertyBuilder = typeBuilder.DefineProperty( property, PropertyAttributes.HasDefault, typeof( object ), Type.EmptyTypes ); propertyBuilder.SetGetMethod( getMethod ); propertyBuilder.SetSetMethod( setMethod ); } // Materialize type return typeBuilder.CreateType(); } }
To use it you’ll need to implement the IDynamicPropertyProvider interface on your own “provider” class. Its job is to get and set a property value and raise “ValueChanged” events when values are changed (externally or by SetPropertyValue).
A basic provider could look something like this (see CreateDynamicWrapper for usage details):
public class MyDynamicProvider : IDynamicPropertyProvider { public Dictionary<string, object> MyValues { get; private set; } public MyDynamicProvider() { MyValues = new Dictionary<string, object>(); } public object CreateDynamicWrapper() { var type = DynamicTypeFactory.CreateType( "MyWrapper", MyValues.Keys.ToArray() ); return Activator.CreateInstance( type, this ); } public object GetPropertyValue( string name ) { return MyValues[ name ]; } public void SetPropertyValue( string name, object value ) { MyValues[ name ] = value; if ( ValueChanged != null ) ValueChanged( this, new PropertyChangedEventArgs( name ) ); } public event PropertyChangedEventHandler ValueChanged; }
From there of course you can bind directly to the wrapper in XAML, ASP.NET or whatever and maybe avoid some typing
6 responses so far ↓
Dew Drop - April 7, 2009 | Alvin Ashcraft's Morning Dew // April 7, 2009 at 6:38 am |
[...] Binding Dynamic Properties in XAML, ASP.NET… (Chris Cavanagh) [...]
Kevin // April 9, 2009 at 4:46 pm |
Thanks for your article. It’s very useful.
There is a datagrid in my project. I’d like to bind column header from database. So I have to create dynamic column for datagrid.
Do you have any idea for binding column header of datagrid?
Thanks.
Jones // April 27, 2009 at 4:22 pm |
Nice post…
Andrus // May 2, 2009 at 2:23 pm |
http://silverlight.net/forums/p/66364/214522.aspx#214522
is better: implements typed properties
Chris Cavanagh // May 2, 2009 at 4:06 pm |
Andrus – Thanks for the feedback!
Andrus // May 3, 2009 at 5:48 am |
I found the following issues:
1. How to add new empty rows to DataGrid? I tried
list.Add(Activator.CreateInstance(t));
but got exception
No parameterless constructor defined for this object.
My entity base class has public parameterless constructor. Generated type does not inherit it.
2. There may be a list of known property types. How to force to use pre-defined property type instead of object?
3. For some columns, System.Security.VerificationException occurs with additional information:
Operation could destabilize the runtime.
How to fix them ?