Chapter 6. Business Framework Implementation

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.

CSLA .NET Project Structure

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.

Project Directory Structure

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.

Project Settings

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.

Visual Studio Application tab

Figure 6.1. Visual Studio Application tab

You can also see the assembly information shown in Figure 6-2 by clicking the Assembly Information button.

Assembly Information dialog

Figure 6.2. Assembly Information dialog

Note

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.

Project Signing

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.

Visual Studio Signing tab

Figure 6.3. Visual Studio Signing tab

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.

Supporting Localization

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.

Visual Studio resource editor

Figure 6.4. Visual Studio resource editor

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.

Csla Namespace

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.

Primary classes in the Csla namespace

Figure 6.5. Primary classes in the Csla namespace

Table 6-2 lists primary classes in the Csla namespace.

Table 6.2. Primary Classes in the Csla Namespace

Type

Description

ApplicationContext

Class that provides access to important application context information; used by the framework, business classes, and UI code

BusinessBase

Base class from which editable root, child, and switchable objects inherit

BusinessListBase

Base class from which editable root and child list objects inherit

CommandBase

Base class from which command objects inherit

CriteriaBase

Base class from which custom criteria objects inherit

DataPortal

Class that exposes the data portal functionality to the client

EditableRootListBase

Base class from which dynamic list objects inherit

NameValueListBase

Base class from which name/value list objects inherit

PropertyInfo

Class that defines metadata for each business object property

ReadOnlyBase

Base class from which read-only root and child objects inherit

ReadOnlyListBase

Base class from which read-only root and child list objects inherit

SingleCriteria

Class that provides a single value criteria for any object

SmartDate

Type that extends DateTime to add the concept of an empty date and other features

Utilities

Type that includes utility methods used by other classes

I'll discuss each of these types at a high level.

ApplicationContext

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

GlobalContext

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

ClientContext

Collection of context data that flows from client to server; changes on the server are not carried back to the client

LocalContext

Collection of context data that exists only in the current location (client or server)

User

Current .NET security (principal) object; safely accesses this value independent of runtime (ASP.NET, WPF, etc.)

AuthenticationType

Authentication setting from CslaAuthentication config value

DataPortalProxy

Data portal proxy provider setting from CslaDataPortalProxy config value

DataPortalUrl

Data portal URL value for Remoting proxy from CslaDataPortalUrl config value

IsInRoleProvider

IsInRole provider type name from CslaIsInRoleProvider config value

AutoCloneOnUpdate

Setting indicating whether objects are cloned before update through local data portal, setting from CslaAutoCloneOnUpdate config value

SerializationFormatter

Serialization provider type name from CslaSerializationFormatter config value

PropertyChangedMode

Setting indicating how the PropertyChanged event should be raised (different for Windows Forms or WPF), setting from CslaPropertyChangedMode config value

ExecutionLocation

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.

Note

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.

BusinessBase

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 IDataErrorInfo interface

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 UndoableBase and implements the IEditableObject interface

Cloning

Implements the ICloneable interface

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.

Inheritance hierarchy for BusinessBase<T>

Figure 6.6. Inheritance hierarchy for BusinessBase<T>

These base classes are covered in Chapters 7 through 14 as each functional area is discussed.

Note

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.

Objects contained by BusinessBase<T>

Figure 6.7. Objects contained by BusinessBase<T>

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.

BusinessListBase

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:

Inheritance hierarchy for BusinessListBase<T>

Figure 6.8. Inheritance hierarchy for BusinessListBase<T>

Note

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.

Note

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.

CommandBase

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.

Note

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.

CriteriaBase

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

Note

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.

DataPortal

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.

EditableRootListBase

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.

NameValueListBase

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.

Inheritance hierarchy for NameValueListBase<K, V>

Figure 6.9. Inheritance hierarchy for NameValueListBase<K, V>

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.

Note

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.

PropertyInfo

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.

Note

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.

ReadOnlyBase

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.

Note

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.

Inheritance hierarchy for ReadOnlyBase<T>

Figure 6.10. Inheritance hierarchy for ReadOnlyBase<T>

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.

ReadOnlyListBase

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.

SingleCriteria

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.

SmartDate

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

Note

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.

Note

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.

Utilities

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

IsNumeric

Provides functionality comparable to the VB runtime IsNumeric() function; determines whether a value can be converted to a number

CallByName

Provides functionality comparable to the VB runtime CallByName() function; calls a property or method by name, using reflection

GetPropertyType

Gets the type returned by a property, returning the primitive type even if the value is Nullable<T>

GetChildItemType

Gets the type of the items contained in a list or collection

CoerceValue

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.

Csla.Core Namespace

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

BusinessBase

Used to create code that can interact with any editable root or child object; base class for BusinessBase<T>

ExtendedBindingList

Used to add a RemovingItem event and an AddRange() method to BindingList<T>; base class for most collection types

IBusinessObject

Implemented by all base classes to provide one common type for all business objects

ISavable

Implemented by all editable base classes; used to create code that can polymorphically save any editable object

ISmartField

Implemented by all "smart" types such as SmartDate; can be used to create other "smart" types such as SmartInt, SmartDouble, etc.

ISupportUndo

Implemented by all undoable base classes; used to create UI frameworks that polymorphically implement form-level cancel buttons

ITrackStatus

Implemented by base classes that maintain object status such as IsDirty, IsValid, etc.; used to write code that polymorphically monitors or reacts to business object status

ObjectCloner

Contains code to clone any object decorated with the Serializable attribute (or DataContract attribute if CSLA is configured to use WCF serialization)

ReadOnlyBindingList

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.

BusinessBase

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.

ExtendedBindingList

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.

IBusinessObject Interface

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.

ICommandObject Interface

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.

IEditableBusinessObject Interface

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.

IEditableCollection Interface

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.

IReadOnlyObject Interface

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.

IReadOnlyCollection Interface

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.

ISavable Interface

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;
}

Warning

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.

ISmartField Interface

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.

Note

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.

ISupportUndo Interface

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.

Warning

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.

ITrackStatus Interface

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.

IUndoableObject 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.

ObjectCloner Class

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.

ReadOnlyBindingList

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.

Conclusion

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset