Fun with C#: How to get rid of INPC using Dynamic – Part 5

Taking it from where we left off at the previous post on this series, let’s improve our DynamicDtoDecorator so it handles raising PropertyChanged events for properties that depend on other properties.

First, let’s revisit our InvoiceItemViewModel class:

public class InvoiceItemViewModel
{
    public string ProductName { get; set; }
    public string Category { get; set; }
    public decimal Price { get; set; }
    public int Quantity { get; set; }
    public decimal Total => Price * Quantity;
}

As far as the UI is concerned, whenever either Price or Quantity changes on that object, the display of Total should refresh accordingly. In other words, Total depends on both Price and Quantity. I wanted to represent this dependency decorating the property with an attribute in the following manner:

[DependsOnAttribute("Price, Quantity")]public decimal Total => Price * Quantity;

Let’s look at the changes needed to implement that behavior. First, the implementation of DependsOnAttribute:

public class DependsOnAttribute : Attribute
{
	public string PropertyNames { get; set; }

	public DependsOnAttribute(string propertyNames)
	{
		PropertyNames = propertyNames;
	}
}

Definitely nothing special there. Next, changes to the DynamicDtoDecorator. Starting with a property to keep a dictionary of property dependencies (the key is the name of the property that has dependencies and the value is a list of properties that it depends on):
readonly Dictionary<string, IEnumerable<string>> _propertyDependencies = new Dictionary<string, IEnumerable<string>>();
We then change the RegisterProperties method so it verifies whether the property its registering has dependencies, and if so, it calls out the RegisterPropertyDependencies method:

protected virtual void RegisterProperties(object dto)
{
    dto.GetType()
        .GetProperties(BindingFlags.Public | BindingFlags.Instance)
        .ToList()
        .ForEach(prop =>
        {
            object[] notIndexedProperty = null;
            Func valueGetter = () => prop.GetValue(dto, notIndexedProperty);
            Action valueSetter = newValue => prop.SetValue(dto, newValue, notIndexedProperty);
            Register(prop.Name, valueGetter, valueSetter);

            var dependsOn = prop.GetCustomAttributes(true).OfType().SingleOrDefault();
            if (dependsOn != null)
            {
                RegisterPropertyDependencies(prop.Name, Array.ConvertAll(dependsOn.PropertyNames.Split(','), d => d.Trim()));
            }
        });
}

Here’s the implementation of RegisterPropertyDependencies:

public void RegisterPropertyDependencies(string property, string[] dependencies)
{
	_propertyDependencies.Add(property, dependencies);
}

We then have to change the SetProperty method to make sure we raise PropertyChanged for properties that depend on the property being changed:

public virtual void SetProperty(string propertyName, dynamic value)
{
	if (!_members.ContainsKey(propertyName))
		throw new InvalidOperationException({0}#x22;Unregistered member: {propertyName}");

	if (_members[propertyName].Get() == value) return;

	_members[propertyName].Set(value);
	RaisePropertyChanged(propertyName);
	RaisePropertyChangedForDependentsOn(propertyName);
}

Here’s the implementation for the RaisePropertyChangedForDependentsOn method:

void RaisePropertyChangedForDependentsOn(string propertyThatWasChanged)
{
    _propertyDependencies.ForEach(dependentProperty =>
    {
        var dependableProperties = dependentProperty.Value;
        if (!dependableProperties.Contains(propertyThatWasChanged)) return;

        var dependentPropertyName = dependentProperty.Key;
        RaisePropertyChanged(dependentPropertyName);
    });
}

The code simply looks through the registered properties that have dependencies and raise the event as appropriate.

This pretty much wraps up all the main pieces for getting rid of direct implementation of INotifiyPropertyChanged in ViewModels by using the C# Dynamic features. I’ll be keeping the latest version of the code in this repository, so feel free to check it out. I’ll be writing new posts for other improvements I’ve added to the decorator and you might find useful. Stay tuned!

In case you missed any part of this series, here are the links to Part 1, Part 2, Part 3 and Part 4.

Advertisements

  1. Leave a comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: