Files
ayanova7/source/csla10/CSLA/BusinessBase.vb
2018-06-29 19:47:36 +00:00

838 lines
30 KiB
VB.net

Imports System.IO
Imports System.Reflection
Imports System.Runtime.Serialization.Formatters.Binary
Imports System.ComponentModel
'Imports System.Configuration
''AyaNova Imports
Imports System.Threading
Imports CSLA.Security
''' <summary>
''' This is the base class from which most business objects
''' will be derived.
''' </summary>
''' <remarks>
''' <para>
''' This class is the core of the CSLA .NET framework. To create
''' a business object, inherit from this class.
''' </para><para>
''' Please refer to 'Expert One-on-One VB.NET Business Objects' for
''' full details on the use of this base class to create business
''' objects.
''' </para>
''' </remarks>
<Serializable()> _
Public MustInherit Class BusinessBase
Inherits Core.UndoableBase
Implements IEditableObject
Implements ICloneable
Implements IDataErrorInfo
Implements Serialization.ISerializationNotification
#Region "AyaNova related convenience items"
''Get the user object so
''we can check rights / get ID value
<Browsable(False)> _
Public ReadOnly Property CurrentUserID() As Guid
Get
Dim CurrentUser As Security.BusinessPrincipal = CType(Thread.CurrentPrincipal, BusinessPrincipal)
Return CurrentUser.ID
End Get
End Property
''Confirm if notification is on or off from current principle object
<Browsable(False)> _
Public ReadOnly Property Notify() As Boolean
Get
Dim CurrentUser As Security.BusinessPrincipal = CType(Thread.CurrentPrincipal, BusinessPrincipal)
Return CurrentUser.UseNotification
End Get
End Property
'' Get security access right level from current identity
Public Function GetRight(ByVal RightName As String) As Int32
Return CType(Thread.CurrentPrincipal, BusinessPrincipal).Right(RightName)
End Function
''Added to give UI ability to know if object is in an
''edit level greater than zero
<Browsable(False)> _
Public ReadOnly Property IsEditing() As Boolean
Get
Return (EditLevel > 0)
End Get
End Property
#End Region
#Region " IsNew, IsDeleted, IsDirty "
' keep track of whether we are new, deleted or dirty
Private mIsNew As Boolean = True
Private mIsDeleted As Boolean = False
Private mIsDirty As Boolean = True
''' <summary>
''' Returns True if this is a new object, False if it is a pre-existing object.
''' </summary>
''' <remarks>
''' An object is considered to be new if its data doesn't correspond to
''' data in the database. In other words, if the data values in this particular
''' object have not yet been saved to the database the object is considered to
''' be new. Likewise, if the object's data has been deleted from the database
''' then the object is considered to be new.
''' </remarks>
''' <returns>A value indicating if this object is new.</returns>
<Browsable(False)> _
Public ReadOnly Property IsNew() As Boolean
Get
Return mIsNew
End Get
End Property
''' <summary>
''' Returns True if this object is marked for deletion.
''' </summary>
''' <remarks>
''' CSLA .NET supports both immediate and deferred deletion of objects. This
''' property is part of the support for deferred deletion, where an object
''' can be marked for deletion, but isn't actually deleted until the object
''' is saved to the database. This property indicates whether or not the
''' current object has been marked for deletion. If it is True, the object will
''' be deleted when it is saved to the database, otherwise it will be inserted
''' or updated by the save operation.
''' </remarks>
''' <returns>A value indicating if this object is marked for deletion.</returns>
<Browsable(False)> _
Public ReadOnly Property IsDeleted() As Boolean
Get
Return mIsDeleted
End Get
End Property
''' <summary>
''' Returns True if this object's data has been changed.
''' </summary>
''' <remarks>
''' <para>
''' When an object's data is changed, CSLA .NET makes note of that change
''' and considers the object to be 'dirty' or changed. This value is used to
''' optimize data updates, since an unchanged object does not need to be
''' updated into the database. All new objects are considered dirty. All objects
''' marked for deletion are considered dirty.
''' </para><para>
''' Once an object's data has been saved to the database (inserted or updated)
''' the dirty flag is cleared and the object is considered unchanged. Objects
''' newly loaded from the database are also considered unchanged.
''' </para>
''' </remarks>
''' <returns>A value indicating if this object's data has been changed.</returns>
<Browsable(False)> _
Public Overridable ReadOnly Property IsDirty() As Boolean
Get
Return mIsDirty
End Get
End Property
''' <summary>
''' Marks the object as being a new object. This also marks the object
''' as being dirty and ensures that it is not marked for deletion.
''' </summary>
''' <remarks>
''' Newly created objects are marked new by default. You should call
''' this method in the implementation of DataPortal_Update when the
''' object is deleted (due to being marked for deletion) to indicate
''' that the object no longer reflects data in the database.
''' </remarks>
Protected Sub MarkNew()
mIsNew = True
mIsDeleted = False
MarkDirty()
End Sub
''' <summary>
''' Marks the object as being an old (not new) object. This also
''' marks the object as being unchanged (not dirty).
''' </summary>
''' <remarks>
''' <para>
''' You should call this method in the implementation of
''' DataPortal_Fetch to indicate that an existing object has been
''' successfully retrieved from the database.
''' </para><para>
''' You should call this method in the implementation of
''' DataPortal_Update to indicate that a new object has been successfully
''' inserted into the database.
''' </para>
''' </remarks>
Protected Sub MarkOld()
mIsNew = False
MarkClean()
End Sub
''' <summary>
''' Marks an object for deletion. This also marks the object
''' as being dirty.
''' </summary>
''' <remarks>
''' You should call this method in your business logic in the
''' case that you want to have the object deleted when it is
''' saved to the database.
''' </remarks>
Protected Sub MarkDeleted()
mIsDeleted = True
MarkDirty()
End Sub
''' <summary>
''' Marks an object as being dirty, or changed.
''' </summary>
''' <remarks>
''' <para>
''' You should call this method in your business logic any time
''' the object's internal data changes. Any time any instance
''' variable changes within the object, this method should be called
''' to tell CSLA .NET that the object's data has been changed.
''' </para><para>
''' Marking an object as dirty does two things. First it ensures
''' that CSLA .NET will properly save the object as appropriate. Second,
''' it causes CSLA .NET to tell Windows Forms data binding that the
''' object's data has changed so any bound controls will update to
''' reflect the new values.
''' </para>
''' </remarks>
Protected Sub MarkDirty()
mIsDirty = True
OnIsDirtyChanged()
End Sub
Private Sub MarkClean()
mIsDirty = False
OnIsDirtyChanged()
End Sub
#End Region
#Region " IsSavable "
''' <summary>
''' Returns True if this object is both dirty and valid.
''' </summary>
''' <remarks>
''' An object is considered dirty (changed) if
''' <see cref="P:CSLA.BusinessBase.IsDirty" /> returns True. It is
''' considered valid if <see cref="P:CSLA.BusinessBase.IsValid" />
''' returns True. The IsSavable property is
''' a combination of these two properties. It is provided specifically to
''' enable easy binding to a Save or OK button on a form so that button
''' can automatically enable/disable as the object's state changes between
''' being savable and not savable.
''' </remarks>
''' <returns>A value indicating if this object is new.</returns>
<Browsable(False)> _
Public Overridable ReadOnly Property IsSavable() As Boolean
Get
Return IsDirty AndAlso IsValid
End Get
End Property
#End Region
#Region " IEditableObject "
<NotUndoable()> _
Private mParent As BusinessCollectionBase
<NotUndoable()> _
Private mBindingEdit As Boolean = False
Private mNeverCommitted As Boolean = True
''' <summary>
''' Used by <see cref="T:CSLA.BusinessCollectionBase" /> as a
''' child object is created to tell the child object about its
''' parent.
''' </summary>
''' <param name="parent">A reference to the parent collection object.</param>
Friend Sub SetParent(ByVal parent As BusinessCollectionBase)
If Not IsChild Then
Throw New Exception("Parent value can only be set for child objects")
End If
mParent = parent
End Sub
''' <summary>
''' Allow data binding to start a nested edit on the object.
''' </summary>
''' <remarks>
''' Data binding may call this method many times. Only the first
''' call should be honored, so we have extra code to detect this
''' and do nothing for subsquent calls.
''' </remarks>
Private Sub IEditableObject_BeginEdit() Implements IEditableObject.BeginEdit
Debug.WriteLine("beginedit " & Me.ToString)
If Not mBindingEdit Then
BeginEdit()
End If
End Sub
''' <summary>
''' Allow data binding to cancel the current edit.
''' </summary>
''' <remarks>
''' Data binding may call this method many times. Only the first
''' call to either IEditableObject.CancelEdit or
''' <see cref="M:CSLA.BusinessBase.IEditableObject_EndEdit">IEditableObject.EndEdit</see>
''' should be honored. We include extra code to detect this and do
''' nothing for subsequent calls.
''' </remarks>
Private Sub IEditableObject_CancelEdit() Implements IEditableObject.CancelEdit
Debug.WriteLine("canceledit " & Me.ToString)
If mBindingEdit Then
CancelEdit()
If IsNew AndAlso mNeverCommitted AndAlso EditLevel <= EditLevelAdded Then
' we're new and no EndEdit or ApplyEdit has ever been
' called on us, and now we've been canceled back to
' where we were added so we should have ourselves
' removed from the parent collection
If Not mParent Is Nothing Then
mParent.RemoveChild(Me)
End If
End If
End If
End Sub
''' <summary>
''' Allow data binding to apply the current edit.
''' </summary>
''' <remarks>
''' Data binding may call this method many times. Only the first
''' call to either IEditableObject.EndEdit or
''' <see cref="M:CSLA.BusinessBase.IEditableObject_CancelEdit">IEditableObject.CancelEdit</see>
''' should be honored. We include extra code to detect this and do
''' nothing for subsequent calls.
''' </remarks>
Private Sub IEditableObject_EndEdit() Implements IEditableObject.EndEdit
Debug.WriteLine("endedit " & Me.ToString)
If mBindingEdit Then
ApplyEdit()
End If
End Sub
#End Region
#Region " Begin/Cancel/ApplyEdit "
''' <summary>
''' Starts a nested edit on the object.
''' </summary>
''' <remarks>
''' <para>
''' When this method is called the object takes a snapshot of
''' its current state (the values of its variables). This snapshot
''' can be restored by calling <see cref="M:CSLA.BusinessBase.CancelEdit" />
''' or committed by calling <see cref="M:CSLA.BusinessBase.ApplyEdit" />.
''' </para><para>
''' This is a nested operation. Each call to BeginEdit adds a new
''' snapshot of the object's state to a stack. You should ensure that
''' for each call to BeginEdit there is a corresponding call to either
''' CancelEdit or ApplyEdit to remove that snapshot from the stack.
''' </para><para>
''' See Chapters 2 and 4 for details on n-level undo and state stacking.
''' </para>
''' </remarks>
Public Sub BeginEdit()
mBindingEdit = True
CopyState()
End Sub
''' <summary>
''' Cancels the current edit process, restoring the object's state to
''' its previous values.
''' </summary>
''' <remarks>
''' Calling this method causes the most recently taken snapshot of the
''' object's state to be restored. This resets the object's values
''' to the point of the last <see cref="M:CSLA.BusinessBase.BeginEdit" />
''' call.
''' </remarks>
Public Sub CancelEdit()
mBindingEdit = False
UndoChanges()
OnIsDirtyChanged()
End Sub
''' <summary>
''' Commits the current edit process.
''' </summary>
''' <remarks>
''' Calling this method causes the most recently taken snapshot of the
''' object's state to be discarded, thus committing any changes made
''' to the object's state since the last <see cref="M:CSLA.BusinessBase.BeginEdit" />
''' call.
''' </remarks>
Public Sub ApplyEdit()
mBindingEdit = False
mNeverCommitted = False
AcceptChanges()
End Sub
#End Region
#Region " IsChild "
<NotUndoable()> _
Private mIsChild As Boolean = False
Friend ReadOnly Property IsChild() As Boolean
Get
Return mIsChild
End Get
End Property
''' <summary>
''' Marks the object as being a child object.
''' </summary>
''' <remarks>
''' <para>
''' By default all business objects are 'parent' objects. This means
''' that they can be directly retrieved and updated into the database.
''' </para><para>
''' We often also need child objects. These are objects which are contained
''' within other objects. For instance, a parent Invoice object will contain
''' child LineItem objects.
''' </para><para>
''' To create a child object, the MarkAsChild method must be called as the
''' object is created. Please see Chapter 7 for details on the use of the
''' MarkAsChild method.
''' </para>
''' </remarks>
Protected Sub MarkAsChild()
mIsChild = True
End Sub
#End Region
#Region " Delete "
''' <summary>
''' Marks the object for deletion. The object will be deleted as part of the
''' next save operation.
''' </summary>
''' <remarks>
''' <para>
''' CSLA .NET supports both immediate and deferred deletion of objects. This
''' method is part of the support for deferred deletion, where an object
''' can be marked for deletion, but isn't actually deleted until the object
''' is saved to the database. This method is called by the UI developer to
''' mark the object for deletion.
''' </para><para>
''' To 'undelete' an object, use <see cref="M:CSLA.BusinessBase.BeginEdit" /> before
''' calling the Delete method. You can then use <see cref="M:CSLA.BusinessBase.CancelEdit" />
''' later to reset the object's state to its original values. This will include resetting
''' the deleted flag to False.
''' </para>
''' </remarks>
Public Sub Delete()
If Me.IsChild Then
Throw New NotSupportedException("Can not directly mark a child object for deletion - use its parent collection")
End If
MarkDeleted()
End Sub
' allow the parent object to delete us
' (Friend scope)
Friend Sub DeleteChild()
If Not Me.IsChild Then
Throw New NotSupportedException("Invalid for root objects - use Delete instead")
End If
MarkDeleted()
End Sub
#End Region
#Region " Edit Level Tracking (child only) "
' we need to keep track of the edit
' level when we were added so if the user
' cancels below that level we can be destroyed
Private mEditLevelAdded As Integer
' allow the collection object to use the
' edit level as needed (Friend scope)
Friend Property EditLevelAdded() As Integer
Get
Return mEditLevelAdded
End Get
Set(ByVal Value As Integer)
mEditLevelAdded = Value
End Set
End Property
#End Region
#Region " Clone "
''' <summary>
''' Creates a clone of the object.
''' </summary>
''' <returns>A new object containing the exact data of the original object.</returns>
Public Function Clone() As Object _
Implements ICloneable.Clone
Dim buffer As New MemoryStream
Dim formatter As New BinaryFormatter
Serialization.SerializationNotification.OnSerializing(Me)
formatter.Serialize(buffer, Me)
Serialization.SerializationNotification.OnSerialized(Me)
buffer.Position = 0
Dim temp As Object = formatter.Deserialize(buffer)
Serialization.SerializationNotification.OnDeserialized(temp)
Return temp
End Function
#End Region
#Region " BrokenRules, IsValid "
' keep a list of broken rules
Private mBrokenRules As New BrokenRules
''' <summary>
''' Override this method in your business class to
''' be notified when you need to set up business
''' rules.
''' </summary>
''' <remarks>
''' You should call AddBusinessRules from your object's
''' constructor methods so the rules are set up when
''' your object is created. This method will be automatically
''' called, if needed, when your object is serialized by
''' the DataPortal or by the Clone method.
''' </remarks>
Protected Overridable Sub AddBusinessRules()
End Sub
''' <summary>
''' Returns True if the object is currently valid, False if the
''' object has broken rules or is otherwise invalid.
''' </summary>
''' <remarks>
''' <para>
''' By default this property relies on the underling <see cref="T:CSLA.BrokenRules" />
''' object to track whether any business rules are currently broken for this object.
''' </para><para>
''' You can override this property to provide more sophisticated
''' implementations of the behavior. For instance, you should always override
''' this method if your object has child objects, since the validity of this object
''' is affected by the validity of all child objects.
''' </para>
''' </remarks>
''' <returns>A value indicating if the object is currently valid.</returns>
<Browsable(False)> _
Public Overridable ReadOnly Property IsValid() As Boolean
Get
Return mBrokenRules.IsValid
End Get
End Property
''' <summary>
''' Provides access to the readonly collection of broken business rules
''' for this object.
''' </summary>
''' <returns>A <see cref="T:CSLA.BrokenRules.RulesCollection" /> object.</returns>
Public Overridable Function GetBrokenRulesCollection() As BrokenRules.RulesCollection
Return mBrokenRules.GetBrokenRules
End Function
''' <summary>
''' Provides access to a text representation of all the descriptions of
''' the currently broken business rules for this object.
''' </summary>
''' <returns>Text containing the descriptions of the broken business rules.</returns>
Public Overridable Function GetBrokenRulesString() As String
Return mBrokenRules.ToString
End Function
''' <summary>
''' Provides access to the broken rules functionality.
''' </summary>
''' <remarks>
''' This property is used within your business logic so you can
''' easily call the <see cref="M:CSLA.BrokenRules.Assert(System.String,System.String,System.Boolean)" />
''' method to mark rules as broken and unbroken.
''' </remarks>
Protected ReadOnly Property BrokenRules() As BrokenRules
Get
Return mBrokenRules
End Get
End Property
''' <summary>
''' Provides access to the broken rules as a single string
''' </summary>
''' <remarks>
''' AyaNova - Provides access to a single string containing broken rules text
''' for easier binding to single text display rather than lists as in default
''' implementation
''' </remarks>
<Browsable(False)> _
Public ReadOnly Property BrokenRulesText() As String
Get
Return mBrokenRules.ToString()
End Get
End Property
#End Region
#Region " Data Access "
''' <summary>
''' Saves the object to the database.
''' </summary>
''' <remarks>
''' <para>
''' Calling this method starts the save operation, causing the object
''' to be inserted, updated or deleted within the database based on the
''' object's current state.
''' </para><para>
''' If <see cref="P:CSLA.BusinessBase.IsDeleted" /> is True the object
''' will be deleted. Otherwise, if <see cref="P:CSLA.BusinessBase.IsNew" />
''' is True the object will be inserted. Otherwise the object's data will
''' be updated in the database.
''' </para><para>
''' All this is contingent on <see cref="P:CSLA.BusinessBase.IsDirty" />. If
''' this value is False, no data operation occurs. It is also contingent on
''' <see cref="P:CSLA.BusinessBase.IsValid" />. If this value is False an
''' exception will be thrown to indicate that the UI attempted to save an
''' invalid object.
''' </para><para>
''' It is important to note that this method returns a new version of the
''' business object that contains any data updated during the save operation.
''' You MUST update all object references to use this new version of the
''' business object in order to have access to the correct object data.
''' </para><para>
''' You can override this method to add your own custom behaviors to the save
''' operation. For instance, you may add some security checks to make sure
''' the user can save the object. If all security checks pass, you would then
''' invoke the base Save method via <c>MyBase.Save()</c>.
''' </para>
''' </remarks>
''' <returns>A new object containing the saved values.</returns>
Public Overridable Function Save() As BusinessBase
If Me.IsChild Then
Throw New NotSupportedException("Can not directly save a child object")
End If
If EditLevel > 0 Then
Throw New ApplicationException("Object is still being edited and can not be saved")
End If
If Not IsValid Then
Throw New ValidationException("Object is not valid and can not be saved")
End If
If IsDirty Then
Return CType(DataPortal.Update(Me), BusinessBase)
Else
Return Me
End If
End Function
''' <summary>
''' Override this method to load a new business object with default
''' values from the database.
''' </summary>
''' <param name="Criteria">An object containing criteria values.</param>
Protected Overridable Sub DataPortal_Create(ByVal Criteria As Object)
Throw New NotSupportedException("Invalid operation - create not allowed")
End Sub
''' <summary>
''' Override this method to allow retrieval of an existing business
''' object based on data in the database.
''' </summary>
''' <param name="Criteria">An object containing criteria values to identify the object.</param>
Protected Overridable Sub DataPortal_Fetch(ByVal Criteria As Object)
Throw New NotSupportedException("Invalid operation - fetch not allowed")
End Sub
''' <summary>
''' Override this method to allow insert, update or deletion of a business
''' object.
''' </summary>
Protected Overridable Sub DataPortal_Update()
Throw New NotSupportedException("Invalid operation - update not allowed")
End Sub
''' <summary>
''' Override this method to allow immediate deletion of a business object.
''' </summary>
''' <param name="Criteria">An object containing criteria values to identify the object.</param>
Protected Overridable Sub DataPortal_Delete(ByVal Criteria As Object)
Throw New NotSupportedException("Invalid operation - delete not allowed")
End Sub
''' <summary>
''' Returns the specified database connection string from the application
''' configuration file.
''' </summary>
''' <remarks>
''' The database connection string must be in the <c>appSettings</c> section
''' of the application configuration file. The database name should be
''' prefixed with 'DB:'. For instance, <c>DB:mydatabase</c>.
''' </remarks>
''' <param name="DatabaseName">Name of the database.</param>
''' <returns>A database connection string.</returns>
'Protected Function DB(ByVal DatabaseName As String) As String
' Return System.CXXXonfiguration.ConfigurationManager.AppSettings("DB:" & DatabaseName)
'End Function
#End Region
#Region " IDataErrorInfo "
Private ReadOnly Property [Error]() As String Implements System.ComponentModel.IDataErrorInfo.Error
Get
If Not IsValid Then
If BrokenRules.GetBrokenRules.Count = 1 Then
Return BrokenRules.GetBrokenRules.Item(0).Description
Else
Return BrokenRules.ToString
End If
End If
End Get
End Property
Private ReadOnly Property Item(ByVal columnName As String) As String Implements System.ComponentModel.IDataErrorInfo.Item
Get
If Not IsValid Then
Return BrokenRules.GetBrokenRules.RuleForProperty(columnName).Description
End If
End Get
End Property
#End Region
#Region " ISerializationNotification "
''' <summary>
''' This method is called on a newly deserialized object
''' after deserialization is complete.
''' </summary>
Protected Overridable Sub Deserialized() _
Implements CSLA.Serialization.ISerializationNotification.Deserialized
AddBusinessRules()
' now cascade the call to all child objects/collections
Dim fields() As FieldInfo
Dim field As FieldInfo
' get the list of fields in this type
fields = Me.GetType.GetFields( _
BindingFlags.NonPublic Or _
BindingFlags.Instance Or _
BindingFlags.Public)
For Each field In fields
If Not field.FieldType.IsValueType AndAlso _
Not Attribute.IsDefined(field, GetType(NotUndoableAttribute)) Then
' it's a ref type, so check for ISerializationNotification
Dim value As Object = field.GetValue(Me)
If TypeOf value Is Serialization.ISerializationNotification Then
DirectCast(value, Serialization.ISerializationNotification).Deserialized()
End If
End If
Next
End Sub
''' <summary>
''' This method is called on the original instance of the
''' object after it has been serialized.
''' </summary>
Protected Overridable Sub Serialized() _
Implements CSLA.Serialization.ISerializationNotification.Serialized
' cascade the call to all child objects/collections
Dim fields() As FieldInfo
Dim field As FieldInfo
' get the list of fields in this type
fields = Me.GetType.GetFields( _
BindingFlags.NonPublic Or _
BindingFlags.Instance Or _
BindingFlags.Public)
For Each field In fields
If Not field.FieldType.IsValueType AndAlso _
Not Attribute.IsDefined(field, GetType(NotUndoableAttribute)) Then
' it's a ref type, so check for ISerializationNotification
Dim value As Object = field.GetValue(Me)
If TypeOf value Is Serialization.ISerializationNotification Then
DirectCast(value, Serialization.ISerializationNotification).Serialized()
End If
End If
Next
End Sub
''' <summary>
''' This method is called before an object is serialized.
''' </summary>
Protected Overridable Sub Serializing() _
Implements CSLA.Serialization.ISerializationNotification.Serializing
' cascade the call to all child objects/collections
Dim fields() As FieldInfo
Dim field As FieldInfo
' get the list of fields in this type
fields = Me.GetType.GetFields( _
BindingFlags.NonPublic Or _
BindingFlags.Instance Or _
BindingFlags.Public)
For Each field In fields
If Not field.FieldType.IsValueType AndAlso _
Not Attribute.IsDefined(field, GetType(NotUndoableAttribute)) Then
' it's a ref type, so check for ISerializationNotification
Dim value As Object = field.GetValue(Me)
If TypeOf value Is Serialization.ISerializationNotification Then
DirectCast(value, Serialization.ISerializationNotification).Serializing()
End If
End If
Next
End Sub
#End Region
End Class