Tải bản đầy đủ (.pdf) (69 trang)

Expert VB 2005 Business Objects Second Edition phần 7 pps

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (1.06 MB, 69 trang )

Friend Shared Function NewEditableChild() As EditableChild
Return New EditableChild
End Function
Then the DataPortal_Create() method can be removed, since it won’t be used. The default
constructor is then used to set any default values that are hard-coded into the class.
Switchable Objects
It’s possible that some classes must be instantiated as root objects on some occasions and as child
objects on others. This can be handled by conditionally calling
MarkAsChild(), based on how the
object is being created.
■Note In most cases, the need for a switchable object indicates a flawed object model. While there are excep-
tions for which this makes sense, you should carefully examine your object model to see if there’s a simpler
solution before implementing a switchable object.
Conditionally calling MarkAsChild() typically can’t be done in the default constructor,
because there’s no way to determine whether the object is being created as a r
oot or a child object
at that point. Instead, you need to go back to your object’s life cycle to see where you
can make
this decision. In fact, since the default is for an object to be a root object, all you need to do is
determine the paths by which a
child object can be created, and make sure to call MarkAsChild()
only in those cases.
The template for creating a “switchable” object is the same as the editable root template, with
the following exceptions:
• Dual criteria objects
• Dual create and fetch factory methods
• Dual create and fetch data access methods
Let’s discuss each change in turn.
Dual Criteria Classes
The object’s criteria must now include a flag to indicate whether the object is being created as a root
or a child object (this is in addition to any object-specific criteria fields in this class). This can be


done either by adding an actual flag field to the
Criteria class or by creating a second criteria class.
I pr
efer the second appr
oach as it makes the code simpler overall.
Remember that for a child object, the criteria class is only used for the create operation, and so
it typically doesn’t need any actual criteria data. The result is that there are two criteria classes; for
example:
<Serializable()> _
Private Class RootCriteria
Private mId As Integer
Public ReadOnly Property Id() As Integer
Get
Return mId
End Get
End Property
CHAPTER 7 ■ USING THE CSLA .NET BASE CLASSES 391
6315_c07_final.qxd 4/7/06 2:19 PM Page 391
Public Sub New(ByVal id As Integer)
mId = id
End Sub
Public Sub New()
End Sub
End Class
<Serializable()> _
Private Class ChildCriteria
End Class
These two classes will be used to differentiate the way the object should be created.
Dual Factory Methods
Instead of single factory methods to create and retrieve the object, there will be two methods for

each operation: one
Public, the other Friend.
Public Shared Function NewSwitchable() As SwitchableObject
Return DataPortal.Create(Of SwitchableObject)(New RootCriteria())
End Function
Friend Shared Function NewSwitchableChild() As SwitchableObject
Return DataPortal.Create(Of SwitchableObject)(New ChildCriteria())
End Function
Public Shared Function GetSwitchableRoot( _
ByVal id As Integer) As SwitchableObject
Return DataPortal.Create(Of SwitchableObject)(New RootCriteria(id))
End Function
Friend Shared Function GetSwitchableChild( _
ByVal dr As SqlDataReader) As SwitchableObject
Return New SwitchableObject(dr)
End Function
Notice how the NewSwitchable() methods are each designed. The Public version (used to cre-
ate a root object) uses the
RootCriteria object, while the Friend version (called by a parent object
to create a child object) uses
ChildCriteria. The DataPortal_Create() methods, which follow, are
called based on the type of the cr
iter
ia object.
The two
GetSwitchable() methods ar
e even more different. The
Public one is called b
y UI code
to retrieve a root object. In this case, the data portal is called to retrieve the object based on the sup-

plied criteria. The
Friend one follows the pattern for child objects, accepting a data reader from the
parent object and passing it along to a
Private constructor, which in turn calls a Private Fetch()
method.
D
ual D
ata A
ccess Methods
The data access methods that handle create and fetch operations are different for a root and child
object. Because of this, these methods are duplicated in a switchable object. In most cases, they can
delegate to a shar
ed implementation that is private to the class. For instance:
CHAPTER 7 ■ USING THE CSLA .NET BASE CLASSES392
6315_c07_final.qxd 4/7/06 2:19 PM Page 392
Private Overloads Sub DataPortal_Create(ByVal criteria As RootCriteria)
DoCreate()
End Sub
Private Overloads Sub DataPortal_Create(ByVal criteria As ChildCriteria)
MarkAsChild()
DoCreate()
End Sub
Private Sub DoCreate()
' load default values from database here
End Sub
Notice how the overload of DataPortal_Create() that accepts a ChildCriteria object calls
MarkAsChild(), while the other does not. This ensures that the object is marked as a child object
when appropriate.
Similarly, the data-retrieval operations are duplicated:
Private Overloads Sub DataPortal_Fetch(ByVal criteria As RootCriteria)

' TODO: create data reader to load values
Using dr As SqlDataReader = Nothing
DoFetch(dr)
End Using
End Sub
Private Sub Fetch(ByVal dr As SqlDataReader)
MarkAsChild()
DoFetch(dr)
End Sub
Private Sub DoFetch(ByVal dr As SqlDataReader)
' TODO: load values
End Sub
If the object is being loaded from the UI, then it is treated as a root object and DataPortal_
Fetch()
is called, passing in appropr
iate criteria. This method opens the database, and sets up and
executes a database command object to get back a data reader. That data reader is then passed to
a central
DoFetch() helper method to copy the data from the data reader into the object’s fields.
On the other hand, if the object is being loaded from a parent object as a child, then its para-
meterized constructor is called, which in turn calls the
Fetch() method. This method calls
MarkAsChild() to mar
k the object as a child, and then the
DoFetch() helper is called to cop
y the
data from the data reader into the object’s fields.
O
bject C
reation Without Defaults

When creating the object using the New keyword instead of calling DataPortal.Create(), the Friend
factor
y method can dir
ectly
call
MarkAsChild(), as sho
wn her
e:
Friend Shared Function NewSwitchableChild() As SwitchableObject
Dim obj As New SwitchableObject
obj.MarkAsChild()
Return obj
End Function
From the parent object’s perspective, there’s no difference—it just calls the factory method;
but this appr
oach is faster because it doesn’t load default values from the database.
CHAPTER 7 ■ USING THE CSLA .NET BASE CLASSES 393
6315_c07_final.qxd 4/7/06 2:19 PM Page 393
Editable Root Collection
At times, applications need to retrieve a collection of child objects directly. To do this, you need to
create a root collection object. For instance, the application may have a Windows Forms UI consist-
ing of a
DataGridView control that displays a collection of Contact objects. If the root object is a
collection of child
Contact objects, the UI developer can simply bind the collection to the
DataGridView, and the user can do in-place editing of the objects within the grid.
This approach means that all the child objects are handled as a single unit in terms of data access.
T
hey are loaded into the collection to start with, so the user can interact with all of them, and then
save them all at once when all edits are complete. This is only subtly different from having a regular

root object that has a collection of child objects. Figure 7-10 shows the regular root object approach
on the left and the collection root object approach on the right.
This approach isn’t recommended when there are large numbers of potential child objects,
because the retrieval pr
ocess can become too slow, but it can be very useful in cases where you can
specify criteria to limit the number of objects returned. To create an editable root collection object,
use a template like this:
<Serializable()> _
Public Class EditableRootList
Inherits BusinessListBase(Of EditableRootList, EditableChild)
#Region " Authorization Rules "
Public Shared Function CanAddObject() As Boolean
' TODO: customize to check user role
Return ApplicationContext.User.IsInRole("")
End Function
Public Shared Function CanGetObject() As Boolean
' TODO: customize to check user role
Return ApplicationContext.User.IsInRole("")
End Function
Public Shared Function CanEditObject() As Boolean
' TODO: customize to check user role
Return ApplicationContext.User.IsInRole("")
End Function
CHAPTER 7 ■ USING THE CSLA .NET BASE CLASSES394
Figure 7-10. Comparing simple root objects (left) and collection root objects (right)
6315_c07_final.qxd 4/7/06 2:19 PM Page 394
Public Shared Function CanDeleteObject() As Boolean
' TODO: customize to check user role
Return ApplicationContext.User.IsInRole("")
End Function

#End Region
#
Region " Factory Methods "
Public Shared Function NewEditableRootList() As EditableRootList
Return New EditableRootList()
End Function
Public Shared Function GetEditableRootList(ByVal id As Integer) As EditableRootList
Return DataPortal.Fetch(Of EditableRootList)(New Criteria(id))
End Function
Private Sub New()
' require use of factory methods
End Sub
#End Region
#Region " Data Access "
<Serializable()> _
Private Class Criteria
Private mId As Integer
Public ReadOnly Property Id() As Integer
Get
Return mId
End Get
End Property
Public Sub New(ByVal id As Integer)
mId = id
End Sub
End Class
Private Overloads Sub DataPortal_Fetch(ByVal criteria As Criteria)
' TODO: load values
RaiseListChangedEvents = False
Using dr As SqlDataReader = Nothing

While dr.Read
Add(EditableChild.GetEditableChild(dr))
End While
End Using
RaiseListChangedEvents = True
End Sub
Protected Overrides Sub DataPortal_Update()
RaiseListChangedEvents = False
For Each item As EditableChild In DeletedList
item.DeleteSelf()
Next
DeletedList.Clear()
CHAPTER 7 ■ USING THE CSLA .NET BASE CLASSES 395
6315_c07_final.qxd 4/7/06 2:19 PM Page 395
For Each item As EditableChild In Me
If item.IsNew Then
item.Insert(Me)
Else
item.Update(Me)
End If
Next
RaiseListChangedEvents = True
End Sub
#End Region
End Class
The Authorization Rules region contains the standard Shared methods discussed earlier for
editable root objects. Since collection objects don’t have detailed properties, there’s no need or
support for the
AddAuthorizationRules() method.
The

Factory Methods region implements factory methods to create, retrieve, and (optionally)
delete the collection. The create method simply uses the
New keyword to create an instance of the
collection. There’s no need to load default values for the collection itself. The retrieve and delete
methods rely on the data portal to do much of the work, ultimately delegating the call to the
appropriate
DataPortal_XYZ method.
In the
Data Access region, the DataPortal_Fetch() method is responsible for getting the data
from the database, typically via a data reader. It then calls the
Shared factory method of the child
class for each row in the data reader, thereby allowing each child object to load its data. The
Shared
factor
y method in the child class calls its o
wn
Private constr
uctor to actually load the data fr
om
the data reader.
The
DataPortal_Update() method
must loop through all the child objects contained in the
deleted object collection, calling each object’s
DeleteSelf() method in turn. An alternative is to
have the collection object dynamically generate a SQL statement to delete all the items in the
DeleteList with a single call. The specific implementation is up to the business developer and
may vary depending on the database design.
Once the child objects have been deleted from the database, that list is cleared. Then the active
child objects are either inserted or updated based on their

IsNew property value.
■Note It’s critical that the deleted child objects be processed first.
It’s quite possible for the user to delete a child object from the collection, and then add a new
child object
with the same primary key value. This means that the collection will have the original
child object mar
ked as deleted in the list of deleted child objects, and the new child object in the list
of active objects. This new object will have its
IsNew property set to True because it’s a new object.
If the original child object isn’t deleted first, the insertion of the new child object will fail.
Thus, the code first processes the list of deleted child objects, and then moves on to process the
list of activ
e child objects
.
B
oth the
DataPortal_Fetch() and DataPortal_Update() methods set the
RaiseListChangedEvents property to False before changing the collection, and then restore it to
True once the operation is complete. Setting this property to False tells the base BindingList(Of T)
class to stop raising the ListChanged event. When doing batches of updates or changes to a collec-
tion, this
can increase performance.
CHAPTER 7 ■ USING THE CSLA .NET BASE CLASSES396
6315_c07_final.qxd 4/7/06 2:19 PM Page 396
Editable Child Collection
The most common type of collection is one that is contained within a parent object to manage
a collection of child objects for that parent; like
ProjectResources and ResourceAssignments in the
sample application.
■Tip Note that the parent object here might be a root object, or it might be a child itself—child objects can be

nested, if that’s what the business object model requires. In other words, this concept supports not only root-child,
but also child-grandchild and grandchild-to-great-grandchild relationships.
A child collection class inherits from BusinessListBase and calls MarkAsChild() during its cre-
ation process to indicate that it’s operating in child mode. This also means that it won’t be directly
retrieved or updated by the
DataPortal, but instead will be retrieved or updated by its parent object:
<Serializable()> _
Public Class EditableChildList
Inherits BusinessListBase(Of EditableChildList, EditableChild)
#Region " Factory Methods "
Friend Shared Function NewEditableChildList() As EditableChildList
Return New EditableChildList
End Function
Friend Shared Function GetEditableChildList( _
ByVal dr As SqlDataReader) As EditableChildList
Return New EditableChildList(dr)
End Function
Private Sub New()
MarkAsChild()
End Sub
Private Sub New(ByVal dr As SqlDataReader)
MarkAsChild()
Fetch(dr)
End Sub
#End Region
#Region " Data Access "
Private Sub Fetch(ByVal dr As SqlDataReader)
RaiseListChangedEvents = False
While dr.Read
Add(EditableChild.GetEditableChild(dr))

End While
RaiseListChangedEvents = True
End Sub
Friend Sub Update(ByVal parent As Object)
CHAPTER 7 ■ USING THE CSLA .NET BASE CLASSES 397
6315_c07_final.qxd 4/7/06 2:19 PM Page 397
RaiseListChangedEvents = False
For Each item As EditableChild In DeletedList
item.DeleteSelf()
Next
DeletedList.Clear()
For Each item As EditableChild In Me
If item.IsNew Then
item.Insert(parent)
Else
item.Update(parent)
End If
Next
RaiseListChangedEvents = True
End Sub
#End Region
End Class
As you can see, this code is very similar to a root collection in structure. The differences start
with the
factory methods. Since only a parent object can create or fetch an instance of this class, the
Shared factory methods are scoped as Friend. The Shared method to create an object simply returns
a new collection object. As with the
EditableChild template, the constructor calls MarkAsChild() to
indicate that this is a child object.
Likewise, the

Shared method to load the child collection with data creates a new collection
object and then calls a parameterized constructor just like in the
EditableChild template. That con-
structor calls a
Fetch() method to load the data.
The
Update() method is identical to the DataPortal_Update() method in the EditableRootList.
It loops through the list of deleted child objects, calling their
DeleteSelf() methods, and then loops
through the active child objects
, calling
Insert() or Update() as appropriate
.
Notice, however, that the
Update() method accepts a reference to the parent object as a param-
eter, and this value is provided to the child objects’
Insert() and Update() methods. As discussed
earlier, this allows the child objects to use data from the parent object as needed for things like for-
eign key values and so forth.
Read-Only Business Objects
Sometimes, an application may need an object that provides data in a read-only fashion. For a read-
only list of data, ther
e
’s
ReadOnlyListBase; but if the r
equir
ement is for a single object containing
read-only data, it should inherit from
ReadOnlyBase. This is one of the simplest types of object to
create, since it does nothing more than retrieve and return data, as shown here:

<Serializable()> _
Public Class ReadOnlyRoot
Inherits ReadOnlyBase(Of ReadOnlyRoot)
#Region " Business Methods "
Private mId As Integer
CHAPTER 7 ■ USING THE CSLA .NET BASE CLASSES398
6315_c07_final.qxd 4/7/06 2:19 PM Page 398
Public ReadOnly Property Id() As Integer
Get
CanReadProperty(True)
Return mId
End Get
End Property
Protected Overrides Function GetIdValue() As Object
Return mId
End Function
#End Region
#Region " Authorization Rules "
Protected Overrides Sub AddAuthorizationRules()
' TODO: add authorization rules
'AuthorizationRules.AllowRead("", "")
End Sub
Public Shared Function CanGetObject() As Boolean
' TODO: customize to check user role
'Return ApplicationContext.User.IsInRole("")
Return True
End Function
#End Region
#Region " Factory Methods "
Public Shared Function GetReadOnlyRoot(ByVal id As Integer) As ReadOnlyRoot

Return DataPortal.Create(Of ReadOnlyRoot)(New Criteria(id))
End Function
Private Sub New()
' require use of factory methods
End Sub
#End Region
#Region " Data Access "
<Serializable()> _
Private Class Criteria
Private mId As Integer
Public ReadOnly Property Id() As Integer
Get
Return mId
End Get
End Property
Public Sub New(ByVal id As Integer)
mId = id
End Sub
End Class
CHAPTER 7 ■ USING THE CSLA .NET BASE CLASSES 399
6315_c07_final.qxd 4/7/06 2:19 PM Page 399
Private Overloads Sub DataPortal_Fetch(ByVal criteria As Criteria)
' load values
End Sub
#End Region
End Class
Like other business objects, a read-only object will have instance fields that contain its data.
I
t will typically also have read-only properties or methods that allow client code to retrieve values.
As long as they don’t change the state of the object, these may even be calculated values.

Like editable objects, read-only objects must override the
GetIdValue() method and provide
a unique identifying value for the object. This value is used by the
Equals(), GetHashCode(), and
ToString() implementations in the ReadOnlyBase class. If those implementations are inadequate
for your needs, you can override them and provide your own implementations.
The
AddAuthorizationRules() method only needs to add roles for read access, since no proper-
ties should be implemented to allow altering of data. It also includes a
CanGetObject() method so
that the UI can enable or disable options based on that result.
In the
Factory Methods region, there’s just one factory method that retrieves the object by call-
ing
DataPortal.Fetch(). This means there’s also a Criteria class, which should be modified to
contain the criteria data needed to select the correct object for retrieval.
The
Data Access region just contains DataPortal_Fetch(). Of course, there’s no need to support
updating or deleting of a read-only object.
Read-Only Collections of Objects
Applications commonly retrieve read-only collections of objects. The CSLA .NET framework
includes
the
ReadOnlyListBase class to help cr
eate r
ead-only collections. It throws an exception any
time there’s an attempt to change which items are in the collection by adding or removing objects.
■Note The template shown here is for the most common scenario: a read-only root collection. You can adapt
this to provide a read-only child collection if desired.
However, there’s no way for the collection object to stop client code from interacting with the

child objects themselves. Typically, the items in the collection will expose only read-only proper-
ties and methods. If read-write objects are put into the collection, client code will be able to alter
their data. A read-only collection only guarantees that objects can’t be added or removed from the
collection.
The
child objects may be derived from
ReadOnlyBase, but mor
e often they will be simple
objects that don’t inherit from any CSLA .NET base class. The only requirements for these child
objects is that they are implemented with read-only properties and that they are marked as
<Serializable()>.
The code for a typical r
ead-only collection object looks like this:
<Serializable()> _
Public Class ReadOnlyList
Inherits ReadOnlyListBase(Of ReadOnlyList, ReadOnlyChild)
#Region " Authorization Rules "
CHAPTER 7 ■ USING THE CSLA .NET BASE CLASSES400
6315_c07_final.qxd 4/7/06 2:19 PM Page 400
Public Shared Function CanGetObject() As Boolean
' TODO: customize to check user role
'Return ApplicationContext.User.IsInRole("")
Return True
End Function
#End Region
#Region " Factory Methods "
Public Shared Function GetList(ByVal filter As String) As ReadOnlyList
Return DataPortal.Fetch(Of ReadOnlyList)(New Criteria(filter))
End Function
Private Sub New()

' require use of factory methods
End Sub
#End Region
#Region " Data Access "
<Serializable()> _
Private Class Criteria
Private mFilter As String
Public ReadOnly Property Filter() As String
Get
Return mFilter
End Get
End Property
Public Sub New(ByVal filter As String)
mFilter = filter
End Sub
End Class
Protected Overrides Sub DataPortal_Fetch(ByVal criteria As Object)
RaiseListChangedEvents = False
IsReadOnly = False
' load values
Using dr As SqlDataReader = Nothing
While dr.Read
Add(ReadOnlyChild.GetReadOnlyChild(dr))
End While
End Using
IsReadOnly = True
RaiseListChangedEvents = True
End Sub
#End Region
End Class

CHAPTER 7 ■ USING THE CSLA .NET BASE CLASSES 401
6315_c07_final.qxd 4/7/06 2:19 PM Page 401
In the Authorization Rules region, there’s just the CanGetObject() method for use by UI code.
In the
Factory Methods region, there’s a factory method to return a collection loaded with data.
It calls
DataPortal.Fetch(), and so there’s a Criteria class as well as a Private constructor. This is
no different from the classes you’ve looked at already.
Finally, the
DataPortal_Fetch() method loads the object with data from the database. To do
this, the
IsReadOnly flag is set to False, the data is loaded from the database, and then IsReadOnly
i
s set to
T
rue
.
When
I
sReadOnly
i
s set to
T
rue
,
any attempt to add or remove items from the collec-
tion will result in an exception being thrown. Temporarily setting it to
False allows the code to
insert all the appropriate child objects into the collection.
Also note that

RaiseListChangedEvents is set to False and then True in a similar manner. To
improve performance, this suppresses the raising of
ListChanged events while the data is being
loaded.
Command Objects
Command objects can be used in many ways. They may be called directly by UI code to execute
arbitrary code on the application server, but even more often they are used
within other business
objects to execute code on the application server. A primary example is when a normal editable
business object wants to implement an
Exists() command. You’ll see an example of this concept
in the
Project and Resource objects in Chapter 8.
If the UI is to directly use the object, the class will be
Public, while if it is to be used within the
context of another business object, it will be a
Private nested class within that business object.
Either way
, the structure of a command object is the same, as shown here:
<Serializable()> _
Public Class CommandObject
Inherits CommandBase
#Region " Authorization Rules "
Public Shared Function CanExecuteCommand() As Boolean
' to see if user is authorized
'Return Csla.ApplicationContext.User.IsInRole("")
Return True
End Function
#End Region
#Region " Client-side Code "

Private mResult As Boolean
Public ReadOnly Property Result() As Boolean
Get
Return mResult
End Get
End Property
Private Sub BeforeServer()
' implement code to run on client
' before server is called
End Sub
CHAPTER 7 ■ USING THE CSLA .NET BASE CLASSES402
6315_c07_final.qxd 4/7/06 2:19 PM Page 402
Private Sub AfterServer()
' implement code to run on client
' after server is called
End Sub
#End Region
#Region " Factory Methods "
Public Shared Function TheCommand() As Boolean
Dim cmd As New CommandObject
cmd.BeforeServer()
cmd = DataPortal.Execute(Of CommandObject)(cmd)
cmd.AfterServer()
Return cmd.Result
End Function
Private Sub New()
' require use of factory methods
End Sub
#End Region
#Region " Server-side Code "

Protected Overrides Sub DataPortal_Execute()
' implement code to run on server
' here - and set result value(s)
mResult = True
End Sub
#End Region
End Class
This class structure is quite a bit different from anything you’ve seen so far.
The
A
uthorization R
ules
r
egion isn

t bad—it just implements a
CanExecuteCommand() method so
that the UI can easily determine whether the current user is authorized to execute the command.
The
Factory Methods region is similar in structure to many of the other templates shown thus
far, but its implementation is different. Rather than passing a
Criteria object to the server, the
Execute() method cr
eates and initializ
es an instance of the command object itself.
That instance
is then sent to the ser
ver through the data portal, which invokes the
DataPortal_Execute() method
on the server.

The
Execute() method also calls the BeforeServer() and AfterServer() methods, which are
found in the
Client-side Code region.
The idea behind this is that the command object can be initializ
ed on the client with any data
required to perform the server-side processing. In fact, the object could do some processing or data
gathering on the client before or after it is transferred to the server through the data portal. The
client-side code may be as complex as needed to prepare to run the server-side code.
Then the data por
tal mo
v
es the object to the application server and calls the
DataPortal_
Execute()
method in the Server-side Code region. The code in this method runs on the server and
CHAPTER 7 ■ USING THE CSLA .NET BASE CLASSES 403
6315_c07_final.qxd 4/7/06 2:19 PM Page 403
can do any server-side work. This might be something as simple as doing a quick database
lookup, or it might be a complex server-side workflow. The code in this method can create and
interact with other business objects (all on the server of course). It can interact directly with the
database, or any other server-side resources, such as the server’s file system or third-party soft-
ware installed on the server.
Command objects are powerful because they provide high levels of flexibility for running both
client and server code in a coordinated manner.
Name/Value List Objects
Perhaps the simplest business object to create is a name/value list that inherits from the
NameValueListBase class in the CSLA .NET framework. The base class provides almost all the
functionality needed, except the actual data access and factory method.
Because name/value list data is often very static, changing rarely, it is often desirable to cache

the data. This can be done in the factory method, as shown in the template:
<Serializable()> _
Public Class NameValueList
Inherits NameValueListBase(Of Integer, String)
#Region " Factory Methods "
Private Shared mList As NameValueList
Public Shared Function GetList() As NameValueList
If mList Is Nothing Then
mList = DataPortal.Fetch(Of NameValueList) _
(New Criteria(GetType(NameValueList)))
End If
Return mList
End Function
Public Shared Sub InvalidateCache()
mList = Nothing
End Sub
Private Sub New()
' require use of factory methods
End Sub
#End Region
#Region " Data Access "
Protected Overrides Sub DataPortal_Fetch(ByVal criteria As Object)
RaiseListChangedEvents = False
IsReadOnly = False
' TODO: load values
Using dr As SqlDataReader = Nothing
While dr.Read
Add(New NameValueListBase(Of Integer, String). _
NameValuePair(dr.GetInt32(0), dr.GetString(1)))
End While

End Using
IsReadOnly = True
RaiseListChangedEvents = True
CHAPTER 7 ■ USING THE CSLA .NET BASE CLASSES404
6315_c07_final.qxd 4/7/06 2:19 PM Page 404
End Sub
#End Region
End Class
The Factory Methods region declares a Shared field to hold the list once it is retrieved. Notice
how the factory method returns the cached list if it is present; only calling the data portal to retrieve
the data if the list is
Nothing. There’s also an InvalidateCache() method that can be called to force a
r
eload of the data if needed.
This caching behavior is optional—if it doesn’t fit your need, then use a factory method like
this:
Public Shared Function GetNameValueList() As NameValueList
Return DataPortal.Fetch(Of NameValueList) _
(New Criteria(GetType(NameValueList)))
End Function
The Data Access region contains only a DataPortal_Fetch() method, which connects to the
database and retrieves the name/value data. The
NameValueListBase class defines a strongly typed
NameValuePair class, which is used to store each element of data. For each row of data from the
database, a
NameValuePair object is created and added to the collection.
Notice the use of the
IsReadOnly property to temporarily unlock the collection and then relock
it so it becomes r
ead-only once the data has been loaded. The

RoleList class in the sample applica
-
tion in Chapter 8 illustrates a complete implementation of a name/value list.
Conclusion
This chapter has discussed the basic concepts and requirements for all business classes based on
CSLA .NET. I discussed the life cycle of business objects, and walked through the creation, retrieval,
update, and delete processes.
The basic structure of each type of business class was covered. There are common require-
ments, including making all the classes serializable, implementing a common set of code regions
for clarity of code, including a
Private constructor, and having a nested Criteria class. There are
also specific structures or templates for each type of business object, including the following:
• Editable root
• Editable child

S
witchable object
• Editable root collection
• Editable child collection

Read-only object
• Read-only collection
• Command object

Name/value list
Chapter 8 will implement the sample pr
oject tracker application classes based on these
concepts and templates.
CHAPTER 7 ■ USING THE CSLA .NET BASE CLASSES 405
6315_c07_final.qxd 4/7/06 2:19 PM Page 405

6315_c07_final.qxd 4/7/06 2:19 PM Page 406
Business Object Implementation
This chapter will implement the business objects designed in Chapter 6 by following the business
object coding structures from Chapter 7. This chapter will illustrate how to write code to create
business objects that enjoy all the features and capabilities built into the CSLA .NET framework.
The great thing is that almost all the code in the business objects will be business focused. Each
business class will largely consist of three areas:
• UI-focused business properties and methods

