Recently, I was working on a project where there was a requirement to print certain documents. These documents were stored, in pieces, in the database and then loaded and assembled at runtime. This allowed parts of the documents to be reused as needed.
The Xaml for these documents contained bindings to data that originated from the database. The usual drill would be to statically define all the required properties on the view model and then write code to populate them from the database. There's nothing wrong with this approach, but the project was in a mode where new documents were being generated quickly and they required new properties to be defined on the view model making the view model code a hot spot.
The data required in the bindings was largely stored as name/value pairs in the database. Wouldn't it be much nice if new values added to the database just showed up at runtime without having to create new properties and the code to populate them?
Using dynamic objects would be a great way to go, if you could bind to them. Unfortunately, they don't support reflection which is required for binding. A second problem is that adding properties to a dynamic object requires a syntax like this:
Connect to XMPP
- dynamic vm = new object();
- vm.PropertyName = 5;
Which, of course, requires you to know all property names to be added, ahead of time. Since I'd like to be able to add properties that I find in the database, this is unsuitable.
I did some research and found this blog post by Lester Lobo. The solution accompanying the post contained this class.
Connect to XMPP
- using System.Collections.Generic;
- using System.Collections.ObjectModel;
- using System.ComponentModel;
- using System.Dynamic;
- using System.Windows.Data;
- using System;
-
- namespace DynamicVM
- {
- public class DynamicObjectClass : DynamicObject, INotifyPropertyChanged
- {
- #region DynamicObject overrides
-
- public DynamicObjectClass()
- {
- }
-
- public override bool TryGetMember(GetMemberBinder binder, out object result)
- {
- return members.TryGetValue(binder.Name, out result);
- }
-
- public override bool TrySetMember(SetMemberBinder binder, object value)
- {
- members[binder.Name] = value;
- OnPropertyChanged(binder.Name);
- return true;
- }
-
- public override IEnumerable<string> GetDynamicMemberNames()
- {
- return members.Keys;
- }
-
- public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
- {
- int index = (int)indexes[0];
- try
- {
- result = itemsCollection[index];
- }
- catch (ArgumentOutOfRangeException)
- {
- result = null;
- return false;
- }
- return true;
- }
-
- public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
- {
- int index = (int)indexes[0];
- itemsCollection[index] = value;
- OnPropertyChanged(System.Windows.Data.Binding.IndexerName);
- return true;
- }
-
- public override bool TryDeleteMember(DeleteMemberBinder binder)
- {
- if (members.ContainsKey(binder.Name))
- {
- members.Remove(binder.Name);
- return true;
- }
- return false;
- }
-
- public override bool TryDeleteIndex(DeleteIndexBinder binder, object[] indexes)
- {
- int index = (int)indexes[0];
- itemsCollection.RemoveAt(index);
- return true;
- }
-
- #endregion DynamicObject overrides
-
- public void AddProperty(string propertyName, object value)
- {
- members[propertyName] = value;
- }
-
- #region INotifyPropertyChanged
-
- public event PropertyChangedEventHandler PropertyChanged;
-
- private void OnPropertyChanged(string propertyName)
- {
- if (PropertyChanged != null)
- PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
- }
-
- #endregion INotifyPropertyChanged
-
- #region Public methods
-
- public object AddItem(object item)
- {
- itemsCollection.Add(item);
- OnPropertyChanged(Binding.IndexerName);
- return null;
- }
-
- #endregion Public methods
-
- #region Private data
-
- Dictionary<string, object> members = new Dictionary<string, object>();
- ObservableCollection<object> itemsCollection = new ObservableCollection<object>();
-
- #endregion Private data
- }
-
- }
This class was almost just what I needed. It solved the problem of not being able to bind to a dynamic, but I still needed a way to add properties on the fly. For this, I added the AddProperty() method, which you'll see in the above listing.
Using the class is easy. Below is an example.
Connect to XMPP
- AValue = new DynamicObjectClass();
- AValue.Foo = "Hello"; //use the out-of-the-box syntax for adding a property
-
- AValue.AddProperty("Bar", 5); //add a property discovered at runtime.
- AValue.AddProperty(propName, propValue);
So, using the above, I can simply read the name/value pairs out of the database and then add each to my view model at runtime. This means that any new value added to the database will automatically be available on the now dynamic view model for binding.
One Offs
Of course, you don't have to use a completely dynamic view model. It's just as easy to define properties at design time and have one of those properties be a dynamic type. Also, it's possible to have a dynamic view model that contains a property that is, itself, a dynamic.