A recent task at work required that I take some data formatted in a less than friendly way and provide an interface to manipulate and save the data. Since our application is built with Silverlight, building a UI and using data binding is the way to go. The problem though, is that the data itself is dynamic based on the orginization consuming the application. This raises the question, how can you build a UI structured around a dynamic business object?
My first inclination was to use the DynamicObject in the System.Dynamic namespace. In looping through the data obtaind from our database, I could construct an object and controltemplate that would reflect the dynamic properties/values interpreted post compilation. However, to my dismay the DynamicObject does not currently work correctly in SL4 but Microsoft has made posts that this is on their radar, perhaps in SL5?
In doing a little research I came across a pretty cool solution that I won't try to take credit for, but did modify slightly to work under our specific scenario, and it works like a charm. You can see the original article here. Essentially we're creating a wrapper class that inherits from DynamicObject and INotifyPropertyChanged. Specifically, we end up storing the added members in a dictionary<string, object> much like the ExpandoObject, and provide an indexer for the object that lets us set and get these dynamic properties. Finally, we override the TryGetMember and TrySetMember methods implemented in the DynamicObject, which let's us use dynamic expressions in code. In code we create a new ElasticObject, specify properties and values, then set our binding. Note that when specifying the PropertyPath in the binding, brackets are needed to indicate we're referencing the object's indexer.
public class ElasticObject : DynamicObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private readonly Dictionary<string, object> properties = new System.Collections.Generic.Dictionary<string, object>();
public object this[string name]
{
get
{
object result;
properties.TryGetValue(name, out result);
return result;
}
set
{
properties[name] = value;
RaisePropertyChanged(name);
}
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
return properties.TryGetValue(binder.Name, out result);
}
/// <summary>
/// Specify Var as binder.Name and pass a KeyValuePair<string, object> where string is the dyanmic propertyname to use
/// and object the value.
/// </summary>
/// <param name="binder"></param>
/// <param name="value"></param>
/// <returns></returns>
public override bool TrySetMember(SetMemberBinder binder, object value)
{
if (binder.Name.ToLower().StartsWith("var"))
{
try
{
if (value != null && value.GetType() == typeof(KeyValuePair<string, object>))
{
properties[((KeyValuePair<string, object>)value).Key] = ((KeyValuePair<string, object>)value).Value;
}
}
catch
{
return false;
}
}
else
{
properties[binder.Name] = value;
RaisePropertyChanged(binder.Name);
}
return true;
}
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
dynamic o = new ElasticObject();
o.Name = "FooBar";
o.var = new KeyValuePair<string, object>("VariablePropertyName", new object());
txt_MyTextBox.SetBinding(TextBox.TextProperty, new Binding("[VariablePropertyName]") { Mode = BindingMode.TwoWay });
txt_MyTextBox.DataContext = o;
Now one requirment I had beyond providing dynamic objects and binding, was to provide a means of specifying dynamic property names as well. The solution I came up, well we'll just call it creative and leave it at that. I modified the TrySetMember function to look for input property names that equal 'var', and then expect a keyvaluepair<string, objrect> as the passed propertyvalue containing you guessed it, the dynamic proprety name to use (Key) and propertyvalue (Value).