Shared factory methods to support the class-in-charge model (as discussed in Chapter 1)
• Data access methods (
DataPortal_XYZ, as discussed in Chapter 4)
The object model created in Chapter 6 includes editable objects and collections, parent-child
collection relationships, read-only lists, a name/value list, and command objects. It also makes use
of custom authentication, requiring the creation of custom principal and identity objects. The cus-
tom identity object will be a read-only object.
In the end, the sample application makes use of every CSLA .NET base class available.
In this chapter, I won’t walk through all the code in the
ProjectTracker business object libr
ary.
Instead, I’ll focus on providing examples of how to implement common types of business objects
and how to establish various object relationships. For the complete code, please refer to the code
download for this book, available at
www.apress.com.
ProjectTracker Objects
Chapter 6 covered the creation of an object model for the sample project-tracking application.
This object model, shown in Figure 8-1, includes some editable root business objects (
Project
and Resource), some editable child objects (ProjectResource and ResourceAssignment), some
collections of child objects (

ProjectResources and ResourceAssignments), and a name/value list
(
RoleList). I
t also includes two read-only collections (
ProjectList and ResourceList) and an
editable root collection (
Roles).
The solid arrows indicate using relationships, where one object uses another for some
purpose—either as a parent-child relationship or for collaboration. The dashed lines indicate
navigation, wher
e a method exists so that the UI dev
eloper can easily get a r
eference to the target
object. Of course, Chapter 6 has complete details on the object model.
By implementing these objects, you should get a good feel for the practical process of taking
the class templates from Chapter 7 and applying them to the creation of real business classes.
407
CHAPTER 8
■ ■ ■
6315_c08_final.qxd 4/7/06 2:00 PM Page 407
Setting Up the Project
Technically, business classes can be placed in a Class Library, Windows Application, or website-
type project in Visual Studio. But to get the full advantages of mobile objects and the CSLA .NET
framework, they really must be placed in a Class Library project.
By putting the business classes in a DLL, it becomes possible for the business objects to be
used by various different “front ends.” This is important, because Chapters 9 through 11 will use
exactly the same business DLL to create Windows Forms, Web Forms, and Web Services interfaces.
It’s equally important in “real-world” applications, since they too often have multiple interfaces.
Even if an application starts with a single interface, the odds are good that at some time in the
future, it will need a new one.

I prefer to collect all my projects under a single Visual Studio solution, including the business
library, the Windows and Web UI projects, and the Web Service project. To this end, you’ll find all
the code in a
ProjectTracker20vb solution in the code download, with each project and website
contained inside.
The
ProjectTracker.Library Class Library project is a library of business classes based on
the design from Chapter 6. This library contains all the business logic for the
ProjectTracker
application.
The code in
ProjectTracker.Library uses the CSL
A .NET fr
amewor
k, and so the pr
oject r
efer-
ences
Csla.dll. This is a file reference that is set up through the Add Reference dialog box, as shown
in Figure 8-2.
408 CHAPTER 8 ■ BUSINESS OBJECT IMPLEMENTATION
Figure 8-1. ProjectTracker application classes
6315_c08_final.qxd 4/7/06 2:00 PM Page 408
This makes the CSLA .NET framework available for use within the project, and is typically all
that is required.
However, remember that
Csla.dll includes code that might run in Enterprise Services (COM+).
In particular, this includes both the
ServicedDataPortal and EnterpriseServicesPortal components
of the data por

tal, as discussed in Chapter 4. If you choose to use the Enterprise Services features, then
you may need to reference
System.EnterpriseServices.dll as well.
The specific case in which this is required is if you configure the data portal to run locally
in the client process
and you mark your DataPortal_XYZ methods with <Transactional
(TransactionTypes.EnterpriseServices)>
. This combination causes the direct use of a
ServicedComponent within the client process, and so requires a reference to System.
EnterpriseServices.dll
. It also has the side effect of requiring that Csla.dll be registered with
COM+, which is handled automatically if the user is an administrator on the client workstation,
but otherwise must be done manually by an administrator using the
regsvcs.exe command line
utility (or as part of a standard
msi setup process).
■Note Enterprise Ser
vices (COM+) isn’t supported on Windows 98 or Windows ME. If you plan to configure the
data portal to run locally in
the c
lient process on older client worksta
tions, you must not use the
<Transactional
(TransactionTypes.EnterpriseServices)> attribute on your data access methods.
I
f y
ou don’t use the
<Transactional(TransactionTypes.EnterpriseServices)> attr
ibute on
your

DataPortal_XYZ methods, no code will use Enterprise Services in the client process, and so
you don’t have to worry about these details.
I’ll discuss the use of the
EnterpriseServicesPortal through the data portal in Chapter 12, as
it has its own unique set of requirements.
CHAPTER 8 ■ BUSINESS OBJECT IMPLEMENTATION 409
Figure 8-2. Referencing the Csla.dll assembly
6315_c08_final.qxd 4/7/06 2:00 PM Page 409
Business Class Implementation
T
he business classes implemented here follow the object-oriented design created in Chapter 6.
That chapter identified not only the classes to be created, but also which CSLA .NET base classes
each one will subclass.
I’ll walk through the first few classes in detail. The other classes will be very similar, so for
t
hose, I’ll discuss only the key features. Of course, the complete code for all classes is available
in the code download for the book.
Project
The Project class is an editable root class that represents a single project in the application. It will
follow the
EditableRoot template, as discussed in Chapter 7. This means that it inherits from
BusinessBase, as shown in Figure 8-3.
Since this is the first business class to be created, I’ll walk through the code in complete detail.
You can assume that subsequent classes follow a similar structure overall.
The Project class will use a number of .NET and CSLA .NET features. To make this easier,
a number of namespaces are imported at the pr
oject level (through the References tab in the
My Project designer). These include:

System.Data

• System.Data.SqlClient
• Csla
• Csla.Data
These references are used to simplify the code in the class. For instance, the data access code
will inter
act with SQL Server, so the project needs to import the
System.Data.SqlClient namespace
.
And of course, CSLA .NET features are used, so namespaces are brought in for that as well.
The class itself is contained within the default
ProjectTracker.Library namespace and is
declared as follows:
<Serializable()> _
Public Class Project
Inherits BusinessBase(Of Project)
CHAPTER 8 ■ BUSINESS OBJECT IMPLEMENTATION410
Figure 8-3. The Project class subclasses BusinessBase
6315_c08_final.qxd 4/7/06 2:00 PM Page 410
The BusinessBase class requires one generic type parameter. This is the type of the business
object itself, and is used to provide strongly typed
Save() and Clone() methods for the object as
discussed in Chapter 3.
The class will contain the standard code regions discussed in Chapter 7:

Business Methods
• V
alidation Rules
• Authorization Rules
• Factory Methods
• Data Access

The class also has a region named Exists. This region implements an Exists() method that can
be used to determine if a specific project’s data exists in the database. I’ll discuss the code in the
Exists region at the end of the chapter.
Let’s walk through each region in turn.
Business Methods
The Business M
ethods
region includes the declar
ation of all instance fields, along with the proper-
ties and methods that implement business logic around those fields. Since
Project is a parent class,
it will also include some special code designed to wor
k well with its child objects.
Instance Field Declarations
The field declarations are as follo
ws:
Private mId As Guid = Guid.NewGuid
Private mName As String = ""
Private mStarted As New SmartDate
Private mEnded As New SmartDate(False)
Private mDescription As String = ""
Private mTimestamp(7) As Byte
Private mResources As ProjectResources = _
ProjectResources.NewProjectResources()
The String fields are all initialized to "". By default, the value would be Nothing, but that causes
problems with data binding, especially in Windows Forms. It is very important that
String type
instance fields be initialized to some non-
Nothing value.
■Note All String instance fields should be initialized with a default value when they’re dec

lared. This is
because Windows Forms data binding throws a runtime exception when attempting to data bind against string
properties that return Nothing.
Also notice that the date v
alues are of type
SmartDate, r
ather than just
DateTime.
The object is
taking advantage of the
Csla.SmartDate class that understands empty dates. The code specifies that
mStarted should treat an empty date as the minimum possible date value, while mEnded will treat it
as the maximum value.
Each
Project object contains a collection of ProjectResource child objects
.
When a
Project
object is created, an empty child collection is also created by calling the appropriate factory method
CHAPTER 8 ■ BUSINESS OBJECT IMPLEMENTATION 411
6315_c08_final.qxd 4/7/06 2:00 PM Page 411
on the collection. The NewProjectResources() method creates an empty collection, ensuring that
child objects can be added as required.
The result is that the instance fields are declared and initialized so the object is immediately
useful for things like data binding, setting property values, or adding child objects.
Read-Only Properties
The bulk of the code in the Business Methods region for most objects will be the properties. Some
objects may include complex methods implementing business logic, but virtually all objects include
properties to allow the UI to view or update the object’s values.
The

Id property of the Project is read-only. It also represents the object’s unique primary key
value in the database:
<System.ComponentModel.DataObjectField(True, True)> _
Public ReadOnly Property Id() As Guid
Get
CanReadProperty(True)
Return mId
End Get
End Property
Since this is the primary key for the data in the database, the value can also be consider
ed to
be a unique identifier for the object itself. The
DataObjectField attribute is used to specify that the
property is both a primary key and an identity value. This attribute is used by data binding, and in
particular by the
CslaDataSource ASP.NET control created in Chapter 5. The attribute is optional,
but is useful for helping to identify the nature of primary key properties.
Notice the use of the
CanReadProperty() method in the get block. This code uses the overload
created in Chapter 3, telling the method to throw a
System.Security.SecurityException if the current
user is not authorized to read the property. This is the simplest way to use the authorization function-
ality built into CSLA .NET. You could also opt to manually check the result with code like this:
If CanReadProperty() Then
Return mId
Else
' take appropriate action
End If
This approach allo
ws you to do something other than throw the default exception.

You would
write your code in the
Else clause to cover the case in which the user isn’t authorized to read the
property. A third approach, which avoids the use of
System.Diagnostics to determine the name of
the property, is as follows:
If CanReadProperty("Id") Then
Return mId
Else
' take appropriate action
End If
Notice that in this case, the name of the property is specified as literal text. This reduces the
maintainability of the code, but has a marginal performance benefit by avoiding the
System.
Diagnostics
call used by the previous overloads. You can determine whether the performance
gain is wor
th the maintainability loss for your particular application.
CHAPTER 8 ■ BUSINESS OBJECT IMPLEMENTATION412
6315_c08_final.qxd 4/7/06 2:00 PM Page 412
■Tip If you are using code generation or code snippets to create your business classes, there’s no real cost to
using a literal value here. Since the code generator creates the code automatically, the likelihood of bugs due
t
o typos is very small, and you may opt to use the literal in order to gain optimal performance.
The Id property illustrates several things: a read-only property, a primary identity key value,
and the use of the
CanReadProperty() calling options.
Read-Write Properties
Now let’s try something a bit more interesting by creating a read-write property, Name:
Public Property Name() As String

Get
CanReadProperty(True)
Return mName
End Get
Set(ByVal Value As String)
CanWriteProperty(True)
If mName <> Value Then
mName = Value
PropertyHasChanged()
End If
End Set
End Property
Since this is neither a primary key nor an identity value, there’s no immediate need to use the
DataObjectField attribute. You may still opt to use this attribute on your properties to provide this
extra information for other purposes, such as automated unit testing.
The
Get block is virtually identical to that in the Id property. In fact, the Get block for properties
will always be the same—the only difference being the name of the instance field that’s returned.
The
Set block deserves some discussion, however. First, notice the CanWriteProperty()
method call. The options for calling CanWriteProperty() are the same as for CanReadProperty(),
so you can take more control or use a literal name for the property if you so desire. Regardless,
the idea is that the object’s property value is only changed if the user is authorized to write to
this property.
Assuming the user is authorized to change the property value, the code checks to see if the
pr
o
vided v
alue is actually new. If it’s the same as the value already in the object, then there’s no
sense in any work being done.

