Click here to Skip to main content
16,015,504 members
Articles / Desktop Programming / WPF

Using DynamicObject to Implement General Proxy Classes

Rate me:
Please Sign up or sign in to vote.
4.95/5 (31 votes)
10 Aug 2010CPOL7 min read 98.3K   1.4K   70   20
Extend any class with INotifyPropertyChanged and/or IDataErrorInfo via the new .NET-Framework-Class DynamicObject

Introduction

.NET-Framework 4.0 introduces the new class DynamicObject. It is a base class for defining dynamic behavior at run time. This library is based on this and wraps any existing object to extend its functionalities. Basically, it allows the following:

  • Extend any class with INotifyPropertyChanged without any additional code
  • Using IEditableObject without any additional code to provide a simple commit/rollback mechanism
  • Provide a simple usage of ValidationAttributes to validate even yet uncommitted values based on the validation rules defined by the underlying class
  • Extend the defined ValidationAttributes for any property at runtime
  • Using IDataErrorInfo to provide validation error via data binding for the GUI

It is intended to add a transparent layer between the GUI and the data provided by the view model.

Contents

Background

A common task in every application is editing data in a modal window. In a basic scenario, the main window contains a simple list control (listview, grid or something similar). Each row represents a single object. When the user doubleclicks a row, a new modal window is shown. It contains several controls to edit any variable data from the selected object. The user may close the window by selecting the "OK"-Button to "submit" the data or a "Cancel"-Button to reject the changes made.

Using databinding in this scenario leads to several problems:

  • How to prevent the immediate update of the displayed list on the main window when the user changes any data on the modal window?
    (Happens because the controls on both windows are bound to the same object)
  • How to "reject" all changes made when the "Cancel"-Button is clicked?
    (With two-way-binding, all changes are written to the properties immediately)
  • How to explicitly validate the data when the "OK"-Button is clicked?
    (May be necessary to validate unchanged properties which are required - such as a lastname when entering data for a new person)

Possible solutions are:

  • Don't use databinding on the modal window.
  • Make a copy from the object bound on the main window and bind to this on the modal window.

Using the "dynamic proxy classes", we can use full databinding on both windows - without cloning/copying any object.

Brief Description of DynamicObject

DynamicObject is new to the .NET-Framework 4.0. It allows the developer to enter any property or method. The properties or method names will be resolved at runtime. It provides just a minimum of functionality and can be used as base class for own implementations.
Additionally the framework provides the more sophisticated class ExpandoObject. It inherits from DynamicObject and stores any value for any property in a dictionary.
Example from MSDN:

C#
class Program
{
    static void Main(string[] args)
    {
        dynamic employee, manager;

        employee = new ExpandoObject();
        employee.Name = "John Smith";
        employee.Age = 33;

        manager = new ExpandoObject();
        manager.Name = "Allison Brown";
        manager.Age = 42;
        manager.TeamSize = 10;

        WritePerson(manager);
        WritePerson(employee);
    }
    private static void WritePerson(dynamic person)
    {
        Console.WriteLine("{0} is {1} years old.",
                          person.Name, person.Age);
        // The following statement causes an exception
        // if you pass the employee object.
        // Console.WriteLine("Manages {0} people", person.TeamSize);
    }
}
// This code example produces the following output:
// John Smith is 33 years old.
// Allison Brown is 42 years old.

Because it is declared as sealed, this class is not useful for our needs. DynamicObject provides a more basic functionality. It has several methods which are called when a property is accessed:

When a property value is read:

C#
public virtual bool TryGetMember( GetMemberBinder binder, out object result) 

When a property value is written:

C#
public virtual bool bool TrySetMember(SetMemberBinder binder, object value) 

We can use this for wrapping an existing object. Accessing any property will try find this property in the wrapped object via reflection and get/set the value.

dynamicobjectproxy_proxy.jpg

Simple Entity Class Used for Examples

C#
public class MyEntity
{
  [Required(AllowEmptyStrings=false, ErrorMessage = "Empty name not allowed")]
  public string Name { get; set; }

  public int Age { get; set; }
}

Class Diagram of Library

dynamicobjectproxy_classdiagram.jpg

DynamicProxy (Implementing INotifyPropertyChanged)

This is the base class for all further implementations. It provides a simple implementation to access the properties of the underlying object:

C#
public class DynamicProxy : DynamicObject, INotifyPropertyChanged 
{ 
  #region protected methods 
  protected PropertyInfo GetPropertyInfo(string propertyName) 
  { 
    return ProxiedObject.GetType().GetProperties().First
	(propertyInfo => propertyInfo.Name == propertyName); 
  } 
	
  protected virtual void SetMember(string propertyName, object value) 
  { 
    GetPropertyInfo(propertyName).SetValue(ProxiedObject, value, null); 
    RaisePropertyChanged(propertyName); 
  } 
	
  protected virtual object GetMember(string propertyName) 
  { 
    return GetPropertyInfo(propertyName).GetValue(ProxiedObject, null);
  } 
	
  protected virtual void OnPropertyChanged(string propertyName) 
  { 
    if (PropertyChanged != null) 
      PropertyChanged(ProxiedObject, new PropertyChangedEventArgs(propertyName));
  } 
	
  protected virtual void RaisePropertyChanged(string propertyName) 
  { 
    OnPropertyChanged(propertyName);
  } 
  #endregion 
	
  #region constructor 
  public DynamicProxy() { } 
  public DynamicProxy(object proxiedObject) 
  { 
    ProxiedObject = proxiedObject; 
  } 
  #endregion 
	
  public override bool TryConvert(ConvertBinder binder, out object result) 
  { 
    if (binder.Type == typeof(INotifyPropertyChanged))
    { 
      result = this; 
      return true; 
    } 
		
    if (ProxiedObject != null && binder.Type.IsAssignableFrom(ProxiedObject.GetType()))
    { 
      result = ProxiedObject; 
      return true; 
    } 
    else 
      return base.TryConvert(binder, out result); 
  } 
	
	public override bool TryGetMember(GetMemberBinder binder, out object result) 
  { 
    result = GetMember(binder.Name); 
    return true; 
  } 
	
	public override bool TrySetMember(SetMemberBinder binder, object value) 
  { 
    SetMember(binder.Name, value); 
    return true; 
	} 
  
	#region public properties 
  public object ProxiedObject { get; set; } 
  #endregion 
	
  #region INotifyPropertyChanged Member 
  public event PropertyChangedEventHandler PropertyChanged; 
  #endregion 
}

Points of Interest

The method TryConvert is implemented to allow some special actions when the user implicitly tries to convert a "DynamicProxy" object to another type. If the object is converted to the type of the underlying object, it returns the underlying object. When converting to the interface "INotifyPropertyChanged", it returns the proxy object itself.

Example

C#
public void Example()
{
  var entity = new MyEntity();
  dynamic proxy = new DynamicProxy(entity);

  // converting to interface returns "proxy object"
  ((INotifyPropertyChanged)proxy).PropertyChanged += (s, e) => DoSomething();

  // converting to type of underlying object returns underlying object itself
  MyEntity underlyingObject = proxy;

  // changing a property raises "PropertyChanged" and writes to corresponding property
  // of underlying object
  proxy.Name = "another name";
}

EditableProxy (Implementing IEditableObject)

Provides functionality to commit or rollback changes to an object that is used as a data source. For that, it contains a Dictionary<string, object>.
After calling the method BeginEdit all changed values of properties are redirected to that dictionary. The underlying object remains unchanged.
Calling the method EndEdit will then write all values in the dictionary to the corresponding properties of the underlying object. It's like committing the values.
CancelEdit throws away the dictionary and leaves the underlying object unchanged. It behaves like a rollback.

A nested class is used to achieve this functionality. It contains two dictionaries. One of them holds the original value for each changed property. The other holds the new value.
I won't show up the complete class here. Just the interesting parts. You will find the complete implementation in the accompanying source code.

C#
[...]
protected override void SetMember(string propertyName, object value)
{
  if (IsEditing)
  {
    _editBackup.SetOriginalValue(propertyName, 
	GetPropertyInfo(propertyName).GetValue(ProxiedObject, null));
    _editBackup.SetNewValue(propertyName, value);
    RaisePropertyChanged(propertyName);
  }
  else
    base.SetMember(propertyName, value);
}

