Modelling

Like many other MVVM frameworks Photon provides a base class that can be used to handle common Data Entity and View Model concerns such as data binding and validation. Photon’s ModelBase class has been designed to be both lightweight, and powerful, allowing you to focus on writing the code you want to write without worrying about tedious plumbing. Wherever possible it leverages standard patterns and practices found in the Silverlight framework, such as data binding (INotifyPropertyChanged),  validation (System.ComponentModel.DataAnnotatons) and error reporting (IDataErrorInfo , INotifyDataErrorInfo). 

INotifyPropertyChanged

The INotifyPropertyChanged interface is at the heart of all models supporting data-binding in Silverlight, WPF, and Phone 7. The ModelBase class provides a SetProperty method for setting properties that ensures that property change events are raised efficiently. The SetProperty method also provides other features as will be seen in other sections. There are currently two overloads of set property, one uses a lambda expression to identify the property name,  the other a string *.

protected bool SetProperty<T>(ref T field, T value, Expression<Func<T>> propertyExpression, 
PropertyChangedCallback<T> onChanged = null)

protected bool SetProperty<T>(ref T field, T value, string propertyName,
PropertyChangedCallback<T> onChanged = null)

The SetProperty method will compare the new value of the field with the current value, and if different, set the field value and raise a PropertyChanged event. If the field is updated SetProperty returns true, otherwise; it returns false. There is also an optional parameter that allows the specification of a call-back to invoke if the property is changed.

Examples:

public class ExampleModel : ModelBase
{
private string _displayName;
private bool _isActive;
private bool _isUpdating;
private string _value;

public string DisplayName
{
get
{
return _displayName;
}
set
{
// set using property name
SetProperty(ref _displayName, value, "DisplayName");
}
}

public bool IsUpdating
{
get
{
return _isUpdating;
}
protected set
{
// set using property expression
SetProperty(ref _isUpdating, value, () => IsUpdating);
}
}

public bool IsActive
{
get
{
return _isActive;
}
set
{
// use return value to raise "OnIsActiveChanged"
if (SetProperty(ref _isActive, value, "IsActive"))
{
this.OnIsActiveChanged();
}
}
}

protected virtual void OnIsActiveChanged()
{
}

public string Value
{
get
{
return _value;
}
set
{
// use call-back to handle changed
SetProperty(ref this._value, value, () => Value, ValueChanged);
}
}

private void ValueChanged(string oldvalue, string newvalue)
{
}
}


* For performance reasons, it is recommended that the string based overload be used when a large number of instances of the type are expected.

Validation and Error Reporting

The ModelBase class implements the IDataErrorInfo  and the INotifyDataErrorInfo interfaces (introduced in Silverlight 4) *. These interfaces allow entities to report synchronous and asynchronous validation errors and are supported by most standard UI elements. The ModelBase class also fully supports attribute based validation through Microsoft's data annotation framework.

[Required, StringLength(50)]
public string FirstName
{
get { return _firstName; }
set { SetProperty(ref _firstName, value, "FirstName"); }
}


* The INotifyDataErrorInfo interface is not currently supported in WPF. To work around this limitation the ModelBase  class raises property changed notifications to notify of asynchronous, or cross field validation errors.

Command Integration

One of the coolest features of the ModelBase class (in Silverlight) is its integration with the  commanding infrastructure. In Silverlight, tracking the CanExecute state of a command and determining when to raise the CanExecuteChanged event can be tedious and error prone.  If a commands availability is dependent on multiple, potentially nested properties, then each of those properties must be monitored for changes to ensure that CanExecuteChanged is invoked correctly.

With Photon the command manager is notified whenever a property is changed via the SetProperty method on the ModelBase class. This means that CanExecute implementations rarely need to worry about monitoring changes and raising CanExecuteChanged events. If this all sounds like a massive performance overhead, don’t worry. The command manager is only notified when a property is changed,  It doesn’t matter how many properties you change, only a single can execute re-evaluation will be performed on the next dispatcher cycle.

Below are two examples that demonstrate the differences in approach, both examples have a SaveCommand whose availability is dependent on the expression “IsActive && CurrentPerson != null && CurrentPerson.IsDirty && !IsBusy”.  The first example shows the traditional approach which involves monitoring each of the expressions property dependencies for changes and manually raising the CanExecuteChanged event. The second example shows how the same functionality can be achieved using Photon. (Note: the use of RelayCommand in the second example)

Traditional Approach:

public class TraditionalCommandExample : PropertyChangedBase
{
private Person _currentPerson;
private bool _isActive;
private bool _isBusy;
private IPersonServiceClient _personServiceClient;
private RelayCommand _saveCommand;

public Person CurrentPerson
{
get
{
return this._currentPerson;
}
set
{
// we need to track the nested IsDirty property on the person!!!
if (_currentPerson != value)
{
if (_currentPerson != null)
{
_currentPerson.PropertyChanged -= OnCurrentPersonPropertyChanged;
}
_currentPerson = value;
if (_currentPerson != null)
{
_currentPerson.PropertyChanged += OnCurrentPersonPropertyChanged;
}
RaisePropertyChanged("CurrentPerson");
SaveCommand.RaiseCanExecuteChanged();
}
}
}

public bool IsBusy
{
get
{
return this._isBusy;
}
protected set
{
if (_isBusy != value)
{
_isBusy = value;
RaisePropertyChanged("IsBusy");
SaveCommand.RaiseCanExecuteChanged();
}
}
}

public bool IsActive
{
get
{
return this._isActive;
}
set
{
if (this._isActive != value)
{
this._isActive = value;
RaisePropertyChanged("IsActive");
SaveCommand.RaiseCanExecuteChanged();
}
}
}

public ICommand SaveCommand
{
get
{
if (_saveCommand == null)
{
_saveCommand = new RelayCommand(
x =>
{
IsBusy = true;
_personServiceClient.SaveAsync(CurrentPerson, args => { IsBusy = false; });
},
x => this.IsActive && this.CurrentPerson != null && this.CurrentPerson.IsDirty && !this.IsBusy);
}
return _saveCommand;
}
}

private void OnCurrentPersonPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (string.IsNullOrEmpty(e.PropertyName) || e.PropertyName == "IsBusy")
{
SaveCommand.RaiseCanExecuteChanged();
}
}
}


Photon Approach:

public class PhotonCommandExample : ModelBase
{
private bool _isActive;
private bool _isBusy;
private IPersonServiceClient _personServiceClient;
private ICommand _saveCommand;
private Person _selectedPerson;

public ICommand SaveCommand
{
get
{
if (_saveCommand == null)
{
_saveCommand = new RelayCommand(
x =>
{
IsBusy = true;
_personServiceClient.SaveAsync(CurrentPerson, args => { IsBusy = false; });
},
x => this.IsActive && this.CurrentPerson != null && this.CurrentPerson.IsDirty && !this.IsBusy);
}
return _saveCommand;
}
}

public bool IsBusy
{
get { return this._isBusy; }
protected set { SetProperty(ref this._isBusy, value, () => IsBusy); }
}

public bool IsActive
{
get { return this._isActive; }
set { SetProperty(ref this._isActive, value, () => IsActive); }
}

public Person CurrentPerson
{
get { return this._selectedPerson; }
set { SetProperty(ref this._selectedPerson, value, () => CurrentPerson); }
}
}

Suspending Notifications & ISuspendNotifyChanged

To maintain consistency it may sometimes be desirable to set multiple properties atomically, only raising property change events once all changes are complete. To accommodate this Photon provides a way to suspend and resume notifications. The ModelBase class tracks all modified properties when notifications are suspended, and raises PropertyChanged events for each of them when notifications are resumed.

Below are two examples that demonstrate the differences in approach,  both examples need to raise PropertyChanged  events for Count, and Current properties as part of an atomic remove operation.

Traditional Approach:

public void Remove(object item)
{
var oldCount = _count;
var oldCurrent = _current;
if (_items.Remove(item))
{
if (_current == item)
{
_current = null;
}

_count--;
}

if (oldCurrent != Current)
{
RaisePropertyChanged("Current");
}

if (oldCount != _count)
{
RaisePropertyChanged("Count");
}
}

Photon Approach:

public void Remove(object item)
{
BeginSuspendNotify();
if (_items.Remove(item))
{
if (Current == item)
{
Current = null;
}

Count--;
}
EndSuspendNotify();
}

PropertyChangingAttribute

The PropertyChangingAttribute can be used to intercept property change operations. It can be used to alter the value being assigned, or to cancel the change altogether. By abstracting common patterns into re-usable attributes, the code becomes more readable and maintainable. In addition to this there are also some subtleties to consider when cancelling changes which are dealt with automatically by ModelBase. For example, if a PropertyChanged  event is not raised when a property change is cancelled UI controls tend not to update themselves with the original value.

The example below shows how to use the built in Trim and Upper attributes.

public class ExampleModel : ModelBase
{
private string _name;
private string _statusCode;

[Trim]
public string Name
{
get { return _name; }
set { SetProperty(ref _name, value, "Name"); }
}

[Trim, Upper]
public string StatusCode
{
get { return _statusCode; }
set { SetProperty(ref _statusCode, value, "StatusCode"); }
}
}

Serialization

Photons ModelBase class is optimized for use across the wire making it an ideal base class for DTO’s (CQRS) .

Performance

So how much does all this cost you? Well, it turns out, not much. The ModelBase class makes use of a a static MetaModel which is initialized once per type. This approach allows extensibility attributes such as ValidationAttribute, or PropertyChangingAttribute to to be cached. In the case of ValidationAttribute performance is actually faster than when using the validation framework directly. As PropertyChangedEventArgs are immutable they are cached per type, this means there is no construction overhead each time a PropertyChanged event is raised.

Last edited Jan 29, 2011 at 5:08 PM by suedama1756, version 2

Comments

No comments yet.