So, if the user is authorized to change the value, and the value is different from what is already
in the object, then the new v
alue is stor
ed in the object. It is important to realize that this occurs
before any validation code runs. This means that the object could end up storing invalid values.
That’s OK, though, because the object has an
IsValid property that can be used to determine
whether any validation rules are currently being violated by values in the object.
The
PropertyHasChanged() method is where the validation rules are actually invoked. This
method performs a sequence of steps:
1. It checks the validation rules for the property.
2. It sets the object’s IsDirty flag to True.
3. I
t r
aises a
PropertyChanged ev
ent for data binding.
CHAPTER 8 ■ BUSINESS OBJECT IMPLEMENTATION 413
6315_c08_final.qxd 4/7/06 2:00 PM Page 413
Like CanReadProperty(), the PropertyHasChanged() method uses System.Diagnostics to
determine the name of the current property, which incurs a marginal performance hit. If this is
a problem for you, the code can be changed to provide the literal name of the property:
PropertyHasChanged("Name")
Again, this is a trade-off between performance and maintainability; you’ll have to determine
which is most important for your application.
The validation rules to be checked are associated with the property in the
AddBusinessRules()
method, which is implemented later in the chapter. Most rule methods assume that the value to
be validated is already in the object’s property, which is why it is important that the instance field

be set to the new value before the validation rules are invoked.
The
IsDirty property indicates whether the object’s data has been changed. Since a new value
has been put into the object, this property must now return
True.
Finally, since the object’s data has changed, any UI bound to the object through data binding
must update its display. This is done by raising a
PropertyChanged event, as discussed in Chapter 3.
The
PropertyHasChanged() method takes care of this automatically.
■Note Whenever the value of an instance field changes, you need to call PropertyHasChanged() for any
properties that have changed values. This ensures that the object’s state and the state of any data-bound UI
components are changed or updated as appropriate.
You can also have other objects handle the
PropertyChanged event if they need to respond to a change
in a business object’s state. For instance, this technique can be used to automatically have a parent object
recalculate values when its child objects are changed.
Most read-write properties look just like the preceding Name property. For instance, here’s the
Description property:
Public Property Description() As String
Get
CanReadProperty(True)
Return mDescription
End Get
Set(ByVal Value As String)
CanWriteProperty(True)
If mDescription <> Value Then
mDescription = Value
PropertyHasChanged()
End If

End Set
End Property
Notice that it is identical to the Name property, other than working with a different instance
field.
The v
ast major
ity of pr
operty methods will look exactly like this. In fact, you can find a code
snippet for both r
ead-only and read-write properties in the
Snippets subdir
ectory in the CSLA .NET
code download.
■T
ip
Y
ou can manually install the snippet files for use in Visual Studio 2005. By default, you should copy them
to the
Visual Basic\My Code Snippets directory under My Documents\Visual Studio 2005\
Code Snippets. I typically put them in a Csla directory beneath My Code Snippets.
CHAPTER 8 ■ BUSINESS OBJECT IMPLEMENTATION414
6315_c08_final.qxd 4/7/06 2:00 PM Page 414
SmartDate Properties
So far, you’ve seen how to implement properties for type Guid and String. Most types follow this
same approach, with obvious small variation for formatting of values and so forth. But dates are
a tougher issue.
One way to deal with dates is to expose them as
DateTime values directly. This works well for
date values that are required, for which an empty date isn’t an option. And of course, it only works
well if you are binding the property to a date-aware control. Unfortunately, most of the date-aware

controls don’t allow the user to just type a free-form date value, and so they aren’t really very good
for any sort of heads-down data entry scenarios.
The
SmartDate class from Chapter 5 is intended to help solve this dilemma by making it easy
for a business class to expose a date value as a
String, yet also be able to treat it like a date. Addi-
tionally,
SmartDate allows for empty date values—it gives you the option of treating an empty date
as the smallest or largest possible date for the purposes of comparison.
The
Started and Ended properties utilize the SmartDate data type. Here’s the Started property:
Public Property Started() As String
Get
CanReadProperty(True)
Return mStarted.Text
End Get
Set(ByVal Value As String)
CanWriteProperty(True)
If mStarted <> Value Then
mStarted.Text = Value
ValidationRules.CheckRules("Ended")
PropertyHasChanged()
End If
End Set
End Property
I’ll discuss the CheckRules() method call shortly. First, let’s focus on how the property is con-
structed. Notice that it is a
String property, so it can be data bound to any text input control. This
means the user can enter the date value in any format that can be parsed, including the shortcuts
added to

SmartDate in Chapter 5 (such as + for tomorrow).
The
Get block returns the Text property of the mStarted field, thus returning the date value as
a
string, for
matted based on the format string set in
mStarted (b
y default it is
d, the shor
t date
for
mat).
The
Set block sets the Text property, automatically triggering the parsing algorithm built
into
SmartDate. That way, the value is stored as a date internal to SmartDate itself. This is impor-
tant because it allows
SmartDate values to be compared to each other, as well as to DateTime
v
alues
.
This compar
ison capability will be used later when the v
alidation r
ules are implemented
in
Project.
The end result is that the UI sees a
String property, but all the features and functionality of
a date type ar

e av
ailable inside the business class.
The
Ended property is declared the same way, but works with the mEnded field instead.
Interdependent Properties
Sometimes an object will have properties that are interdependent, or at least have interdependent
v
alidation logic
. The
Started and Ended pr
oper
ties are good examples of this case. Later on, you’ll
see how to implement a business validation rule saying that the value of
Ended must not be earlier
than the v
alue of
Started—a pr
oject can

t end before it begins.
CHAPTER 8 ■ BUSINESS OBJECT IMPLEMENTATION 415
6315_c08_final.qxd 4/7/06 2:00 PM Page 415

×