protected override object GetMember(string propertyName)
{
  return IsEditing && _editBackup.NewValues.ContainsKey(propertyName) ?
    _editBackup.NewValues[propertyName] :
    base.GetMember(propertyName);
}

[...].
#region IEditableObject methods
public void BeginEdit()
{
  if (!IsEditing)
    _editBackup = new BackupState();
}

public void CancelEdit()
{
  if (IsEditing)
  {
    _editBackup = null;
  }
}

public void EndEdit()
{
  if (IsEditing)
  {
    var editObject = _editBackup;
    _editBackup = null;

    foreach (var item in editObject.NewValues)
      SetMember(item.Key, item.Value);
  }
}
#endregion

Points of Interest

While not in editing mode, the behaviour equals DynamicProxy. After calling BeginEdit, the property changed event is still called even though the underlying object remains unchanged.

This way, the developer can easily show a modal window (as described in "Background" above). The "OK"-Button will call EndEdit and the "Cancel"-Button will call CancelEdit.

Example

C#
public void Example()
{
  var entity = new MyEntity();
  dynamic proxy = new DynamicProxy(entity);

  proxy.BeginEdit();
  try
  {
    // Try change some values
    proxy.Name = "Another value";

    proxy.EndEdit();
  }
  catch (Exception)
  {
    proxy.CancelEdit();			
    throw;
  }
}

ValidatingProxy

The .NET-Framework supports the validation of property via attributes. This is useful for database constraints and any other basic validations. The simple entity class provided for this article defines the attribute Required for the property "Name".

The class ValidatingProxy extends EditableProxy. On changing a property, it gets these validation attributes from the underlying object and uses them to validate the new value. Error messages are stored in a dictionary, which contains for every changed property a list of error messages (if there are any).

In addition, when calling the method Validate, it forces validation for all properties with defined validation attributes. Even though they are unchanged, the method has several overloads to gain a finer control about what to validate.

C#
[...]
protected override void SetMember(string propertyName, object value)
{
  if (ValidateOnChange)
    Validate(propertyName, value);

  base.SetMember(propertyName, value);      
}

protected virtual IEnumerable<ValidationAttribute> 
	GetValidationAttributes(PropertyInfo propertyInfo)
{
  var validationAttributes = new List<ValidationAttribute>();

  foreach (ValidationAttribute item in propertyInfo.GetCustomAttributes
				(typeof(ValidationAttribute), true))
    validationAttributes.Add(item);

   return validationAttributes;
}

protected virtual bool Validate(PropertyInfo propertyInfo, object value)
{
  var validationAttributes = GetValidationAttributes(propertyInfo);
  if (validationAttributes.Count<validationattribute>() == 0)
    return true;

  var validationContext = new ValidationContext(ProxiedObject, null, null);
  var validationResults = new Collection<validationresult>();

  var returnValue = Validator.TryValidateValue(
    value,
    validationContext,
    validationResults,
    validationAttributes);

  if (returnValue)
  {
    if (_validationResults.ContainsKey(propertyInfo.Name))
      _validationResults.Remove(propertyInfo.Name);
  }
  else
  {
    if (_validationResults.ContainsKey(propertyInfo.Name))
      _validationResults[propertyInfo.Name] = validationResults;
    else
      _validationResults.Add(propertyInfo.Name, validationResults);
  }

  return returnValue;
}
[...].

Points of Interest

As you may have noticed, there is no implementation of any real "error handling interface" consumable via data binding by the GUI (WPF or Silverlight) yet. The simple reason for that is that I wanted to provide a base class without any concrete implementation.

DataErrorInfoProxy (Implementing IDataErrorInfo)

Implementing the interface IDataErrorInfo is very easy now. The interface provides two properties:

  • One to get all current validation errors
  • The other to get only the current validation errors of a specific property
C#
public class DataErrorInfoProxy : ValidatingProxy, IDataErrorInfo
{
  #region IDataErrorInfo Member
  public string Error
  {
    get
    {
      var returnValue = new StringBuilder();

      foreach (var item in _validationResults)
        foreach (var validationResult in item.Value)
          returnValue.AppendLine(validationResult.ErrorMessage);

      return returnValue.ToString();
    }
  }

  public string this[string columnName]
  {
    get
    {
      return _validationResults.ContainsKey(columnName) ?
        string.Join(Environment.NewLine, _validationResults[columnName]) :
        string.Empty;
    }
  }
  #endregion
}

Points of Interest

Because we already have all needed information in our base class, we just need to collect and format them for display.

Putting It Together in a Sample Application

dynamicobjectproxy_sampleapp.jpg

The sample contains two windows:

The main window with simple list control (listview). Each row represents a single object. The shown data is generated in a simple loop. When the user doubleclicks a row, a new modal window is shown.

The modal window contains several controls to edit the data from the selected object. The user may close the window by selecting the "OK"-Button to "submit" the data or a "Cancel"-Button to reject the changes made. When the "OK"-Button is selected, the validation of the values is executed and any changes of the validation errors are displayed immediately to the user.

Both windows are using data binding with the same property names.

If the users clicks "Add new entity" on the main window, the modal window will be shown with an "empty" entity object. Even if the user doesn't enter any name or age, the validation will be executed on "OK" and errors will be shown.

Points of Interest

Updating GUI to Show Validation Errors

One of the most interesting parts was the need for updating the display to reflect any changes in the validation state of the controls. The article WPF ErrorProvider - Integrating IDataErrorInfo, WPF, and the Validation Application Block (VAB) from Rahul Singla helped me a lot and I converted parts of his code as extension methods to C#:

C#
public static class ExtensionDependencyObject
{
  private static DependencyProperty GetDependencyProperty
		(PropertyInfo propertyInfo, DependencyObject element)
  {
    return typeof(DependencyProperty).IsAssignableFrom(propertyInfo.PropertyType) ?
      propertyInfo.GetValue(element, null) as DependencyProperty :
      null;
  }

  private static DependencyProperty GetDependencyProperty
		(FieldInfo fieldInfo, DependencyObject element)
  {
    return typeof(DependencyProperty).IsAssignableFrom(fieldInfo.FieldType) ?
      fieldInfo.GetValue(element) as DependencyProperty :
      null;
  }

  private static DependencyProperty GetDependencyProperty
		(MemberInfo memberInfo, DependencyObject element)
  {
    switch (memberInfo.MemberType)
    {
      case MemberTypes.Field:    return GetDependencyProperty
				(memberInfo as FieldInfo, element);
      case MemberTypes.Property: return GetDependencyProperty(
				memberInfo as PropertyInfo, element);
      default:                   return null;          
    }
  }

  public static void FindBindingsRecursively
	(this DependencyObject element, IDataErrorInfo dataErrorInfo)
  {
    FindBindingsRecursively(element, dataErrorInfo, null);
  }

  public static void FindBindingsRecursively
	(this DependencyObject element, IDataErrorInfo dataErrorInfo, 
	string boundPropertyName)
  {
    // See if we should display the errors on this element
    var members = element.GetType().GetMembers
	(BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy);

    foreach (var member in members)
    {
      var dp = GetDependencyProperty(member, element);

      if (dp != null)
      {
        // Awesome, we have a dependency property. does it have a binding? 
        // If yes, is it bound to the property we're interested in?
        var bb = BindingOperations.GetBinding(element, dp);

        if (bb != null)
        {
          if (string.IsNullOrEmpty(boundPropertyName) || 
			bb.Path.Path == boundPropertyName)
          {
            // This element has a DependencyProperty that 
            // we know of that is bound to the property we're interested in. 
            // Now we just tell the callback and the caller will handle it.
            if (element is FrameworkElement)
            {
              var errors = dataErrorInfo[bb.Path.Path];
              var be = (element as FrameworkElement).GetBindingExpression(dp);

              if (string.IsNullOrEmpty(errors))
                Validation.ClearInvalid(be);
              else
                Validation.MarkInvalid(
                be,
                new ValidationError(new ExceptionValidationRule(), be, errors, null));
            }
          }
        }
      }
    }

    // Now, recurse through any child elements
    if (element is FrameworkElement || element is FrameworkContentElement)
      foreach (var childElement in LogicalTreeHelper.GetChildren(element))
        if (childElement is DependencyObject)
          FindBindingsRecursively(childElement as DependencyObject, 
		dataErrorInfo, boundPropertyName);
  }
}

