In Chapter 1, I discuss the concepts behind the use of business objects and distributed objects. In Chapter 2, I explored the design of the business framework. Chapters 3 through 5 cover object-oriented design in general and then focus more around the specific stereotypes directly supported by CSLA .NET. In this chapter, I start walking through the implementation of the CSLA .NET framework by providing an overview of the namespaces and project structure of the framework. Then in Chapters 7 through 16, I provide detail about the implementation of each of the major features of the framework as discussed in Chapter 2.
The focus in this chapter is on the overall project structure and namespaces used to organize all the framework code and a walkthrough of the structure of the major types in the Csla
and Csla.Core
namespaces.
CSLA .NET has existed since around the year 2001 and has been steadily evolving since then, based on feedback from the user community and to keep up with the many changes Microsoft has made to the Microsoft .NET Framework. The result is that CSLA .NET is now a large and complex framework.
My goal, however, is to take complexity out of the application and place it into CSLA .NET so developers who use the framework don't need to deal with the complexity. In other words, CSLA .NET solves some pretty complicated issues and tries to expose its solutions in an easy-to-use manner.
As discussed in Chapters 4 and 5, business developers primarily interact with a limited set of base classes provided by CSLA .NET:
Csla.BusinessBase<T>
Csla.BusinessListBase<T,C>
Csla.ReadOnlyBase<T>
Csla.ReadOnlyListBase<T,C>
Csla.NameValueListBase<K,V>
Csla.CommandBase
Csla.EditableRootListBase<T>
Csla.CriteriaBase
These base classes are the primary classes from which most business objects inherit. Almost all the other classes in CSLA .NET exist to support the functionality provided by these base classes. In particular, BusinessBase<T>
relies on quite a number of other classes. For instance, Csla.BusinessBase<T>
inherits from Csla.Core.BusinessBase
, which inherits from Csla.Core.UndoableBase
. It also makes use of the ValidationRules
and AuthorizationRules
classes, among others.
I'll start by describing the overall structure of the Csla
project and then discuss each of the namespaces and the functionality each contains, with some extra emphasis on the Csla
and Csla.Core
namespaces. Most of the namespaces contain implementations of major CSLA .NET features that are covered in detail in subsequent chapters.
Obviously, there is a lot to cover, and this book will only include the critical code from each class. You'll want to download the code for the book at www.apress.com/book/view/1430210192
or www.lhotka.net/cslanet/download.aspx
so you can see each complete class or type as it is discussed.
The current version of CSLA .NET requires the use of Visual Studio 2008 and the Microsoft .NET Framework version 3.5 SP1. Earlier versions of CSLA .NET exist that support Microsoft .NET 1.0 through 3.0 and are also available from the download website.
CSLA .NET is implemented as a Class Library project named Csla
. This means it builds as a DLL, which can be referenced by your business application projects.
To keep all the source files in the project orderly, the project's code is organized into a set of folders. Table 6-1 lists the folders in the project.
Table 6.1. Folders in the CSLA Project
Folder | Purpose |
---|---|
These are the types most commonly used by developers as they build business objects based on CSLA .NET. | |
Core | These types are used by other framework classes and often extend the .NET Framework or enable extension of CSLA .NET. |
Data | These types provide functionality to simplify writing data access code. |
DataPortalClient | These types are part of the data portal functionality (see Chapter 15). |
Linq | These types are required by the LINQ to CSLA functionality. |
Reflection | These are a set of helper types that abstract the use of reflection. |
Security | These are the types that implement authorization and help implement custom authentication. |
Serialization | These are a set of helper types that abstract the serialization of objects. |
Server | These types implement the server-side data portal functionality (see Chapter 15). |
Silverlight | These types enable interaction with CSLA .NET for Silverlight. |
Validation | These are the types that implement the business and validation rules support for editable business objects. |
Web | These are types and controls used to assist in the creation of Web Forms user interfaces. |
Windows | These are types and controls used to assist in the creation of Windows Forms user interfaces. |
Workflow | These are types used to assist in the creation of Windows Workflow Foundation workflows. |
Wpf | These contain types and controls used to assist in the creation of WPF user interfaces. |
By organizing the various files into folders and related namespaces, the project is far easier to understand. There's an additional Diagrams folder in the code download, containing many of the diagrams (or pieces of them at least) used to create the figures in this book.
The Csla
project is a Class Library and it targets the .NET Framework 3.5 SP1. You can see these settings in Visual Studio by double-clicking the Properties node under the Csla
project in Solution Explorer and looking at the Application tab as shown in Figure 6-1.
You can also see the assembly information shown in Figure 6-2 by clicking the Assembly Information button.
The copyright information here must be preserved based on the license agreement. The license is available in the code download at www.apress.com/book/view/1430210192
or www.lhotka.net/cslanet/license.aspx
.
The Csla.dll
file is a signed assembly. This means that the assembly has a strong name and so can be identified uniquely. This is required because CSLA .NET optionally can use Enterprise Services, which requires that assemblies be signed. Additionally, there are many scenarios where applications desire strongly named assemblies so they can ensure that they are really interacting with the assembly they referenced at development time.
Figure 6-3 shows the Signing tab in the project's properties window, where you can see that the CslaKey.snk
file is being used to sign the assembly.
The CslaKey.snk
file is included as a file in the project and contains a public/private key pair. The public key is available to any code consuming the assembly and can be used to ensure that the private key (which should be kept private) was used to sign the DLL.
Normally, when you sign your assemblies you would want to protect your key file to ensure that the private key remains private. The CslaKey.snk
file included in the download is there for convenience so you can easily build the project. You may wish to replace this with your own key file if you want your application to only use the Csla.dll
assembly you built.
The CSLA .NET framework supports localization. For a framework, the key to supporting localization is to avoid using any string literal values that might be displayed to the end user. The .NET Framework and Visual Studio 2008 offer features to assist in this area through the use of resources.
To see the resource editor in Visual Studio, double-click the Properties node under the Csla
project in Solution Explorer to bring up the project's properties window. Click the Resources tab to navigate to the built-in resource editor. Figure 6-4 shows this editor with several of the string resources from Resources.resx
.
The complete set of resources is available in the Resources.resx
file in the download. Additionally, a number of people around the world have been kind enough to translate the resources to various languages. As this is an ongoing process, refer to www.lhotka.net/cslanet/download.aspx
for updates to the framework and resource files.
Now that you understand the basic project structure, let's walk through each folder (and thus each namespace) in turn, so you have a high-level picture of the functionality contained in each one.
The Csla
namespace contains the types that are most commonly used by business developers as they create business objects using CSLA .NET. The files in this namespace are in the top-level folder in the project: /.
The primary classes in this namespace are the base classes used to support the stereotypes discussed in Chapters 4 and 5. Figure 6-5 illustrates these classes.
As you can see, these base classes implement a great many interfaces. Some of the interfaces are standard .NET interfaces, such as ICloneable
, but most of them are defined in the Csla.Core
namespace and are part of CSLA .NET.
CSLA .NET is an inheritance-based framework, which means that the primary way developers use the framework is by inheriting from one of the framework's base classes. That is a powerful model because it allows a developer to tap into predefined functionality with very little effort. Much like inheriting from Form
instantly gives you a fully functional window in Windows, inheriting from BusinessBase
instantly gives you a fully functional business object that supports data binding, validation, authorization, and so forth.
However, inheritance is not terribly flexible. A class can only inherit from one thing, while it can implement many interfaces. So for the normal scenarios, the base classes are ideal, but for advanced scenarios such as building a UI framework on top of CSLA business objects, these interfaces are invaluable. I discuss these more in the section on the Csla.Core
namespace later in the chapter.
Table 6-2 lists primary classes in the Csla
namespace.
Table 6.2. Primary Classes in the Csla Namespace
Type | Description |
---|---|
| Class that provides access to important application context information; used by the framework, business classes, and UI code |
| Base class from which editable root, child, and switchable objects inherit |
| Base class from which editable root and child list objects inherit |
| Base class from which command objects inherit |
| Base class from which custom criteria objects inherit |
| Class that exposes the data portal functionality to the client |
| Base class from which dynamic list objects inherit |
| Base class from which name/value list objects inherit |
| Class that defines metadata for each business object property |
| Base class from which read-only root and child objects inherit |
| Base class from which read-only root and child list objects inherit |
| Class that provides a single value criteria for any object |
| Type that extends |
| Type that includes utility methods used by other classes |
I'll discuss each of these types at a high level.
The ApplicationContext
class is a central location from which application context information can be accessed. Some of this context comes from the application's configuration file, some from in-memory settings, and some from ambient environmental values in .NET.
Table 6-3 lists the context information available through ApplicationContext
.
Table 6.3. Context Data Contained Within ApplicationContext
Context Data | Description |
---|---|
| Collection of context data that flows from client to server and then from server back to client; changes on either end are carried across the network |
| Collection of context data that flows from client to server; changes on the server are not carried back to the client |
| Collection of context data that exists only in the current location (client or server) |
| Current .NET security (principal) object; safely accesses this value independent of runtime (ASP.NET, WPF, etc.) |
| Authentication setting from |
| Data portal proxy provider setting from |
| Data portal URL value for Remoting proxy from |
| |
| Setting indicating whether objects are cloned before update through local data portal, setting from |
| Serialization provider type name from |
| Setting indicating how the |
| Value indicting whether the code is currently executing on the client or server side of the data portal |
These context values can be grouped into three areas: configuration settings, ambient values, and context dictionaries.
Configuration Settings
The configuration settings include items read from the config file. This is done using the standard .NET System.Configuration.ConfigurationManager
class. In this case ApplicationContext
is simply wrapping existing functionality to provide a more abstract way to access the config values.
Ambient Values
The ambient values include the User
and ExecutionLocation
properties. Each one is different and is worth discussing.
User Property
When code is running outside ASP.NET, it relies on System.Threading.Thread.CurrentPrincipal
to maintain the user's principal object. On the other hand, when code is running inside ASP.NET, the only reliable way to find the user's principal object is through HttpContext.Current.User
. Normally, this would mean that you would have to write code to detect whether HttpContext.Current
is null
, and only use System.Threading
if HttpContext
isn't available. The User
property automates this process on your behalf:
public static IPrincipal User
{
get
{
if (HttpContext.Current == null)
return Thread.CurrentPrincipal;
else
return HttpContext.Current.User;
}
set
{
if (HttpContext.Current != null)
HttpContext.Current.User = value;
Thread.CurrentPrincipal = value;
}
}
In general, Csla.ApplicationContext.User
should be used in favor of either System.Threading
or HttpContext
directly because it automatically adjusts to the environment in which your code is running. With CSLA .NET-based applications, this is particularly important because your client code could be a Windows Forms application but your server code could be running within ASP.NET. Remember that your business objects run in both locations and so must behave properly both inside and outside ASP.NET.
ExecutionLocation Property
The ExecutionLocation
property can be used by business code to determine whether it is currently executing on the client or on the server. This is particularly useful when writing data access code because that code could run on either the client or the server, depending on whether the channel adapter uses LocalProxy
or one of the remote proxies. Remember that LocalProxy
is designed such that the "server-side" code runs on the client.
The property value is of type ExecutionLocations
, defined by the following enumerated type:
public enum ExecutionLocations
{
Client
,Server
}
The ExecutionLocation
value is global to both the client and server, so it is stored in a static
field. This is shared by all threads on the server, but that's OK because it will always return the Server
value when on the server, and Client
when on the client:
private static ExecutionLocations _executionLocation =
ExecutionLocations.Client;
public static ExecutionLocations ExecutionLocation
{
get { return _executionLocation; }
}
The value defaults to Client
. This is fine, as it should only be set to Server
in the case that the Csla.Server.DataPortal
class explicitly sets it to Server
. Recall that in that DataPortal
class there's a SetContext()
method that only runs when the server-side components really are running on the server. In that case, it calls the SetExecutionLocation()
method on ApplicationContext
:
internal static void SetExecutionLocation(ExecutionLocations location)
{
_executionLocation = location;
}
This way, the value is set to Server
only when the code is known to physically be executing in a separate AppDomain
, process, and probably computer from the client.
Context Dictionaries
Finally, let's discuss the three context dictionaries: LocalContext, ClientContext
, and GlobalContext
. On the surface, it seems like maintaining a set of globally available information is easy—just use a static
field and be done with it. Unfortunately, things are quite a bit more complex when building a framework that must operate in a multithreaded server environment.
CSLA .NET supports client/server architectures through the data portal. The server-side components of the data portal may run in ASP.NET on an IIS server or within the Windows Activation Service (WAS) under Windows Server 2008.
In these cases, the server may be supporting many clients at the same time. All the client requests are handled by the same Windows process and by the same .NET AppDomain
. It turns out that static
fields exist at the AppDomain
level: meaning that a given static
field is shared across all threads in an AppDomain
. This is problematic because multiple client requests are handled within the same AppDomain
but on different threads. So static
fields aren't the answer.
The solution is different in ASP.NET and in any other .NET code. Either way, the .NET Framework illustrates the right answer. Look at CurrentPrincipal
; it is associated with the current Thread
object, which provides an answer for any code running outside of ASP.NET. Within ASP.NET, there's the HttpContext
object, which is automatically maintained by ASP.NET itself.
So, when outside ASP.NET, the answer is to associate the context data directly with the current Thread
object, and when inside ASP.NET, the context data can be stored using the HttpContext
.
Let's discuss the Thread
option first. While the .NET Thread object already has a property for CurrentPrincipal
, it doesn't have a property for the concept of LocalContext
. But it does have a concept called named slots. Every Thread
object has a collection associated with it. Each entry in this collection is referred to as a slot. Slots can be referred to by a key, or a name—hence the term named slot. The GetNameDataSlot()
method on the Thread
object returns access to a specific slot as an object of type LocalDataStoreSlot
. You can then use the Thread
object's GetData()
and SetData()
methods to get and set data in that slot.
While this is a bit more complex than dealing with a conventional collection, you can think of named slots as being like a collection of arbitrary values associated with a Thread
object.
When running in ASP.NET, things are a bit simpler because HttpContext
has an Items
collection. This is a dictionary of name/value pairs that is automatically maintained by ASP.NET and is available to your code. Within ASP.NET, this is the only safe place to put shared data such as context data because ASP.NET may switch your code to run on different threads in certain advanced scenarios. This gets interesting when the application needs to know if it is running under ASP.NET or some other environment so it can store the values in the right location. When running under ASP.NET, thread-local storage isn't safe and HttpContext
must be used. When running outside of ASP.NET, HttpContext
isn't even available and thread-local storage is the right answer.
The reason thread-local storage isn't safe under ASP.NET is that under some circumstances with custom HttpModule
types, ASP.NET may change the thread on which your code is executing. If that happens, any values attached to thread-local storage will be lost (because they are on the old thread, not the one your code is switched to by ASP.NET). So in ASP.NET, the HttpContext
is the only safe location to store context values.
The context dictionaries I discuss here do not replace Session
in ASP.NET. These context values are only available for the lifetime of a single server call. If you want to store values across pages or server calls, you should use Session
.
I'll use the ClientContext
to illustrate the solution. The context dictionary will be stored in either HttpContext
or thread-local storage, based on the environment within which the code is running. And because this code may run in a multithreaded environment, it must employ locking to ensure thread safety.
First, there's the public
property that exposes the value:
private static object _syncClientContext = new object();
private const string _clientContextName = "Csla.ClientContext";
public static ContextDictionary ClientContext
{
get
{
lock (_syncClientContext)
{
ContextDictionary ctx = GetClientContext();
if (ctx == null)
{
ctx = new ContextDictionary();
SetClientContext(ctx);
}
return ctx;
}
}
}
When ClientContext
is accessed, a lock is used to ensure thread safety. Within that lock, the GetClientContext()
method is called to retrieve the context dictionary from its storage location:
internal static ContextDictionary GetClientContext()
{
if (HttpContext.Current == null)
{
if (ApplicationContext.ExecutionLocation == ExecutionLocations.Client)
lock (_syncClientContext)
return (ContextDictionary)
AppDomain.CurrentDomain.GetData(_clientContextName);
else
{
LocalDataStoreSlot slot =
Thread.GetNamedDataSlot(_clientContextName);
return (ContextDictionary)Thread.GetData(slot);
}
}
else
return (ContextDictionary)
HttpContext.Current.Items[_clientContextName];
}
This method detects whether the code is running in ASP.NET and retrieves the dictionary from HttpContext
or thread-local storage as appropriate.
If the dictionary does not yet exist, it is created, then stored using the SetClientContext()
method:
private static void SetClientContext(ContextDictionary clientContext)
{
if (HttpContext.Current == null)
{
if (ApplicationContext.ExecutionLocation == ExecutionLocations.Client)
lock (_syncClientContext)
AppDomain.CurrentDomain.SetData(
_clientContextName, clientContext);
else
{
LocalDataStoreSlot slot =
Thread.GetNamedDataSlot(_clientContextName);
Thread.SetData(slot, clientContext);
}
}
else
HttpContext.Current.Items[_clientContextName] = clientContext;
}
This method works much the same way, checking to see if the code is running in ASP.NET and storing the value in the correct location.
Notice that both of these methods use internal
scope. This is because they are invoked not only from the LocalContext
property but also from the data portal. The ClientContext
and GlobalContext
values flow to and from the client and server through the data portal. Chapter 15 provides more details about the data portal.
At this point you should have an understanding about how the ApplicationContext
class provides access to application context values to the rest of the application.
The BusinessBase<T>
class exposes most of the functionality for a single editable object and combines support for data binding, validation rules, authorization rules, and n-level undo. I discuss each of these feature areas in Chapters 7 through 16.
Like all base classes, this class must be Serializable
and abstract
. Much of its behavior flows from its base classes, or from other objects it contains. Here is the declaration of the class:
namespace Csla
{
[Serializable]
public abstract class BusinessBase<T> :
Core.BusinessBase, Core.ISavable where T : BusinessBase<T>
The generic type T
is constrained to be the type of business object being created, so a business class is declared like this:
[Serializable] public class CustomerEdit : BusinessBase<CustomerEdit> { }
The BusinessBase<T>
class inherits from Csla.Core.BusinessBase
, where most of the implementation resides. The reason for this is that BusinessBase<T>
is generic and thus doesn't support polymorphism. The BusinessBase
base class is not generic and so provides a common base class for all editable objects that is polymorphic:
[Serializable]
public abstract class BusinessBase :
Csla.Core.UndoableBase, IEditableBusinessObject
,System.ComponentModel.IEditableObject
,System.ComponentModel.IDataErrorInfo
,ICloneable, Csla.Security.IAuthorizeReadWrite
,IParent, Server.IDataPortalTarget
,IManageProperties
As you can see, BusinessBase
inherits from UndoableBase
and then implements a lot of different interfaces. Some of these are standard .NET interfaces to support things such as data binding, while others are defined by CSLA .NET and are used within the framework itself or as extensibility points for users of the framework.
This class pulls together a lot of functionality. The goal is to abstract all this functionality into a set of easily understood behaviors that simplify the creation of business objects. Table 6-4 lists the functional areas.
Table 6.4. Functional Areas Implemented in BusinessBase
Functional Area | Description |
---|---|
Data binding | Provides support for Windows Forms, WPF, and Web Forms data binding against editable business objects |
Business rules | Provide abstract access to the business and validation rules behavior and implement the |
Authorization rules | Provide abstract access to the authorization rules behavior |
Object status tracking | Keeps track of whether the object is new, old, dirty, clean, or marked for deletion |
Root, parent, and child behaviors | Implement behaviors so the object can function as a root object, a parent object, or a child of another object or collection |
N-level undo | Provides access to the underlying n-level undo functionality imple-mented in |
Cloning | Implements the |
Persistence | Provides access to the data portal and supports necessary data portal interaction for object persistence |
Figure 6-6 shows the inheritance hierarchy of BusinessBase<T>
, illustrating the base classes that work together to provide some of this functionality.
These base classes are covered in Chapters 7 through 14 as each functional area is discussed.
MobileObject
exists to support serialization through the MobileFormatter
, which is part of CSLA .NET for Silverlight. CSLA .NET for Silverlight is outside the scope of this book, and MobileObject
has no impact on how CSLA .NET works within the .NET runtime.
BusinessBase<T>
also contains objects that it relies on to implement various behaviors, as illustrated in Figure 6-7.
By combining inheritance, containment, and collaboration, BusinessBase<T>
consolidates a great deal of functionality without becoming overly complex itself.
Editable objects are perhaps the most common business object used in most applications, and BusinessBase<T>
combines a great deal of functionality. Because of this, the majority of Chapters 7 through 14 provide detail around this class and its behaviors.
The BusinessListBase<T, C>
class provides the functionality to support editable root and child collections. It works closely with BusinessBase<T>
to support data binding, parent-child relationships, n-level undo, and object persistence.
The class is Serializable
and abstract
:
namespace Csla
{
[Serializable]
public abstract class BusinessListBase<T, C> :
Core.ExtendedBindingList<C>
,Core.IEditableCollection, Core.IUndoableObject, ICloneable
,Core.ISavable, Core.IParent, Server.IDataPortalTarget
,IQueryable<C>, Linq.IIndexSearchable<C>, Core.IPositionMappable<C>
where T : BusinessListBase<T, C>
where C : Core.IEditableBusinessObject
Like BusinessBase, BusinessListBase
inherits from a base class and implements quite a number of interfaces. It also collaborates with other framework objects to implement the behaviors listed in Table 6-4.
Figure 6-8 shows the inheritance hierarchy of BusinessListBase
:
MobileList<T>
exists to support serialization through the MobileFormatter
, which is part of CSLA .NET for Silverlight. CSLA .NET for Silverlight is outside the scope of this book, and MobileList<T>
has no impact on how CSLA .NET works within the .NET runtime.
Ultimately, the BusinessListBase
inherits from BindingList<T>
in the System.ComponentModel
namespace. This is the .NET base class that provides support for collections that support data binding in Windows Forms and WPF.
WPF does include ObservableCollection<T>
as a base class to support data binding. While that base class is useful in WPF, it is not recognized by Windows Forms or Web Forms data binding. Fortunately, BindingList<T>
is recognized by all current UI technologies, including WPF, so is the best choice for any collection that needs to support any UI.
ExtendedBindingList<T>
extends BindingList<T>
by adding a RemovingItem
event and an AddRange()
method to all collections.
Editable collections are very common in most business applications. Chapters 7 through 14 expand on the implementation of the various features supported by BusinessListBase
.
The CommandBase
class supports the creation of command objects. As discussed in Chapter 5, command objects allow you to write code that runs on the client, the application server, and again on the client. This is the simplest of the base classes because all it needs to do is provide basic support for use of the data portal.
The class is defined like this:
[Serializable]
public abstract class CommandBase : Core.MobileObject
,Core.ICommandObject, Server.IDataPortalTarget
As with all base classes, it is Serializable
and abstract
. It implements several interfaces, most notably IDataPortalTarget
so the data portal can interact with the object as needed.
MobileObject
exists to support serialization through the MobileFormatter
, which is part of CSLA .NET for Silverlight. CSLA .NET for Silverlight is outside the scope of this book, and MobileObject
has no impact on how CSLA .NET works within the .NET runtime.
Chapter 15 provides detail about how the data portal interacts with command objects.
The CriteriaBase
class supports the creation of custom criteria objects as described in Chapter 5. Custom criteria classes must implement the ICriteria
interface, and CriteriaBase
merely simplifies that process. The ICriteria
interface ensures that a custom criteria object can provide the data portal with the Type
object representing the type of business object to be created, retrieved, or deleted.
SingleCriteria
is a subclass of CriteriaBase
. I discuss it later in this chapter.
The Criteria
class is defined like this:
[Serializable]
public class CriteriaBase : Csla.Core.MobileObject, ICriteria
MobileObject
exists to support serialization through the MobileFormatter
, which is part of CSLA .NET for Silverlight. CSLA .NET for Silverlight is outside the scope of this book, and MobileObject
has no impact on how CSLA .NET works within the .NET runtime.
The primary job of CriteriaBase
is to provide a default implementation of ICriteria
, making it easy to create a custom criteria class by simply subclassing CriteriaBase
:
[Serializable]
public class MyCriteria : CriteriaBase
{
public string Value1 { get; set; }
public string Value2 { get; set; }
public MyCriteria(string value1, string value2)
: base(typeof(MyBusinessClass))
{
this.Value1 = value1;
this.Value2 = value2;
}
}
The highlighted line of code indicates the key point of interaction between this subclass and the base class. The CriteriaBase
class needs to know the type of business object being created, retrieved, or deleted. It gets this Type object as a parameter to its constructor, and that value is typically provided directly by the subclass.
The data portal is covered in Chapter 15. One part of the data portal is the DataPortal
classes in the Csla
namespace. There are two classes: DataPortal
and DataPortal<T>
.
The DataPortal
class is a static
class, and it exposes a set of public
methods that can be used for synchronous interaction with the data portal. It is declared like this:
public static class DataPortal
The most common way to use the data portal is through these synchronous static
methods, and most business classes use the DataPortal
class.
In WPF you can use the CslaDataProvider
control from the Csla.Wpf
namespace to asynchronously retrieve business objects. In other types of application, such as Windows Forms, you can use the .NET BackgroundWorker
component to do the same thing, but that requires extra work on the part of the UI developer.
To minimize that effort, you can use the DataPortal<T>
class, which provides asynchronous access to the data portal. This class is defined like this:
public class DataPortal<T>
Notice that this is not a static
class, and in fact you must create an instance of the class to call its methods.
The data portal is a large and relatively complex part of CSLA .NET and is covered in Chapter 15.
The EditableRootListBase
class supports the creation of dynamic collections. As discussed in Chapter 5, these collections are designed specifically to support in-place editing of data in a Windows Forms DataGrid
-style interface, where changes to each row of data should be saved immediately as the user moves off that row.
This base class exists primarily to support data binding and to abstract the interaction with the editable root objects it contains. It is declared like this:
[Serializable]
public abstract class EditableRootListBase<T> :
Core.ExtendedBindingList<T>, Core.IParent, Server.IDataPortalTarget
where T : Core.IEditableBusinessObject, Core.IUndoableObject, Core.ISavable
Like BusinessListBase
, this class inherits from ExtendedBindingList
and thus BindingList<T>
. It gains support for data binding in WPF and Windows Forms from BindingList<T>
. You can get an idea of the inheritance hierarchy for this class by looking at Figure 6-8.
It also implements numerous interfaces, enabling it to act as a parent object, interact with the data portal, and so forth. Chapters 7 through 14 detail the implementation of the various major subsystems used by EditableRootListBase
.
The NameValueListBase
class supports a specific type of read-only collection, where the items in the collection are simple name/value pairs. The class is defined like this:
[Serializable]
public abstract class NameValueListBase<K, V> :
Core.ReadOnlyBindingList<NameValueListBase<K, V>.NameValuePair>
,ICloneable, Core.IBusinessObject, Server.IDataPortalTarget
Notice how there are two type parameters: K
and V
. These are used to specify the types of the key and value elements in each item contained in the collection.
Also notice that the collection inherits from ReadOnlyBindingList
in the Csla.Core
namespace. Figure 6-9 illustrates the inheritance hierarchy for NameValueListBase
.
The ReadOnlyBindingList
class extends BindingList<T>
, adding the ability to have a read-only collection that fully supports data binding in WPF, Windows Forms, and Web Forms.
MobileList<T>
exists to support serialization through the MobileFormatter
, which is part of CSLA .NET for Silverlight. CSLA .NET for Silverlight is outside the scope of this book, and MobileList<T>
has no impact on how CSLA .NET works within the .NET runtime.
The most interesting thing about NameValueListBase
is that it contains a nested class called NameValuePair
. NameValuePair
is a simple class that exposes Key
and Value
properties, of types K
and V
respectively. It is declared like this:
[Serializable]
public class NameValuePair
This class uses a clever side effect of generics, because while NameValuePair
itself isn't generic, it is able to use the generic type parameters of NameValueListBase
because it is a nested class.
Combined, NameValueListBase
and NameValuePair
make it very easy to create read-only name/value collections that support data binding. These collections are often used to populate combo box or list controls and to implement validation logic where a value is required to exist in a list of known values.
The PropertyInfo<T>
class supports the storage of metadata about the properties declared in a business class. When you declare a normal property in a business class it looks something like this:
public string Name { get { return _name; } set { _name = value; } }
In an editable business object, you'll typically also need to have the property check authorization, run business rules, and do other work. To help simplify this process, CSLA .NET allows you to declare the property using GetProperty()
and SetProperty()
helper methods:
public string Name { get { return GetProperty("Name"); } set { SetProperty("Name", _value) } }
The problem with this code is that the GetProperty()
and SetProperty()
methods need to know what property is being accessed or set so the correct authorization and business rules can be applied. Passing the property name in as a string literal is a maintenance issue. If someone refactors the property name to something like FirstName
, the string literals won't be automatically updated. In fact, a detailed manual search through a lot of code might be required to find all the places where that string literal is used.
PropertyInfo
objects help consolidate this string literal into one location, where the PropertyInfo
object is created. Throughout CSLA .NET, any place where a property name is required, you can provide a PropertyInfo
object instead:
private static PropertyInfo<string> NameProperty =
RegisterProperty(new PropertyInfo<string>("Name"));
public string Name
{
get { return GetProperty(NameProperty); }
set { SetProperty(NameProperty, _value) }
}
Unfortunately, there's no way to entirely eliminate the string literal, but this technique allows the literal value to exist exactly one time in your entire class, minimizing the maintenance issue.
This technique is very similar to the DependencyProperty
concept used by WPF and WF.
The PropertyInfo
class is declared like this:
public class PropertyInfo<T> : Core.IPropertyInfo, IComparable
Notice that it implements an IPropertyInfo
interface from the Csla.Core
namespace. Technically, CSLA .NET accepts IPropertyInfo
parameters everywhere, and this PropertyInfo
class is just one possible implementation. This is an intentional extensibility point for CSLA .NET, allowing you to create other IPropertyInfo
implementations (or subclasses of PropertyInfo
) that store other metadata about each property.
One scenario where you might do this is if you want to store data access metadata, such as the database, table, and column name where the property value is stored.
The BusinessBase
and BusinessListBase
classes provide the tools needed to build editable objects and collections. However, most applications also include a number of read-only objects and collections. An application might have a read-only object that contains system configuration data, or a read-only collection of ProductType
objects that are used just for lookup purposes.
The ReadOnlyBase
class provides a base on which business developers can build read-only root and child objects. By definition, a read-only object is quite simple: it's just a container for data, possibly with authorization or formatting logic to control how that data is accessed. It doesn't support editing of the data, so there's no need for n-level undo, change events, or much of the other complexity built into BusinessBase
.
ReadOnlyBase
supports read-only properties, authorization, and persistence.
Like all base classes, this one is Serializable
and abstract
. It also implements Csla.Core.IBusinessObject
to provide some level of polymorphic behavior even though this is a generic class:
[Serializable]
public abstract class ReadOnlyBase<T> : Core.MobileObject
,ICloneable, Core.IReadOnlyObject
,Csla.Security.IAuthorizeReadWrite, Server.IDataPortalTarget
,Core.IManageProperties
where T : ReadOnlyBase<T>
Like BusinessBase
, the generic type T
is constrained to be the type of business object being created, so a business class is declared like this:
[Serializable] public class DefaultCustomerData : ReadOnlyBase<DefaultCustomerData> { }
Presumably, any business object based on this class would consist entirely of read-only properties or methods that just return values. ReadOnlyBase
supports data binding, authorization, and persistence. Figure 6-10 illustrates the inheritance hierarchy for ReadOnlyBase
.
MobileObject
exists to support serialization through the MobileFormatter
, which is part of CSLA .NET for Silverlight. CSLA .NET for Silverlight is outside the scope of this book, and MobileObject
has no impact on how CSLA .NET works within the .NET runtime.
Because ReadOnlyBase
provides such little functionality, it doesn't need a complex base class. It contains an AuthorizationRules
object like BusinessBase
and interacts with the DataPortal
class, but otherwise it doesn't need most of the advanced features of CSLA .NET required by BusinessBase
.
Read-only objects are common in most business applications, and the features supported by ReadOnlyBase
are fully explored in Chapters 7 through 14.
Like the ReadOnlyBase
class, ReadOnlyListBase
is quite simple. It is designed to make it easy for a business developer to create a business collection that doesn't allow items to be added or removed. Presumably, it will be used to contain read-only child objects, but any type of child object is allowed. Read-only collections do support data binding, authorization, and persistence.
The ReadOnlyListBase
class is defined like this:
[Serializable]
public abstract class ReadOnlyListBase<T, C> :
Core.ReadOnlyBindingList<C>, Csla.Core.IReadOnlyCollection
,ICloneable, Server.IDataPortalTarget
where T : ReadOnlyListBase<T, C>
Like BusinessListBase
, it accepts two generic type parameters. Type T
is constrained to be a subclass of this base class and refers to the type of the collection being created. Type C
is the type of the child object to be contained within the collection, and it can be any type. Again, it would make the most sense for the child type to be some form of read-only object, but that's not required by the collection class. A business collection would be declared like this:
[Serializable] public class CustomerList : Csla.ReadOnlyListBase<CustomerList, CustomerInfo> { }
This indicates that the collection contains child objects of type CustomerInfo
.
Like NameValueListBase
, this class inherits from ReadOnlyBindingList
in the Csla.Core
namespace. Look at Figure 6-9 to see how the inheritance hierarchy works for this class.
Read-only collections are very common in most business applications, and ReadOnlyListBase
supports the root and child collection stereotypes discussed in Chapter 5.
The SingleCriteria<B, C>
class is a subclass of CriteriaBase
and provides an implementation of the most common criteria class, where a business object can be identified by a single criteria value.
The SingleCriteria
class is defined like this:
[Serializable()]
public class SingleCriteria<B, C> : CriteriaBase
The B
type parameter is the type of business object to create, retrieve, or delete. The C
type parameter is the type of the criteria value being passed through the data portal.
Of course the interesting part about inheriting from CriteriaBase
is that it requires the type of business object to be retrieved, so the constructor in SingleCriteria
uses the B
type parameter:
public SingleCriteria(C value)
: base(typeof(B))
{
_value = value;
}
The end result is that most business objects can be created, retrieved, or deleted using SingleCriteria
by writing code like this:
return DataPortal.Fetch<CustomerEdit>(new SingleCriteria<CustomerEdit, int>(id));
If you need more extensive criteria objects, you can create your own subclasses of CriteriaBase
, as discussed in Chapter 5.
The SmartDate
type is a struct
that contains and extends a DateTime
value. It is discussed in Chapter 2 from a design perspective. The type is declared like this:
[Serializable]
[System.ComponentModel.TypeConverter(
typeof(Csla.Core.TypeConverters.SmartDateConverter))]
public struct SmartDate : Csla.Core.ISmartField
,IComparable, IConvertible, IFormattable
,Csla.Serialization.Mobile.IMobileObject
The IMobileObject
interface exists to support serialization through the MobileFormatter
, which is part of CSLA .NET for Silverlight. CSLA .NET for Silverlight is outside the scope of this book, and IMobileObject
has no impact on how CSLA .NET works within the .NET runtime.
Not only is this type Serializable
but it has a custom type converter, specified by the TypeConverter
attribute. In fact, SmartDate
is a very complex type because it implements operators, type converters, and various other .NET features to act as closely as possible to the DateTime
type.
The ISmartField
interface, defined in Csla.Core
, is used to allow the rest of the CSLA .NET framework to interact with any "smart" data types such as this that add string parsing and the concept of being "empty" to a type. You can implement ISmartField
to create your own "smart" types such as SmartInt
or SmartDouble
, but be aware that your type will also need to override many operators and provide type converters much like SmartDate
in order to act as a first-class type in .NET.
The implementation of SmartDate
is covered in more detail in Chapter 16.
The Utilities
class contains utility methods that are used by other parts of the CSLA .NET framework. Many of these methods abstract the use of the .NET type system and reflection. Table 6-5 lists the methods in this class.
Table 6.5. Public Methods in the Utilities Class
Method | Description |
---|---|
| Provides functionality comparable to the VB runtime |
| Provides functionality comparable to the VB runtime |
| Gets the type returned by a property, returning the primitive type even if the value is |
| Gets the type of the items contained in a list or collection |
| Coerces a value from one type into another type; somewhat like doing a cast but far more aggressive and powerful |
While these methods exist primarily to support the CSLA .NET framework itself, they are public
and can be used by business or UI code as well. In particular, IsNumeric(), CallByName()
, and CoerceValue()
can be useful in many scenarios.
The Csla
namespace includes other types beyond those discussed in this chapter. The classes discussed here, and all the rest of the classes, exist to support important features such as data binding, business and validation rules, authorization rules, the data portal, LINQ to CSLA, and so forth. As I discuss each subsystem in Chapters 7 through 14, you'll get a full understanding of all the types.
The Csla.Core
namespace contains types that are not intended for daily use by business developers. Rather, these types are intended for use by the CSLA .NET framework itself and to enable advanced scenarios such as extending or customizing CSLA .NET. This is a primary motivation for putting them into their own namespace—to help keep them out of sight of business developers during normal development.
One primary use for the types in Csla.Core
is to allow people to extend the framework. For instance, Core.BusinessBase
could easily act as a starting point for creating some different or more advanced BusinessBase
-style class. Likewise, Core.ReadOnlyBindingList
is useful as a base for creating any type of read-only collection that supports data binding.
There are also numerous interfaces in Csla.Core
, which are very useful if you are building a UI framework that interacts with business objects. The base classes exposed by CSLA .NET are generic types, such as BusinessBase<T>
. While generics are a powerful tool, they have a major drawback in that generic types are not polymorphic. For example, a List<string>
and List<int>
are two different types that do not inherit from List<T>
. In fact, their common base type is IList
, which is not a generic type.
The same thing is true for BusinessBase<Customer>
and BusinessBase<Product>
. If you want to write code that can work with either type, you need to fall back to Csla.Core.BusinessBase
, which is not a generic type. But if you want more focused behavior, such as the ability to save any editable object, you'd want to use the Csla.Core.ISavable
interface. That interface is implemented by both BusinessBase
and BusinessListBase
because both support editable objects that can be saved. Table 6-6 lists the most commonly used classes and interfaces in Csla.Core
.
Table 6.6. Commonly Used Classes and Interfaces in Csla.Core
Interface | Purpose |
---|---|
| Used to create code that can interact with any editable root or child object; base class for |
| Used to add a |
Implemented by all base classes to provide one common type for all business objects | |
| Implemented by all editable base classes; used to create code that can polymorphically save any editable object |
Implemented by all "smart" types such as | |
Implemented by all undoable base classes; used to create UI frameworks that polymorphically implement form-level cancel buttons | |
Implemented by base classes that maintain object status such as | |
| Contains code to clone any object decorated with the |
| Can be used as a base class to create your own read-only collections that support data binding; base class for all read-only collection types |
There are many more types in the Csla.Core
namespace, but they are designed for internal use by CSLA .NET and are not intended for use by code outside the framework. However, several of these types are important to understanding the structure of other framework classes, and so I'll walk through them.
Earlier in the chapter I discuss BusinessBase<T>
and its base class, BusinessBase
. The BusinessBase
class is the non-generic base class for all editable root and child objects.
Most collection types in CSLA .NET inherit from ExtendedBindingList
, including BusinessListBase, ReadOnlyBindingList
, and ReadOnlyListBase
.
ExtendedBindingList
extends the BindingList<T>
class from the System.ComponentModel
namespace by adding a RemovingItem
event and an AddRange()
method.
The BindingList<T>
already raises a ListChanged
event, but that occurs after an item has been removed from the collection and doesn't provide a reference to the item that is removed. The new RemovingItem
event occurs while the item is being removed from the list and provides a reference to the item that is being removed.
The AddRange()
method allows you to add a range of items to the collection. It accepts an IEnumerable<T>
and adds the items in that list to the end of the collection. This method can be used to merge two collections or to add more data to a collection over time.
Finally, ExtendedBindingList
implements the IsSelfBusy
and IsBusy
properties and other functionality that is required for asynchronous object persistence, as I discuss in Chapter 15.
Generic types such as BindingList<T>
are very powerful because they allow a developer to easily create a strongly typed instance of the generic type. The following defines a strongly typed collection of type string
:
BindingList<string> myStringList;
Similarly, the following defines a strongly typed collection of type int
:
BindingList<int> myIntList;
Since both myStringList
and myIntList
are "of type" BindingList<T>
, you might think they are polymorphic—that you could write one method that could act on both fields. But you can't. Generic types are not inherited and thus do not come from the same type. This is highly counterintuitive at first glance but nonetheless is a fact of life when working with generic types.
Since CSLA .NET makes use of generic types (BusinessBase<T>, BusinessListBase<T,C>
, etc.), this is a problem. There are cases in which a UI developer will want to treat all business objects the same—or at least be able to use the .NET type system to determine whether an object is a business object.
In order to treat instances of a generic type polymorphically, or to do type checks to see if those instances come from the same type, the generic type must inherit from a non-generic base class or implement a non-generic interface. In the case of BindingList<T>
, the generic type implements IBindingList
. So both myStringList
and myIntList
can be treated as IBindingList
types.
To provide this type of polymorphic behavior to CSLA .NET business objects, all business base classes implement Csla.Core.IBusinessObject
. This, then, is the ultimate base type for all business objects. Here's the code for IBusinessObject
:
namespace Csla.Core
{
public interface IBusinessObject
{
}
}
Notice that this interface has no members (methods, properties, etc). This is because there are no common behaviors across both read-only and editable business objects. The interface remains useful, however, because it allows code to easily detect whether an object is a business object, through code like this:
if (theObject is Csla.Core.IBusinessObject) { // theObject is a business object }
The next couple of interfaces will have more members.
The final common interface is ICommandObject
. Like IBusinessObject
, this is an empty interface:
interface ICommandObject : IBusinessObject
{
}
Again, you can use this interface to easily determine whether a business object inherits from CommandBase
within your business or UI code.
Editable business objects must provide a set of basic behaviors so the parts of CSLA .NET can interact with each other properly. These behaviors are defined by the IEditableBusinessObject
interface. This interface is designed for internal use within CSLA .NET and should not be used by code outside the framework:
public interface IEditableBusinessObject :
IBusinessObject, ISupportUndo, IUndoableObject, ITrackStatus
This interface is implemented by BusinessBase
and ensures that behaviors related to n-level undo and parent-child relationships exist in that class. These features are discussed in more detail in Chapter 13.
While a BusinessListBase<T,C>
is both a business object and an editable object, it is also a collection. It turns out that collections need one extra behavior beyond a simple editable object, so the IEditableCollection
interface adds that extra method:
public interface IEditableCollection : IUndoableObject
{
void RemoveChild(Core.BusinessBase child);
}
The RemoveChild()
method will be important later in the chapter during the implementation of BusinessBase
and BusinessListBase
, and specifically for the implementation of the System.ComponentModel.IEditableObject
interface. This interface has some tricky requirements for interaction between a child object in a collection and the collection itself.
In the same way that IBusinessObject
provides a form of polymorphism and commonality across all business objects, IReadOnlyObject
does the same thing for read-only business objects—specifically those that inherit from ReadOnlyBase<T>
.
It turns out that all read-only objects support a method for authorization: CanReadProperty()
. This method is defined in the interface as follows:
public interface IReadOnlyObject : IBusinessObject
{
bool CanReadProperty(string propertyName);
}
The CanReadProperty()
method is discussed in Chapter 12 when I discuss authorization.
The IReadOnlyCollection
interface exists purely to support polymorphism for read-only collection objects that inherit from ReadOnlyListBase<T, C>
. As such, it is an empty interface:
interface IReadOnlyCollection : IBusinessObject
{
}
You can use this interface to easily determine whether a business object is a read-only collection as needed within your business or UI code.
Editable root objects in CSLA .NET implement a Save()
method. This includes objects that inherit from both BusinessBase
and BusinessListBase
. The ISavable
interface formalizes the concept of a savable object, which really means an editable root object. You can use this interface to create a UI framework that can save any editable root object:
public interface ISavable
{
object Save();
void SaveComplete(object newObject);
event EventHandler<SavedEventArgs> Saved;
}
The SaveComplete()
method exists for internal use by CSLA .NET only and should never be used by business or UI code.
The ISavable
interface defines a common Save()
method and a Saved
event. The Saved
event is raised after an object has successfully saved itself by calling the data portal. This event follows the standard EventHandler
pattern, passing two parameters to the event handler: a reference to the sender and a SavedEventArgs
parameter. This SavedEventArgs
parameter contains a reference to the new object that is returned as a result of the Save()
method call.
The Saved
event is intended to address the complexity that occurs when your business object is referenced in numerous locations throughout your application, by multiple forms in the UI, for instance. If you call Save()
on the object in one location, all the other places where that object is referenced must be updated to use the new object returned as a result of Save()
.
The Saved
event provides a solution because it is a standard, centralized, event that provides this notification. Any code holding a reference to a business object can handle the Saved
event. That code will be notified when that object has been saved. The code can then update its reference to use the new object returned as a result of the Save()
call.
The SmartDate
type extends DateTime
to provide the concept of an empty date. It also adds the ability to convert the value into and out of a string
representation to simplify binding the value to a TextBox
type control in the UI.
SmartDate
is discussed in depth in Chapter 16.
Members of the community have created other "smart" data types such as SmartInt
or SmartDouble
that provide the same concepts to those other data types. To standardize and simplify the creation of those types, CSLA .NET defines the ISmartField
interface.
This interface allows CSLA .NET to interact with any "smart" data type in a consistent manner:
public interface ISmartField
{
string Text { get; set; }
bool IsEmpty { get; }
}
As you can see, the interface enables both the concept of an empty value and the ability to convert the value into and out of a text representation.
Creating a new data type, especially one that extends an existing type such as DateTime
or int
requires a lot of work. The goal is to have the new type work as much like the original as possible, which means overriding many (if not all) operators and providing support for type conversion and cast operations. I discuss some of these challenges in Chapter 16 when I cover the implementation of SmartDate
.
Many smart client UI frameworks enable cancel buttons or similar concepts and thus interact with the n-level undo support provided by CSLA .NET objects. The ISupportUndo
interface is designed to make creating these UI frameworks easier, by providing a polymorphic interface the UI can use to interact with the objects:
public interface ISupportUndo
{
void BeginEdit();
void CancelEdit();
void ApplyEdit();
}
I discuss n-level undo in detail in Chapter 13, but you can infer from the method names that the UI can call BeginEdit()
before allowing the user to interact with the object, and then can call either CancelEdit()
or ApplyEdit()
to roll back or accept any changes the user has made to the object.
When building a Windows Forms UI, it is critical that these methods be called only when the object is not data bound to the UI.
Using these methods, a UI developer or UI framework can allow users to edit an object and then click a cancel button to completely undo any changes they've made to the object.
Editable objects maintain several status values about the state of the object. These status values are discussed in detail in Chapter 8, which covers their implementation in BusinessBase
and BusinessListBase
. The ITrackStatus
interface allows a UI framework to get these status values in a polymorphic manner, regardless of the type of the editable object:
public interface ITrackStatus
{
bool IsValid { get; }
bool IsSelfValid { get; }
bool IsDirty { get; }
bool IsSelfDirty { get; }
bool IsDeleted { get; }
bool IsNew { get; }
bool IsSavable { get; }
}
A UI framework often uses these status values to enable or disable various UI elements to give the user cues about what actions are possible at any point in time. As you'll see in Chapter 10, the CslaDataProvider
automatically handles these details in a WPF interface.
In the same way that IBusinessObject
provides a form of polymorphism and commonality across all business objects, IUndoableObject
does the same thing for any object that supports n-level undo. This includes those that inherit from BusinessBase<T>
and BusinessListBase<T,C>
, among others.
This polymorphic ability is of critical importance in the implementation of UndoableBase
, as discussed in Chapter 13. UndoableBase
needs to be able to treat all editable objects the same in order to implement the n-level undo functionality.
Here's the code for IUndoableObject
:
public interface IUndoableObject
{
int EditLevel { get; }
void CopyState(int parentEditLevel, bool parentBindingEdit);
void UndoChanges(int parentEditLevel, bool parentBindingEdit);
void AcceptChanges(int parentEditLevel, bool parentBindingEdit);
}
The n-level undo support implemented by UndoableBase
requires that every object implements the property and three methods listed in this interface.
Putting these methods in an interface is a double-edged sword. On one hand it clearly defines the methods and will make it easier to implement UndoableBase
. On the other hand, these methods are now potentially available to any code using a business object. In other words, a UI developer could write code to call these methods—almost certainly causing nasty bugs and side-effects because these methods aren't designed for public use.
This is a difficult design decision when building frameworks. In this case the benefits of having a common interface for use by UndoableBase
appears to outweigh the potential risk of a UI developer doing something foolish by calling the methods directly.
To help minimize this risk, the actual implementation methods in the base classes will keep these methods private
. That way, they can only be called by directly casting the object to the IUndoableObject
type.
All read-only and editable objects implement the System.ICloneable
interface. This interface defines a Clone()
method that returns an exact copy of the original object. Also remember that all business objects are mobile objects: marked with the Serializable
attribute.
Creating a clone of a Serializable
object is easily accomplished through the use of the BinaryFormatter
object in the System.Runtime.Serialization.Formatters.Binary
namespace.
Alternately you can configure CSLA .NET to use the NDCS provided as part of WCF. In that case, ObjectCloner
can clone both Serializable
and DataContract
objects. I discuss the pros and cons of using the NDCS in Chapter 4, and as stated there, I recommend using the BinaryFormatter
in most cases.
The implementation of cloning is a few lines of code. Rather than replicating this code in every base class, it can be centralized in a single object. All the base classes can then collaborate with this object to perform the clone operation.
The class contains the following code:
namespace Csla.Core
{
public static class ObjectCloner
{
public static object Clone(object obj)
{
using (MemoryStream buffer = new MemoryStream())
{
ISerializationFormatter formatter =
SerializationFormatterFactory.GetFormatter();
formatter.Serialize(buffer, obj);
buffer.Position = 0;
object temp = formatter.Deserialize(buffer);
return temp;
}
}
}
}
This class is static
, as there is no reason to create an instance of the class.
The Clone()
method itself uses a .NET formatter to serialize the object's state into an in-memory buffer. All objects referenced by the business object are also automatically serialized into the same buffer. The combination of an object and all the objects it references, directly or indirectly, is called an object graph.
The SerializationFormatterFactory.GetFormatter()
method returns an object that invokes either a BinaryFormatter
or NDCS based on how CSLA .NET is configured. The default is to use the BinaryFormatter
, but you can use the CslaSerializationFormatter
configuration setting in the application's config file to use the NDCS if desired.
The in-memory buffer is immediately deserialized to create a copy of the original object graph. The buffer is then disposed, as it could consume a fair amount of memory, depending on the size of the fields in your objects.
The resulting copy is returned to the calling code.
The ReadOnlyBindingList<C>
class implements a read-only collection based on System.ComponentModel.BindingList<T>
. The standard BindingList<T>
class implements a read-write collection that supports data binding, but there are numerous cases in which a read-only collection is useful. For example, ReadOnlyBindingList
is the base class for Csla.ReadOnlyListBase, Csla.NameValueListBase
and Csla.Validation.BrokenRulesCollection
.
This class inherits from BindingList<T>
. It is also Serializable
and abstract
, like all the framework base classes:
[Serializable]
public abstract class ReadOnlyBindingList<C> :
System.ComponentModel.BindingList<C>, Core.IBusinessObject
{
}
All the basic collection and data binding behaviors are already implemented by BindingList
. Making the collection read-only is a matter of overriding a few methods to prevent alteration of the collection. Of course, the collection has to be read-write at some point in order to get data into the collection at all. To control whether the collection is read-only, there's a field and a property:
private bool _isReadOnly = true;
public bool IsReadOnly
{
get { return _isReadOnly; }
protected set { _isReadOnly = value; }
}
Notice that while the IsReadOnly
property is public
for reading, it is protected
for changing. This way, any code can determine whether the collection is read-only or read-write, but only a subclass can lock or unlock the collection.
The class contains a constructor that turns off the options to edit, remove, or create items in the collection by setting some properties in the BindingList
base class:
protected ReadOnlyBindingList()
{
AllowEdit = false;
AllowRemove = false;
AllowNew = false;
}
The rest of the class overrides the methods in BindingList
that control alteration of the collection. Each override checks the IsReadOnly
property and throws an exception when an attempt is made to change the collection when it is in read-only mode.
The only complicated overrides are ClearItems()
and RemoveItem()
. This is because AllowRemove
is typically set to false
and must be temporarily changed to true
to allow the operation (when the collection is not in read-only mode). For instance, here's the ClearItems()
method:
protected override void ClearItems()
{
if (!IsReadOnly)
{
bool oldValue = AllowRemove;
AllowRemove = true;
base.ClearItems();
DeferredLoadIndexIfNotLoaded();
_indexSet.ClearIndexes();
AllowRemove = oldValue;
}
else
throw new NotSupportedException(Resources.ClearInvalidException);
}
The original AllowRemove
value is restored after the operation is complete.
Notice the call to DeferredLoadIndexIfNotLoaded()
, which triggers recreation of any indexes being maintained by LINQ to CSLA. LINQ to CSLA is discussed in Chapter 14.
This completes all the types in the Csla.Core
namespace. You can look at the full implementation of each type by downloading the code from www.apress.com/book/view/1430210192
or www.lhotka.net/cslanet/download.aspx
. Also, Chapters 7 through 16 cover many of the concepts and code in more detail.
In this chapter I started discussing the implementation of the CSLA .NET framework. This chapter walked through the overall project structure, including the folders and namespaces used to organize the code and types in the framework. It also provided some detail around the types in the Csla
and Csla.Core
namespaces. You can start to see how the framework supports features such as the following:
Data binding
Business and validation rules
Authorization rules
Object status tracking
Parent-child object relationships
Editable and read-only objects
N-level undo
Object persistence and the data portal
Chapters 7 through 16 provide more detail about the implementation of each major framework feature. From Chapter 17 on, the focus is on building the simple business application designed in Chapter 3 to illustrate how the classes in the framework can be used to build applications based on business objects.