Introduction
The implementation of System.ComponentModel.IEditableObject on hundrets of business objects could be boring. But with a little help from PostSharp its fun. The result is a reusable component, which implements the interface and the logic behind on the fly. INotifyPropertyChanged is a candidate too, but here some design drawbacks araise.
Details
We bring in the magic by decorating the business objects class with a simple attribute.
<Serializable(), SupportDataBinding()> _
Public Class Customer
.....
End Class
This object is able to generate a backup copy of all altered fields after BeginEdit is invoked and copy the values back if CancleEdit is called. You can use this object behind DataGridView or similar controls, which allows inline editing. To demonstrate the mechanism in detail, a data input page is also provided. You can launch the commands of IEditableObject in any sequence.
How it works:
The implementation of the attribute is responsible to provide a list of aspects.
``` _ Public NotInheritable Class SupportDataBindingAttribute Inherits CompoundAspect
<NonSerialized()> Private myAspectPriority As Integer = 0
''' <summary>
''' Method called at compile time to get individual aspects required
''' by the current compound aspect.
''' </summary>
''' <param name="targetElement">Metadata element (<see cref="Type"/> in
''' our case) to which the current custom attribute
''' instance is applied.</param>
''' <param name="collection">Collection of aspects to which individual
''' aspects should be added.</param>
Public Overloads Overrides Sub ProvideAspects(ByVal targetElement As Object, ByVal collection As LaosReflectionAspectCollection)
' Get the target type.
Dim targetType As Type = DirectCast(targetElement, Type)
' On the type, add a Composition aspect to implement the
' INotifyPropertyChanged interface.
collection.AddAspect(targetType, New AddNotifyPropertyChangedInterfaceSubAspect())
' On the type, add a Composition aspect to implement the
' IEditableObject interface
collection.AddAspect(targetType, New AddEditableObjectInterfaceSubAspect())
' Add a OnMethodBoundaryAspect on each writable non-static property.
' The implementation of
' INotifyPropertyChanged.PropertyChanged needs the name of the property
' (not of the field), so we have to detect
' changes on the property level, not on the field level. Unfortunately,
' there is no rule for naming properties and
' their related fields. Even more, one property could access many fields,
' or gets its value out of one or
' more fields. At this point, using an enhancer to add this functionallity
' is only recomended, as there exixts a design
' rule, which relates one field to one and only one property and vice versa.
' Unfortunately, there is no compile time check available.
' Please be careful!!
' Personally, I tend to say, that implementing INotifyPropertyChanged is out
' of the scope of enhancers due to the
' possibility to pack logic within the properties implementation, which is
'never under the enhancers control.
For Each pi As PropertyInfo In targetType.UnderlyingSystemType.GetProperties()
If pi.DeclaringType Is targetType AndAlso pi.CanWrite Then
Dim mi As MethodInfo = pi.GetSetMethod()
If Not mi.IsStatic Then
collection.AddAspect(mi, New OnPropertySetSubAspect(pi.Name, Me))
End If
End If
Next
' Add a OnFieldAccess aspect on each writable non-static field, used to backup the original values to have a backup copy
' for the CancleEdit command of IEditableObject.
For Each field As FieldInfo In targetType.GetFields(BindingFlags.Instance Or BindingFlags.Public Or BindingFlags.NonPublic)
If field.DeclaringType Is targetType AndAlso (field.Attributes And FieldAttributes.InitOnly) = 0 Then
collection.AddAspect(field, New OnFieldAccessSubAspect())
End If
Next
End Sub
.... ```
First we need two aspects of type CompositionAspect to handle the implementation of INotifyPropertyChanged and IEditableObject. There is a helper class for each interface responsible for adding the interface to the type of the business object and a helper class each, which actually holds the implementation of the interface.
The logic behind INotifyPropertyChanged should be triggerd, when the property changes. This leads to some problems as note above.
The logic behind IEditableObject is triggered by an OnFieldAccessAspect, which redirects the write operations to the fields and add the backup logic. Depending of the actual objects state, controlled by IEditableObject, the old field value is saved in a dictionary before the new value is set. CancleEdit copies all values stored in the dictionary back, EndEdit just clears the dictionary, because the values were not needed anymore.