DynamicObject

The class "DynamicObject" has some nice features. One of them is the possibility to declare "real" properties in derived classes and access them the same way as "virtual properties". Reflection checks the "real" properties first and only if there is none matching property found, it will call TryGetMember.

Things To Be Done

History

  • 1.0: Initial release
  • 1.1: Upload of most recent version with already fixed bug as reported by myker

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Germany Germany
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionFighting with WPF Databinding Pin
Alexander Chernosvitov24-May-14 0:18
Alexander Chernosvitov24-May-14 0:18 
GeneralMy vote of 5 Pin
[Dolphin]20-Feb-12 22:12
[Dolphin]20-Feb-12 22:12 
GeneralSilverlight Support Pin
RStern24-Mar-11 2:17
RStern24-Mar-11 2:17 
GeneralLots of usage possible especially when using the new EF4.0... Pin
Your Display Name Here1-Feb-11 12:19
Your Display Name Here1-Feb-11 12:19 
GeneralLittle problem Pin
zordark26-Jan-11 0:30
zordark26-Jan-11 0:30 
GeneralMy vote of 5 Pin
Adam Langley12-Jan-11 11:36
Adam Langley12-Jan-11 11:36 
Generallast function Validate() always returns false Pin
Yury Goltsman1-Sep-10 11:39
Yury Goltsman1-Sep-10 11:39 
GeneralSome comments Pin
DaProgramma16-Aug-10 20:27
DaProgramma16-Aug-10 20:27 
First of all: Nice! and very usable.

But: It is not the DynamicObject that makes it possible. Its traditional reflection and some access methods to allow for generic reading/writing properties of Entity objects. The DynamicObject is simply some syntactic sugar that makes access of your functionality more convenient.

For example, you don't get any Intellisense on the members of the wrapped object any more. And what's worse: Any spelling errors now manifest at runtime, not compile time. For larger systems that are developed with agile methods (i.e. with heavy and often refactoring) this may lead to more trouble than you might imagine.

Therefore to make it even more usable, one idea could be to provide methods to check for such conditions at runtime.

Greetz
GeneralRe: Some comments Pin
Sacha Barber13-Oct-10 5:49
Sacha Barber13-Oct-10 5:49 
GeneralRe: Some comments Pin
Member 249471018-Feb-11 8:41
Member 249471018-Feb-11 8:41 
GeneralI want to like this but [modified] Pin
Sacha Barber13-Aug-10 6:02
Sacha Barber13-Aug-10 6:02 
AnswerRe: I want to like this but Pin
OlliFromTor13-Aug-10 9:14
OlliFromTor13-Aug-10 9:14 
GeneralRe: I want to like this but Pin
Yury Goltsman31-Aug-10 8:49
Yury Goltsman31-Aug-10 8:49 
GeneralRe: I want to like this but [modified] Pin
philippe dykmans1-Dec-12 1:25
philippe dykmans1-Dec-12 1:25 
GeneralMy vote of 5 [modified] Pin
picazo12-Aug-10 7:37
picazo12-Aug-10 7:37 
GeneralMy vote of 5 Pin
ruiyiz11-Aug-10 12:19
ruiyiz11-Aug-10 12:19 
GeneralTerrific Idea.. trouble with INotifyPropertyChanged Pin
myker10-Aug-10 8:15
myker10-Aug-10 8:15 
AnswerRe: Terrific Idea.. trouble with INotifyPropertyChanged Pin
OlliFromTor10-Aug-10 9:10
OlliFromTor10-Aug-10 9:10 
GeneralRe: Terrific Idea.. trouble with INotifyPropertyChanged Pin
myker10-Aug-10 9:15
myker10-Aug-10 9:15 
GeneralMy vote of 5 Pin
geo_m10-Aug-10 2:59
geo_m10-Aug-10 2:59 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.