Option Strict On Imports System.Reflection Imports System.IO Imports System.Runtime.Serialization Imports System.Runtime.Serialization.Formatters.Binary ''' ''' ''' Namespace Core ''' ''' Implements n-level undo capabilities. ''' ''' ''' You should not directly derive from this class. Your ''' business classes should derive from ''' . ''' _ Public MustInherit Class UndoableBase Inherits CSLA.Core.BindableBase ' keep a stack of object state values _ Private mStateStack As New Stack ' variables containing type info for comparisons Private Shared UndoableType As Type = GetType(UndoableBase) Private Shared BusinessType As Type = GetType(BusinessBase) Private Shared CollectionType As Type = GetType(BusinessCollectionBase) ''' ''' Returns the current edit level of the object. ''' Protected ReadOnly Property EditLevel() As Integer Get Return mStateStack.Count End Get End Property ''' ''' Copies the state of the object and places the copy ''' onto the state stack. ''' Protected Friend Sub CopyState() Dim currentType As Type = Me.GetType Dim state As New Hashtable() Dim fields() As FieldInfo Dim field As FieldInfo Dim fieldName As String Debug.WriteLine("CopyState" & currentType.ToString() & " EditLevel:" & Me.EditLevel.ToString()) Do ' get the list of fields in this type fields = currentType.GetFields( _ BindingFlags.NonPublic Or _ BindingFlags.Instance Or _ BindingFlags.Public) For Each field In fields ' make sure we process only our variables If field.DeclaringType Is currentType Then ' see if this field is marked as not undoable If Not NotUndoableField(field) Then ' the field is undoable, so it needs to be processed Dim value As Object = field.GetValue(Me) If TypeInheritsFrom(field.FieldType, CollectionType) Then ' make sure the variable has a value If Not value Is Nothing Then ' this is a child collection, cascade the call CType(value, BusinessCollectionBase).CopyState() End If ElseIf TypeInheritsFrom(field.FieldType, BusinessType) Then ' make sure the variable has a value If Not value Is Nothing Then ' this is a child object, cascade the call CType(value, BusinessBase).CopyState() End If Else ' this is a normal field, simply trap the value fieldName = field.DeclaringType.Name & "!" & field.Name state.Add(fieldName, value) End If End If End If Next currentType = currentType.BaseType Loop Until currentType Is UndoableType ' serialize the state and stack it Dim buffer As New MemoryStream() Dim formatter As New BinaryFormatter() formatter.Serialize(buffer, state) mStateStack.Push(buffer.ToArray) End Sub ''' ''' Restores the object's state to the most recently ''' copied values from the state stack. ''' ''' ''' Restores the state of the object to its ''' previous value by taking the data out of ''' the stack and restoring it into the fields ''' of the object. ''' Protected Friend Sub UndoChanges() ' if we are a child object we might be asked to ' undo below the level where we stacked states, ' so just do nothing in that case If EditLevel > 0 Then Dim buffer As New MemoryStream(CType(mStateStack.Pop(), Byte())) buffer.Position = 0 Dim formatter As New BinaryFormatter() Dim state As Hashtable = CType(formatter.Deserialize(buffer), Hashtable) Dim currentType As Type = Me.GetType Dim fields() As FieldInfo Dim field As FieldInfo Dim fieldName As String Do ' get the list of fields in this type fields = currentType.GetFields( _ BindingFlags.NonPublic Or _ BindingFlags.Instance Or _ BindingFlags.Public) For Each field In fields If field.DeclaringType Is currentType Then ' see if the field is undoable or not If Not NotUndoableField(field) Then ' the field is undoable, so restore its value If TypeInheritsFrom(field.FieldType, CollectionType) Then ' this is a child collection, cascade the call CType(field.GetValue(Me), BusinessCollectionBase).UndoChanges() ElseIf TypeInheritsFrom(field.FieldType, BusinessType) Then ' this is a child object, cascade the call CType(field.GetValue(Me), BusinessBase).UndoChanges() Else ' this is a regular field, restore its value fieldName = field.DeclaringType.Name & "!" & field.Name field.SetValue(Me, state.Item(fieldName)) End If End If End If Next currentType = currentType.BaseType Loop Until currentType Is UndoableType End If End Sub ''' ''' Accepts any changes made to the object since the last ''' state copy was made. ''' ''' ''' The most recent state copy is removed from the state ''' stack and discarded, thus committing any changes made ''' to the object's state. ''' Protected Friend Sub AcceptChanges() If EditLevel > 0 Then mStateStack.Pop() Dim currentType As Type = Me.GetType Dim fields() As FieldInfo Dim field As FieldInfo 'Dim fieldName As String Do ' get the list of fields in this type fields = currentType.GetFields( _ BindingFlags.NonPublic Or _ BindingFlags.Instance Or _ BindingFlags.Public) For Each field In fields If field.DeclaringType Is currentType Then ' see if the field is undoable or not If Not NotUndoableField(field) Then ' the field is undoable so see if it is a collection If TypeInheritsFrom(field.FieldType, CollectionType) Then ' it is a collection so cascade the call CType(field.GetValue(Me), BusinessCollectionBase).AcceptChanges() ElseIf TypeInheritsFrom(field.FieldType, BusinessType) Then ' it is a child object so cascade the call ' JOHN AYANOVA CUSTOMIZED: Added check if is not nothing ' below because some objects have uninitialized children '(workorder) If Not field.GetValue(Me) Is Nothing Then CType(field.GetValue(Me), BusinessBase).AcceptChanges() End If End If End If End If Next currentType = currentType.BaseType Loop Until currentType Is UndoableType End If End Sub #Region " Helper Functions " Private Function NotUndoableField(ByVal Field As FieldInfo) As Boolean Return Attribute.IsDefined(Field, GetType(NotUndoableAttribute)) '' get a list of all NotUndoableAttributes for this field 'Dim attribs() As Object = _ ' Field.GetCustomAttributes(GetType(NotUndoableAttribute), True) '' return True if any NotUndoableAttributes exist on this field 'Return (UBound(attribs) > -1) End Function Private Function TypeInheritsFrom( _ ByVal TypeToCheck As Type, ByVal CheckAgainst As Type) As Boolean Dim base As Type = TypeToCheck ' scan up through the inheritance hierarchy, checking each ' class to see if it is the one we're looking for While Not base.BaseType Is Nothing ' if we find the target class return True If base Is CheckAgainst Then Return True base = base.BaseType End While ' the target class is not in the inheritance hierarchy so ' return False Return False End Function #End Region #Region " DumpState " ''' ''' Writes the object's field data into the debugger ''' output window in VS.NET. ''' Public Sub DumpState() Dim currentType As Type = Me.GetType Dim state As New Hashtable() Dim field As FieldInfo Dim fieldName As String Dim fields() As FieldInfo Debug.IndentSize = 2 Debug.WriteLine("OBJECT " & currentType.Name) Debug.WriteLine("UndoableBase!EditLevel: " & EditLevel) Do ' get the list of fields in this type fields = currentType.GetFields( _ BindingFlags.NonPublic Or _ BindingFlags.Instance Or _ BindingFlags.Public) For Each field In fields If field.DeclaringType Is currentType Then fieldName = field.DeclaringType.Name & "!" & field.Name ' see if this field is marked as not undoable If Not NotUndoableField(field) Then ' the field is undoable, so it needs to be processed If TypeInheritsFrom(field.FieldType, CollectionType) Then ' this is a child collection, cascade the call Debug.Indent() Debug.WriteLine("COLLECTION " & fieldName) If Not field.GetValue(Me) Is Nothing Then CType(field.GetValue(Me), BusinessCollectionBase).DumpState() Else Debug.WriteLine("") End If Debug.Unindent() ElseIf TypeInheritsFrom(field.FieldType, BusinessType) Then ' this is a child object, cascade the call Debug.Indent() Debug.WriteLine("CHILD OBJECT " & fieldName) If Not field.GetValue(Me) Is Nothing Then CType(field.GetValue(Me), BusinessBase).DumpState() Else Debug.WriteLine("") End If Debug.Unindent() Else ' this is a normal field, simply trap the value If Not field.GetValue(Me) Is Nothing Then Debug.WriteLine(fieldName & ": " & field.GetValue(Me).ToString) Else Debug.WriteLine(fieldName & ": ") End If End If Else ' field is not undoable If Not field.GetValue(Me) Is Nothing Then Debug.WriteLine(" " & fieldName & ": " & field.GetValue(Me).ToString) Else Debug.WriteLine(" " & fieldName & ": ") End If End If End If Next currentType = currentType.BaseType Loop Until currentType Is UndoableType End Sub #End Region End Class End Namespace