Chapter 5. CSLA .NET Object Templates

In Chapter 4, I discussed the common object stereotypes directly supported by CSLA .NET, as well as many of the high-level details about the structure and life cycle of the different types of business object.

In this chapter, I'll continue that discussion, but at a lower level of detail. This chapter will walk through each of the stereotypes, providing a generalized code template to show the detailed code structure of each one. As a refresher, these are the stereotypes that CSLA .NET supports directly:

  • Editable root

  • Editable child

  • Editable, switchable (i.e., root or child) object

  • Editable root collection

  • Editable child collection

  • Read-only root

  • Read-only root collection

  • Read-only child

  • Read-only child collection

  • Command object

  • Name/value list

  • Dynamic editable collection

  • Dynamic editable root

  • Criteria

If your application requires other stereotypes, you can usually extend CSLA .NET to accommodate those requirements, but these stereotypes are sufficient for the majority of business applications I've encountered in my career.

The templates I'll create in this chapter aren't complete business object implementations, but they do illustrate the basic structure you need to follow when creating that type of business object. This information will help you create class templates or code snippets for use in Visual Studio and will make your development experience more productive.

Business Class Structure

The flow of this chapter will be straightforward. I'll discuss each stereotype in turn, focusing on the coding template for each type of object.

Editable Root Business Objects

The most common type of object is the editable root business object, since any object-oriented system based on CSLA .NET typically has at least one root business object or root collection. (Examples of this type of object include the Project and Resource objects discussed in Chapter 3.) These objects often contain collections of child objects, as well as their own object-specific data.

In addition to being common, an editable object that's also a root object is the most complex object type, so its code template covers all the possible code regions discussed in Chapter 4. The basic structure for an editable root object, with example or template code in each region, is as follows:

[Serializable]
public class EditableRoot : BusinessBase<EditableRoot>
{
  #region Business Methods

  // TODO: add your own fields, properties and methods

  // example with private backing field
  private static PropertyInfo<int> IdProperty =
    RegisterProperty(typeof(EditableRoot), new PropertyInfo<int>("Id"));
  private int _Id = IdProperty.DefaultValue;
  public int Id
  {
    get { return GetProperty(IdProperty, _Id); }
    set { SetProperty(IdProperty, ref _Id, value); }
  }

  private static PropertyInfo<string> NameProperty =
    RegisterProperty(typeof(EditableRoot), new PropertyInfo<string>("Name"));
  public string Name
  {
    get { return GetProperty(NameProperty); }
    set { SetProperty(NameProperty, value); }
  }

  #endregion

  #region Validation Rules

  protected override void AddBusinessRules()
  {
    // TODO: add validation rules
    //ValidationRules.AddRule(RuleMethod, NameProperty);
  }

  #endregion

  #region Authorization Rules
protected override void AddAuthorizationRules()
  {
    // TODO: add authorization rules
    //AuthorizationRules.AllowWrite(NameProperty, "Role");
  }

  private static void AddObjectAuthorizationRules()
  {
    // TODO: add authorization rules
    //AuthorizationRules.AllowEdit(typeof(EditableRoot), "Role");
  }

  #endregion

  #region Factory Methods

  public static EditableRoot NewEditableRoot()
  {
    return DataPortal.Create<EditableRoot>();
  }

  public static EditableRoot GetEditableRoot(int id)
  {
    return DataPortal.Fetch<EditableRoot>(
      new SingleCriteria<EditableRoot, int>(id));
  }

  public static void DeleteEditableRoot(int id)
  {
    DataPortal.Delete(new SingleCriteria<EditableRoot, int>(id));
  }

  private EditableRoot()
  { /* Require use of factory methods */ }

  #endregion

  #region Data Access

  [RunLocal]
  protected override void DataPortal_Create()
  {
    // TODO: load default values
    // omit this override if you have no defaults to set
    base.DataPortal_Create();
  }

  private void DataPortal_Fetch(SingleCriteria<EditableRoot, int> criteria)
  {
    // TODO: load values
  }
[Transactional(TransactionalTypes.TransactionScope)]
  protected override void DataPortal_Insert()
  {
    // TODO: insert values
  }

  [Transactional(TransactionalTypes.TransactionScope)]
  protected override void DataPortal_Update()
  {
    // TODO: update values
  }

  [Transactional(TransactionalTypes.TransactionScope)]
  protected override void DataPortal_DeleteSelf()
  {
    DataPortal_Delete(new SingleCriteria<EditableRoot, int>(this.Id));
  }

  [Transactional(TransactionalTypes.TransactionScope)]
  private void DataPortal_Delete(SingleCriteria<EditableRoot, int> criteria)
  {
    // TODO: delete values
  }

  #endregion


}

You must define the class, which includes making it serializable, giving it a name, and having it inherit from BusinessBase.

The Business Methods region includes all member or instance field declarations, along with any business-specific properties and methods. These properties and methods typically interact with the instance fields, performing calculations and other manipulation of the data based on the business logic.

I have included examples for the two most common types of property declaration. The first example uses a private backing field, which you declare directly in code:

// example with private backing field
    private static PropertyInfo<int> IdProperty =
      RegisterProperty(typeof(EditableRoot), new PropertyInfo<int>("Id"));
  private int _Id = IdProperty.DefaultValue;
    public int Id
    {
      get { return GetProperty(IdProperty, _Id); }
      set { SetProperty(IdProperty, ref _Id, value); }
    }

This approach performs better than the alternative, but does require slightly more complex code. Additionally, if you intend on using CSLA .NET for Silverlight to create a Silverlight version of your application, this approach will require extra coding to support that environment.

Note

While this book does not explicitly cover CSLA .NET for Silverlight, I will comment, where appropriate, on choices you can make in CSLA .NET for Windows that could make it easier or harder to reuse your code in CSLA .NET for Silverlight.

The second example uses a managed backing field, meaning that CSLA .NET manages the field value, so you don't need to declare your own field:

private static PropertyInfo<string> NameProperty =
      RegisterProperty(typeof(EditableRoot), new PropertyInfo<string>("Name"));
    public string Name
    {
      get { return GetProperty(NameProperty); }
      set { SetProperty(NameProperty, value); }
    }

This approach does incur a performance penalty, because the field value is stored in a data structure instead of a simple field. However, it requires less code and works automatically with CSLA .NET for Silverlight in the Silverlight environment.

The Validation Rules region, at a minimum, overrides the AddBusinessRules() method. In this method, you call ValidationRules.AddRule() to associate rule methods with properties. This region may also include custom rule methods for rules that aren't already available in Csla.Validation.CommonRules or in your own library of rule methods.

The Authorization Rules region overrides the AddAuthorizationRules() method and implements the static AddObjectAuthorizationRules() method.

The AddAuthorizationRules() method should include calls to methods on the AuthorizationRules object: AllowRead(), AllowWrite(), DenyRead(), DenyWrite(), AllowExecute(), and DenyExecute(). Each one associates a property or method with a list of roles that are to be allowed read and write access to that element.

The AddObjectAuthorization() method allows you to specify the roles that can interact with the object itself. You can specify the roles that area allowed to create, get, edit, and delete the object. You do this by calling the AllowCreate(), AllowGet(), AllowEdit(), and AllowDelete() methods on the Csla.Security.AuthorizationRules type.

The purpose of this object-level authorization is to allow the UI developer to easily determine whether the current user can get, add, update, or delete this type of object. That way, the UI can enable, disable, or hide controls to provide appropriate visual cues to the end user. Additionally, the data portal uses these rules to ensure that only authorized users are able to perform a requested action.

In the Factory Methods region, static factory methods create, retrieve, and delete the object. Of course, these are just examples that you must change as appropriate. You must tailor the accepted parameters to match the identifying criteria for your particular business object.

The example code uses the CSLA .NET SingleCriteria class. As discussed in Chapter 4, if your object requires more complex criteria, you'll need to create your own criteria class. The criteria stereotype is discussed later in this chapter.

Finally, the Data Access region includes the DataPortal_XYZ methods. These methods must include the code to load defaults, retrieve object data, and insert, update, and delete object data, as appropriate. In most cases, you do this through ADO.NET, but you could just as easily implement this code to read or write to an XML file, call a web service, or use any other data store you can imagine.

You can safely omit the DataPortal_Create() method if your object doesn't require any initialization when you create it. If your object does require default values to be loaded into properties (hard-coded from a config file or from a database table), then you should implement the method to initialize those values.

Notice the RunLocal attribute on the DataPortal_Create() method.

[RunLocal]
    protected override void DataPortal_Create()

This attribute is used to force the data portal to run the method locally. You use this attribute for methods that do not need to interact with server-side resources (like the database) when they are executed. Typically, you would only use this attribute on DataPortal_Create(), and only if you don't need to load default values from a database table into a new object. The use of the RunLocal attribute is optional, and when you use this attribute, the decorated DataPortal_XYZ method must not access the database, because it may not be running in a physical location where the database is available.

The Transactional attributes on the methods that insert, update, or delete data specify that those methods should run within a System.Transactions.TransactionScope transactional context.

[Transactional(TransactionalTypes.TransactionScope)]
    protected override void DataPortal_Update()

You may opt instead to use the TransactionTypes.EnterpriseServices setting to run within a COM+ distributed transaction, or the TransactionTypes.Manual setting to handle your own transactions using ADO.NET or stored procedures.

Tip

Many organizations use an abstract, metadata-driven Data Access layer. In environments like this, the business objects don't use ADO.NET directly. This works fine with CSLA .NET, since the data access code in the DataPortal_XYZ methods can interact with an abstract Data Access layer just as easily as it can interact with ADO.NET directly.

The key thing to note about this code template is that there's very little code in the class that's not related to the business requirements. Most of the code implements business properties, validation, and authorization rules or data access. The bulk of the nonbusiness code (code not specific to your business problem) is already implemented in the CSLA .NET framework.

Immediate or Deferred Deletion

As implemented in the template, the UI developer can delete the object by calling the static delete method and providing the criteria to identify the object to be deleted. Another option is to implement deferred deletion, whereby the object must be retrieved, marked as deleted, and then updated in order for it to be deleted. The object's data is then deleted as part of the update process.

To support deferred deletion, simply remove the static delete method.

//  public static void DeleteEditableRoot(int id)
//  {
//    DataPortal.Delete(new Criteria(id));
//  }

Then, the only way to delete the object is by calling the Delete() method on an instance of the object and updating that object to the database by calling Save(). You would use this UI code:

var root = EditableRoot.GetEditableRoot(123);
  root.Delete();
  root.Save();

Because immediate deletion is the more common model, that is what I chose to show in the template.

Object State Management

The data portal automatically manages the state of the business object. Table 5-1 shows the state of the object after each DataPortal_XYZ method.

Table 5.1. Object State After Data Portal Methods Complete

Method

Object State After Method Completes

DataPortal_Create()

IsNew is true; IsDirty is true; IsDeleted is false

DataPortal_Fetch()

IsNew is false; IsDirty is false; IsDeleted is false

DataPortal_Insert()

IsNew is false; IsDirty is false; IsDeleted is false

DataPortal_Update()

IsNew is false; IsDirty is false; IsDeleted is false

DataPortal_DeleteSelf()

IsNew is true; IsDirty is true; IsDeleted is false

DataPortal_Delete()

Not applicable; object not returned

Because the data portal takes care of these details, you can simply use the state properties without worrying about maintaining them yourself.

Using an Object Factory

You can also choose to use the ObjectFactory attribute on the business class to indicate that the data portal should use an object factory rather than interact with the business object directly.

In that case, you need to make some slight alterations to the template. You need to add the ObjectFactory attribute to the class.

[ObjectFactory("MyFactories.MyFactory, MyFactories")]
  [Serializable]
  public class EditableRoot : BusinessBase<EditableRoot>

You also need to eliminate the entire Data Access region from the template. The data portal will not invoke any DataPortal_XYZ methods if the class has an ObjectFactory attribute. Instead, the data portal will create an instance of the specified object factory type and will invoke methods on that object.

The following illustrates the basic structure of an object factory class:

public class MyFactory : Csla.Server.ObjectFactory
{
  public object Create()
  {
    EditableRoot result = new EditableRoot();
    // initialize the new object with default data
    MarkNew(result);
    return result;
  }
public object Fetch(SingleCriteria<EditableRoot, int> criteria)
  {
    EditableRoot result = new EditableRoot();
    // load the new object with data based on the criteria
    MarkOld(result);
    return result;
  }

  public object Update(object obj)
  {
    // insert, update or delete the data for obj
    MarkOld(obj);
    return obj;
  }

  public void Delete(SingleCriteria<EditableRoot, int> criteria)
  {
    // delete data based on the criteria
  }
}

The object factory assumes complete responsibility for creating and interacting with the business object, including setting the object's state by calling the protected methods from the ObjectFactory base class: MarkNew() and MarkOld().

While the object factory model requires more code and effort, it does provide more flexibility, and it can provide better separation between the business and data access logic.

Editable Child Business Objects

Most applications will have some editable child objects, or even grandchild objects. Examples of these include the ProjectResource and ResourceAssignment objects from the Project Tracker reference application. In many cases, the child objects are contained within a child collection object, which I'll discuss later. In other cases, the child object might be referenced directly by the parent object. Either way, the basic structure of a child object is the same; in some ways, this template is similar to the editable root:

[Serializable]
public class EditableChild : BusinessBase<EditableChild>
{
  #region Business Methods

  // TODO: add your own fields, properties, and methods
// example with private backing field
  private static PropertyInfo<int> IdProperty =
    RegisterProperty(typeof(EditableChild), new PropertyInfo<int>("Id"));
  private int _Id = IdProperty.DefaultValue;
  public int Id
  {
    get { return GetProperty(IdProperty, _Id); }
    set { SetProperty(IdProperty, ref _Id, value); }
  }

  // example with managed backing field
  private static PropertyInfo<string> NameProperty =
    RegisterProperty(typeof(EditableChild), new PropertyInfo<string>("Name"));
  public string Name
  {
    get { return GetProperty(NameProperty); }
    set { SetProperty(NameProperty, value); }
  }

  #endregion

  #region Validation Rules

  protected override void AddBusinessRules()
  {
    // TODO: add validation rules
    //ValidationRules.AddRule(RuleMethod, NameProperty);
  }

  #endregion

  #region Authorization Rules

  protected override void AddAuthorizationRules()
  {
    // TODO: add authorization rules
    //AuthorizationRules.AllowWrite(NameProperty, "Role");
  }

  private static void AddObjectAuthorizationRules()
  {
    // TODO: add authorization rules
    //AuthorizationRules.AllowEdit(typeof(EditableChild), "Role");
  }

  #endregion

  #region Factory Methods

  internal static EditableChild NewEditableChild()
  {
    return DataPortal.CreateChild<EditableChild>();
  }

  internal static EditableChild GetEditableChild(object childData)
  {
    return DataPortal.FetchChild<EditableChild>(childData);
  }

  private EditableChild()
  { /* Require use of factory methods */ }

  #endregion
#region Data Access

  protected override void Child_Create()
  {
    // TODO: load default values
    // omit this override if you have no defaults to set
    base.Child_Create();
  }

  private void Child_Fetch(object childData)
  {
    // TODO: load values
  }

  private void Child_Insert(object parent)
  {
    // TODO: insert values
  }

  private void Child_Update(object parent)
  {
    // TODO: update values
  }

  private void Child_DeleteSelf(object parent)
  {
    // TODO: delete values
  }

  #endregion
}

As with all business classes, this one is serializable and inherits from a CSLA .NET base class. The fact that it is a child object is specified by the data portal calls to CreateChild() and FetchChild() in the factory methods. Behind the scenes, the data portal calls the MarkAsChild() method so the object is explicitly marked as a child, and if you choose not to use the data portal to create your child objects, you'll need to ensure manually that MarkAsChild() is called.

The Business Methods region is the same as with a root object: it simply implements the properties and methods required by the business rules. Similarly, the Validation Rules and Authorization Rules regions are the same as with a root object.

The Factory Methods region is a bit different. The factory methods are internal rather than public, as they should be called only by the parent object, not by the UI code. Also, there's no need for a static delete method, because BusinessBase implements a DeleteChild() method that BusinessListBase calls automatically when the child is removed from a collection. Perhaps most importantly, notice the data portal calls to CreateChild() and FetchChild() (rather than to Create() and Fetch()) in the factory methods.

Also, notice how the GetEditableChild() method accepts a parameter containing child data. This parameter is passed from the parent and includes the data necessary to load this child object's field values. Normally this value will be a LINQ to SQL or ADO.NET Entity Framework entity object, an ADO.NET DataReader that is already pointing to the correct row of data, or something similar. And normally the parameter won't be of type object, but will be strongly typed.

The biggest difference from a root object comes in the Data Access region. Instead of DataPortal_XYZ methods, a child object implements Child_XYZ methods. The Child_Create() method is implemented to support the loading of default values on the creation of a new child object. Please note that this method doesn't run on the application server, so Child_Create() cannot talk to the database. It is only useful for loading default values that are hard-coded, from some other object, or from a client-side configuration file.

The Child_Fetch() method typically accepts a parameter containing the data that should be used to load the object's field values. Normally the parent object loads all the data for its children and provides that data through a parameter. A less efficient approach is to have each child object contact the database independently to load its data.

The Child_Insert(), Child_Update(), and Child_Delete() methods typically accept a reference to the parent object as a parameter. The assumption is that any child object will need data from the parent while being inserted or updated into the database. Most often, the parent contains a foreign key value required by the child object during data access.

Note

Typically, the parent parameter will be strongly typed based on the class of the parent object itself.

In Project Tracker, for example, the ProjectResource child object needs the Id property from its parent Project object so that it can store it as a foreign key in the database. By getting a reference to its parent Project object, the ProjectResource gains access to that value as needed.

Loading Default Values from a Data Store

If you need to load default values from the database into a child object, the NewEditableChild() factory method must call DataPortal.Create(), and the object must implement a DataPortal_Create() method just like an editable root object. Because this is a child object, the DataPortal_Create() method would need to call MarkAsChild(), as shown here:

protected override void DataPortal_Create()
  {
    MarkAsChild();
    // TODO: load default values here
    base.DataPortal_Create();
  }

The rest of the Child_XYZ methods (Fetch, Insert, Update, and DeleteSelf) will all run on the application server and can assume they have access to the database.

You can choose to use the ObjectFactory with a child object as well, if you want to supply an object factory class. In this case, you would not implement a DataPortal_Create() method in the business class, but instead would implement a Create() method in the object factory class.

The object factory class would look like this:

public class MyChildFactory : Csla.Server.ObjectFactory
{
  public object Create()
  {
    EditableChild result = new EditableChild();
    // initialize the new object with default data
    MarkNew(result);
    MarkAsChild(result);
    return result;
  }
}

This is mostly the same as the code I described earlier for an editable root. The differences here are that only the Create() method is implemented, and the MarkAsChild() method is called to mark the new business object as a child object.

Object State Management

The data portal manages the state of the business object automatically. Table 5-2 shows the state of the object after each Child_XYZ method.

The only exception is that if you use the ObjectFactory attribute to initialize a new child object with values from the database, then the object factory will be responsible for setting the business object's state as it is created. Even in that case, the data portal will still manage the child object's state for fetch, insert, update, and delete operations.

Table 5.2. Object State After Data Portal Methods Complete

Method

Object State After Method Completes

Child_Create()

IsNew is true; IsDirty is true; IsDeleted is false

Child_Fetch()

IsNew is false; IsDirty is false; IsDeleted is false

Child_Insert()

IsNew is false; IsDirty is false; IsDeleted is false

Child_Update()

IsNew is false; IsDirty is false; IsDeleted is false

Child_DeleteSelf()

IsNew is true; IsDirty is true; IsDeleted is false

Because the data portal takes care of these details, you can simply use the state properties without worrying about maintaining them yourself.

Switchable Objects

It's possible that some classes must be instantiated as root objects on some occasions and as child objects on others. You can handle this by implementing a set of static factory methods for the root model, and another set for the child model. You also need to implement both the DataPortal_XYZ and Child_XYZ methods in the Data Access region.

Tip

In most cases, the need for a switchable object indicates a flawed object model. Although there are exceptions for this that make sense, you should examine your object model carefully to see if there's a simpler solution before implementing a switchable object.

The template for creating a switchable object is the same as the editable root template, with the following exceptions:

  • Dual factory methods

  • Dual data access methods

Let's discuss each change in turn.

Dual Factory Methods

Instead of single factory methods to create and retrieve the object, you will need two methods for each operation: one public, the other internal. To keep this organized, you may consider creating separate code regions for each. For example, one region may have the root factories.

#region Root Factory Methods

  public static SwitchableObject NewSwitchableObject()
  {
    return DataPortal.Create<SwitchableObject>();
  }

  public static SwitchableObject GetSwitchableObject(int id)
  {
    return DataPortal.Fetch<SwitchableObject>(
      new SingleCriteria<SwitchableObject, int>(id));
  }

  public static void DeleteSwitchableObject(int id)
  {
    DataPortal.Delete(new SingleCriteria<SwitchableObject, int>(id));
  }

  #endregion

Notice that this region is no different from the factory region in an editable root. Then there's a whole other set of factory methods to support the object's use as a child.

#region Child Factory Methods

  internal static SwitchableObject NewSwitchableChild()
  {
    return DataPortal.CreateChild<SwitchableObject>();
  }

  internal static SwitchableObject GetSwitchableChild(object childData)
  {
    return DataPortal.FetchChild<SwitchableObject>(childData);
  }

  private SwitchableObject()
  { /* Require use of factory methods */ }

  #endregion

This set of factory methods is the same as what you'd see in an editable child.

The key here is that the UI can call the public factory methods directly to interact with the object as a root, while only a parent object can call the internal factory methods to use the object as a child.

Dual Data Access Methods

Because the public factory methods call the Create() and Fetch() methods on the data portal, those calls are routed to DataPortal_XYZ methods. In those cases, the object is a root, so you can call the Save() method, which also routes to the DataPortal_XYZ methods.

At the same time, the internal factory methods call CreateChild() and FetchChild() on the data portal, which route to Child_XYZ methods. In those cases, the object would be a child, so its parent would be responsible for saving the child object; that is also handled through Child_XYZ methods.

The end result is that the object must implement both sets of data access code to handle both the root and child scenarios. It is often simpler to create two data access regions—one for each set of methods. For example, here's the region for the root methods:

#region Root Data Access

  [RunLocal]
  protected override void DataPortal_Create()
  {
    // TODO: load default values
    // omit this override if you have no defaults to set
    base.DataPortal_Create();
  }

  private void DataPortal_Fetch(SingleCriteria<SwitchableObject, int> criteria)
  {
    // TODO: load values
  }

  [Transactional(TransactionalTypes.TransactionScope)]
  protected override void DataPortal_Insert()
  {
    // TODO: insert values
  }

  [Transactional(TransactionalTypes.TransactionScope)]
  protected override void DataPortal_Update()
  {
    // TODO: update values
  }

  [Transactional(TransactionalTypes.TransactionScope)]
  protected override void DataPortal_DeleteSelf()
  {
    DataPortal_Delete(new SingleCriteria<SwitchableObject, int>(this.Id));
  }

  [Transactional(TransactionalTypes.TransactionScope)]
  private void DataPortal_Delete(SingleCriteria<SwitchableObject, int> criteria)
  {
    // TODO: delete values
  }

  #endregion

This is the same code you'd see in an editable root object. You'll also need a region for the child scenario.

#region Child Data Access

  protected override void Child_Create()
  {
    // TODO: load default values
    // omit this override if you have no defaults to set
    base.Child_Create();
  }

  private void Child_Fetch(object childData)
  {
    // TODO: load values
  }

  private void Child_Insert(object parent)
  {
    // TODO: insert values
  }

  private void Child_Update(object parent)
  {
    // TODO: update values
  }

  private void Child_DeleteSelf(object parent)
  {
    // TODO: delete values
  }

  #endregion

Again, this is the same code you'd see in an editable child.

The result is that the object is treated as a root object when the public factory methods are called, and as a child when the internal factory methods are called. The data portal takes care of calling MarkAsChild() and managing the object's state, as shown previously in Tables 5-1 and 5-2.

Editable Root Collection

At times, applications need to retrieve a collection of child objects directly. To do this, you need to create a root collection object. For instance, the application may have a WPF UI consisting of a ListBox control that displays a collection of Contact objects. If the root object is a collection of child Contact objects, the UI developer can simply bind the collection to the ListBox (with an appropriate data template), and the user can edit all the objects in the list.

This approach means that all the child objects are handled as a single unit in terms of data access. They are loaded into the collection to start with, so the user can interact with all of them and then save them as a batch when all edits are complete. This is only subtly different from having a regular root object that has a collection of child objects. Figure 5-1 shows the regular root object approach on the left, and the collection root object approach on the right.

Comparing simple root objects (left) and collection root objects (right)

Figure 5.1. Comparing simple root objects (left) and collection root objects (right)

This approach isn't recommended when there are large numbers of potential child objects, because the retrieval process can become too slow. However, it can be useful in cases where you can specify criteria to limit the number of objects returned. To create an editable root collection object, use a template like this:

[Serializable]
public class EditableRootList :
  BusinessListBase<EditableRootList, EditableChild>
{
  #region Authorization Rules

  private static void AddObjectAuthorizationRules()
  {
    // TODO: add authorization rules
    //AuthorizationRules.AllowGet(typeof(EditableRootList), "Role");
  }

  #endregion

  #region Factory Methods

  public static EditableRootList NewEditableRootList()
  {
    return DataPortal.Create<EditableRootList>();
  }

  public static EditableRootList GetEditableRootList(int id)
  {
    return DataPortal.Fetch<EditableRootList>(
      new SingleCriteria<EditableRootList, int>(id));
  }
private EditableRootList()
  { /* Require use of factory methods */ }

  #endregion

  #region Data Access

  private void DataPortal_Fetch(
    SingleCriteria<EditableRootList, int> criteria)
  {
    RaiseListChangedEvents = false;
    // TODO: load values into memory
    object childData = null;
    foreach (var item in (List<object>)childData)
      this.Add(EditableChild.GetEditableChild(childData));
    RaiseListChangedEvents = true;
  }

  #endregion
}

The Authorization Rules region implements the AddObjectAuthorizationRules() method to define the roles that can interact with the object. This is the same as with an editable root object, and the UI developer can use this information to enable and disable UI elements accordingly. Also, the data portal uses these rules to ensure only authorized users are able to create, get, edit, or delete the collection object. The one difference is that this AddObjectAuthorizationRules() method only needs to define the roles allowed to get the object.

The Factory Methods region implements factory methods to create, retrieve, and (optionally) delete the collection. The methods rely on the data portal to do much of the work, ultimately delegating the call to the appropriate DataPortal_XYZ method.

In the Data Access region, the DataPortal_Fetch() method is responsible for getting the data from the database using whatever data access technology you choose. This is often LINQ to SQL, the ADO.NET Entity Framework, or raw ADO.NET. In any case, the Data Access layer must return the child data as a result so it can use it to load each individual child object with data.

You load each child object by calling the child object's factory method, passing the object's data as a parameter. The resulting child object is added to the collection. The DataPortal_Fetch() method sets the RaiseListChanged Events property to false before changing the collection, and then restores it to true once the operation is complete. Setting this property to false tells the base BindingList<T> class to stop raising the ListChanged event. When doing batches of updates or changes to a collection, this can increase performance.

The BusinessListBase class includes a default implementation of the DataPortal_Update() method, which loops through all child objects (and deleted child objects) to ensure the appropriate Child_XYZ method is called on each when the collection is being saved. Normally this is the desired behavior, but you can override DataPortal_Update() if you need to take control of the update process for some unusual scenario.

As with the editable root stereotype, you can use the ObjectFactory attribute to have the data portal invoke an object factory rather than invoke the DataPortal_XYZ methods directly in the business class.

Collection objects that inherit from BusinessListBase automatically support data binding in WPF, Windows Forms, and Web Forms. However, if you bind a collection to a Windows Forms DataGrid control, you may be surprised to find that the user can't just add new items by going to the bottom of the grid. You need to write a bit of extra code in your collection to enable this behavior.

In the constructor, you need to give data binding permission to add new items to the collection by setting the AllowNew property.

private EditableRootList()
    {
      /* Require use of factory methods */
    AllowNew = true;
    }

When you do this, you also must override the AddNewCore() method. This method is defined by the .NET collection base class from which BusinessListBase inherits, and it is this method that is responsible for creating a new child object and adding it to the collection when requested by data binding.

The override looks like this:

protected override EditableChild AddNewCore()
    {
      var item = EditableChild.NewEditableChild();
      Add(item);
      return item;
    }

The method must perform three tasks: create the object, add it to the collection, and return the object as a result. Notice that no parameter is provided to AddNewCore(). This means that you must be able to create new child objects without providing any criteria or other information. Usually this is not a problem, but if the only way to create your new child object is with some criteria information, then you won't be able to enable this feature of data binding.

Editable Child Collection

The most common type of collection is one that is contained within a parent object to manage a collection of child objects for that parent—for example, ProjectResources and ResourceAssignments in the sample application.

Tip

Note that the parent object here might be a root object, or it might be a child itself. Child objects can be nested, if that's what the business object model requires. In other words, this concept supports not only root-to-child, but also child-to-grandchild and grandchild-to-great-grandchild relationships.

A child collection class inherits from BusinessListBase and has factory methods that use the CreateChild() and FetchChild() data portal methods, so the object is marked as a child when it is created. If you choose to avoid the data portal for creating the child object, you must make sure MarkAsChild() is called during the object's creation process to indicate that it's operating in child mode.

Remember that child objects are not retrieved or updated directly by the UI, but instead are retrieved or updated by the child object's parent object.

[Serializable]
public class EditableChildList :
  BusinessListBase<EditableChildList, EditableChild>
{
  #region Factory Methods
internal static EditableChildList NewEditableChildList()
  {
    return DataPortal.CreateChild<EditableChildList>();
  }

  internal static EditableChildList GetEditableChildList(
    object childData)
  {
    return DataPortal.FetchChild<EditableChildList>(childData);
  }

  private EditableChildList()
  { }

  #endregion

  #region Data Access

  private void Child_Fetch(object childData)
  {
    RaiseListChangedEvents = false;
    foreach (var child in (IList<object>)childData)
      this.Add(EditableChild.GetEditableChild(child));
    RaiseListChangedEvents = true;
  }

  #endregion
  }

As you can see, this code is similar to a root collection in structure, though there is no Authorization Rules region.

The factory methods are somewhat different, because they are scoped as internal and call the child data portal methods. Notice that the GetEditableChildList() method requires that the parent provide a preloaded object containing the data for the list's child objects. It is less efficient, but possible, for the child list to go directly to the database in its Child_Fetch() method and load its own data, but typically the parent object provides this data, as mocked up here.

The Data Access region contains a Child_Fetch() method, which is responsible for creating all child objects by calling their factory methods, and adding those objects to the collection. As with any collection, the RaiseListChangedEvents property is set to false first, and to true when complete to prevent a flood of unnecessary ListChanged events during the data-retrieval process.

The BusinessListBase class contains a default implementation for Child_Update() that automatically calls the right Child_XYZ method on all child objects of the list when the list is saved. Normally this implementation is sufficient, but you can override Child_Update() to customize that behavior if required for some unusual edge case.

As with an editable root collection, you can set AllowNew to true and override AddNewCore() if you want data binding to add new items to the collection automatically.

Read-Only Business Objects

Sometimes, an application may need an object that provides data in a read-only fashion. For a read-only list of data, there's ReadOnlyListBase; however, if the requirement is for a single object containing read-only data, it should inherit from ReadOnlyBase. This is one of the simplest types of objects to create, since it does nothing more than retrieve and return data, as shown here:

[Serializable]
public class ReadOnlyRoot : ReadOnlyBase<ReadOnlyRoot>
{
  #region Business Methods

  // TODO: add your own fields, properties and methods

  // example with managed backing field
  private static PropertyInfo<int> IdProperty =
    RegisterProperty(typeof(ReadOnlyRoot), new PropertyInfo<int>("Id", "Id"));
  public int Id
  {
    get { return GetProperty(IdProperty); }
  }

  // example with private backing field
  private static PropertyInfo<string> NameProperty =
    RegisterProperty(typeof(ReadOnlyRoot),
    new PropertyInfo<string>("Name", "Name"));
  private string _name = NameProperty.DefaultValue;
  public string Name
  {
    get { return GetProperty(NameProperty, _name); }
  }

  #endregion

  #region Authorization Rules

  protected override void AddAuthorizationRules()
  {
    // TODO: add authorization rules
    //AuthorizationRules.AllowRead("Name", "Role");
  }

  private static void AddObjectAuthorizationRules()
  {
    // TODO: add authorization rules
    //AuthorizationRules.AllowGet(typeof(ReadOnlyRoot), "Role");
  }

  #endregion

  #region Factory Methods

  public static ReadOnlyRoot GetReadOnlyRoot(int id)
  {
    return DataPortal.Fetch<ReadOnlyRoot>(
      new SingleCriteria<ReadOnlyRoot, int>(id));
  }

  private ReadOnlyRoot()
  { /* require use of factory methods */ }
#endregion

  #region Data Access

  private void DataPortal_Fetch(SingleCriteria<ReadOnlyRoot, int> criteria)
  {
    // TODO: load values
  }

  #endregion
}

Like other business objects, a read-only object will have either managed or private fields that contain its data. Typically, it will also have read-only properties or methods that allow client code to retrieve values. As long as they don't change the state of the object, these may even be calculated values.

The AddAuthorizationRules() method only needs to add roles for read access, since no properties should be implemented to allow altering of data. Similarly, the AddObjectAuthorizationRules() method only needs to define the roles allowed to get the object.

In the Factory Methods region, there's just one factory method that retrieves the object by calling DataPortal.Fetch(). The Data Access region just contains DataPortal_Fetch(). Of course, there's no need to support updating or deleting a read-only object.

As with the editable root stereotype, you can use the ObjectFactory attribute to have the data portal invoke a Fetch() method from an object factory rather than invoke the DataPortal_Fetch() method in the business class directly.

Read-Only Child Objects

You create a read-only child object using the same code as that for a read-only root object. The only difference is how you handle the data portal and data access.

[Serializable]
  public class ReadOnlyChild : ReadOnlyBase<ReadOnlyChild>
  {
    #region Business Methods

    // TODO: add your own fields, properties, and methods

    // example with managed backing field
    private static PropertyInfo<int> IdProperty =
      RegisterProperty(typeof(ReadOnlyRoot),
      new PropertyInfo<int>("Id", "Id"));
    public int Id
    {
      get { return GetProperty(IdProperty); }
    }

    // example with private backing field
    private static PropertyInfo<string> NameProperty =
      RegisterProperty(typeof(ReadOnlyRoot),
      new PropertyInfo<string>("Name", "Name"));
    private string _name = NameProperty.DefaultValue;
public string Name
    {
      get { return GetProperty(NameProperty, _name); }
    }

    #endregion

    #region Factory Methods

  internal static ReadOnlyChild GetReadOnlyChild(object childData)
  {
    return DataPortal.FetchChild<ReadOnlyChild>(childData);
  }

    private ReadOnlyChild()
    { /* require use of factory methods */ }

    #endregion

    #region Data Access

  private void Child_Fetch(object childData)
  {
    // TODO: load values from childData
  }

    #endregion
  }

As with a root object, a read-only child object should only have read-only properties. And as with an editable child, the factory method and data access method assume the parent object will be providing the pre-retrieved data needed to load the object's fields.

Read-Only Collection

Applications commonly retrieve read-only collections of objects. The CSLA .NET framework includes the ReadOnlyListBase class to help create read-only collections. It throws an exception any time there's an attempt to change which items are in the collection by adding or removing objects.

Note

The template shown here is for the most common scenario: a read-only root collection. You can adapt this to provide a read-only child collection if desired.

However, there's no way for the collection object to stop client code from interacting with the child objects themselves. Typically, the items in the collection expose only read-only properties and methods. If read-write objects are put into the collection, the client code will be able to alter their data. A read-only collection only guarantees that objects can't be added or removed from the collection.

The child objects may be derived from ReadOnlyBase, but they could just as easily be simple objects that don't inherit from any CSLA .NET base class. The only requirements for these child objects are that they are implemented with read-only properties and that they are marked as Serializable.

The code for a typical read-only collection object looks like this:

[Serializable]
public class ReadOnlyList :
  ReadOnlyListBase<ReadOnlyList, ReadOnlyChild>
{
  #region Authorization Rules

  private static void AddObjectAuthorizationRules()
  {
    // TODO: add authorization rules
    //AuthorizationRules.AllowGet(typeof(ReadOnlyList), "Role");
  }

  #endregion

  #region Factory Methods

  public static ReadOnlyList GetReadOnlyList(string filter)
  {
    return DataPortal.Fetch<ReadOnlyList>(
      new SingleCriteria<ReadOnlyList, string>(filter));
  }

  private ReadOnlyList()
  { /* require use of factory methods */ }

  #endregion

  #region Data Access

  private void DataPortal_Fetch(
    SingleCriteria<ReadOnlyList, string> criteria)
  {
    RaiseListChangedEvents = false;
    IsReadOnly = false;
    // TODO: load values
    object objectData = null;
    foreach (var child in (List<object>)objectData)
      Add(ReadOnlyChild.GetReadOnlyChild(child));
    IsReadOnly = true;
    RaiseListChangedEvents = true;
  }

  #endregion
}

The Authorization Rules region contains only the AddObjectAuthorizationRules() method, which only needs to define the roles allowed to get the object.

The Factory Methods region has a factory method to return a collection loaded with data. It calls DataPortal.Fetch() and often passes in some criteria or filter value to restrict the results of the list.

Finally, the DataPortal_Fetch() method loads the object with data from the database. To do this, the IsReadOnly flag is set to false, the data is loaded from the database, and then IsReadOnly is set to true. When IsReadOnly is set to true, any attempt to add or remove items from the collection results in an exception being thrown. Temporarily setting it to false allows the code to insert all the appropriate child objects into the collection.

Also note that RaiseListChangedEvents is set to false and then true in a similar manner. To improve performance, this suppresses the raising of ListChanged events while the data is being loaded.

Read-Only Child Collection

A read-only child collection is virtually identical to a read-only root collection. The differences are in the factory methods and data access.

[Serializable]
  public class ReadOnlyChildList :
    ReadOnlyListBase<ReadOnlyChildList, ReadOnlyChild>
  {
    #region Authorization Rules

    private static void AddObjectAuthorizationRules()
    {
      // TODO: add authorization rules
      //AuthorizationRules.AllowGet(typeof(ReadOnlyChildList), "Role");
    }

    #endregion

    #region Factory Methods

    internal static ReadOnlyChildList GetReadOnlyChildList(object childData)
    {
      return DataPortal.FetchChild<ReadOnlyChildList>(childData);
    }

    private ReadOnlyChildList()
    { /* require use of factory methods */ }

    #endregion

    #region Data Access

    private void Child_Fetch(object childData)
    {
      RaiseListChangedEvents = false;
      IsReadOnly = false;
      // TODO: load values
      foreach (var child in (List<object>)childData)
        Add(ReadOnlyChild.GetReadOnlyChild(child));
      IsReadOnly = true;
      RaiseListChangedEvents = true;
    }

    #endregion
  }

The internal factory method calls the FetchChild() data portal method, which in turn calls Child_Fetch(). Notice that the parent object is assumed to be providing some object containing all the data necessary to load the collection's child objects.

Command Objects

Command objects can be used in many ways. They may be called directly by UI code to execute arbitrary code on the application server, but even more often they are used within other business objects to execute code on the application server. A primary example is when a normal editable business object wants to implement an Exists() command. You'll see an example of this concept in the Project and Resource objects in the Project Tracker reference application.

If the UI is to use the object directly, the class will be public. On the other hand, if the UI is to use the object within the context of another business object, the class will be a private nested class within that business object. Either way, the structure of a command object is the same, as shown here:

[Serializable]
public class CommandObject : CommandBase
{
  #region Factory Methods

  public static bool Execute()
  {
    CommandObject cmd = new CommandObject();
    cmd.BeforeServer();
    cmd = DataPortal.Execute<CommandObject>(cmd);
    cmd.AfterServer();
    return cmd.Result;
  }

  private CommandObject()
  { /* require use of factory methods */ }

  #endregion

  #region Client-side Code

  // TODO: add your own fields and properties
  bool _result;

  public bool Result
  {
    get { return _result; }
  }

  private void BeforeServer()
  {
    // TODO: implement code to run on client
    // before server is called
  }

  private void AfterServer()
  {
    // TODO: implement code to run on client
    // after server is called
  }

  #endregion
#region Server-side Code

  protected override void DataPortal_Execute()
  {
    // TODO: implement code to run on server
    // and set result value(s)
    _result = true;
  }

  #endregion
}

This class structure is quite a bit different from anything you've seen so far.

The Factory Methods region is similar to many of the other templates thus far in structure, but its implementation is different. Rather than passing a criteria object to the server, the Execute() method creates and initializes an instance of the command object itself. That instance is then sent to the server through the data portal, which invokes the DataPortal_Execute() method on the server.

The Execute() method also calls BeforeServer() and AfterServer() methods, which are found in the Client-side Code region. The idea behind this is that the command object can be initialized on the client with any data required to perform the server-side processing. In fact, the object could do some processing or data gathering on the client before or after it is transferred to the server through the data portal. The client-side code may be as complex as needed to prepare to run the server-side code.

Then the data portal moves the object to the application server and calls the DataPortal_Execute() method in the Server-side Code region. The code in this method runs on the server and can do any server-side work. This might be something as simple as doing a quick database lookup, or it might be a complex server-side workflow. The code in this method can create and interact with other business objects (all on the server, of course). It can interact directly with the database or any other server-side resources, such as the server's file system or third-party software installed on the server.

As with the editable root stereotype, you can use the ObjectFactory attribute to have the data portal invoke an Update() method from an object factory rather than invoke the DataPortal_Execute() method in the command object directly.

Command objects are powerful because they provide high levels of flexibility for running both client and server code in a coordinated manner.

Name/Value List Objects

Perhaps the simplest business object to create is a name/value list that inherits from the NameValueListBase class in the CSLA .NET framework. The base class provides almost all the functionality needed, except the actual data access and factory method.

Because name/value list data is often static and changes rarely, it is often desirable to cache the data. You can do this in the factory method, as shown in this template:

[Serializable]
public class NameValueList : NameValueListBase<int, string>
{
  #region Factory Methods

  private static NameValueList _list;

  public static NameValueList GetNameValueList()
  {
    if (_list == null)
      _list = DataPortal.Fetch<NameValueList>();
return _list;
  }

  public static void InvalidateCache()
  {
    _list = null;
  }

  private NameValueList()
  { /* require use of factory methods */ }

  #endregion

  #region Data Access

  private void DataPortal_Fetch()
  {
    RaiseListChangedEvents = false;
    IsReadOnly = false;
    // TODO: load values
    //object listData = null;
    //foreach (var item in listData)
    //  Add(new NameValueListBase<int, string>.
    //    NameValuePair(item.Key, item.Value));
    IsReadOnly = true;
    RaiseListChangedEvents = true;
  }

  #endregion
}

The Factory Methods region declares a static field to hold the list once it is retrieved. Notice how the factory method returns the cached list if it is present; it only calls the data portal to retrieve the data if the list is null. You can also call an InvalidateCache() method to force a reload of the data if needed.

This caching behavior is optional—if it doesn't fit your need, then use a factory method, like this:

public static NameValueList GetNameValueList()
{
  return DataPortal.Fetch<NameValueList>();
}

The Data Access region contains only a DataPortal_Fetch() method, which calls the Data Access layer to retrieve the name/value data. The NameValueListBase class defines a strongly typed NameValuePair class, which is used to store each element of data. For each row of data from the database, a NameValuePair object is created and added to the collection.

Notice the use of the IsReadOnly property to temporarily unlock the collection and then relock it so it becomes read-only once the data has been loaded. The RoleList class in the Project Tracker reference application illustrates a complete implementation of a name/value list.

Dynamic Editable Collection

The dynamic editable collection stereotype is designed to support a narrow and focused scenario in which the UI is a Windows Forms grid control that is data bound to the collection, and in which the user wants his changes to each row saved as soon as he moves off each row.

This behavior is conceptually similar to some old Visual Basic behavior when using a DAO dynaset. In that case, changes made by the user to a row of data were automatically committed when the user navigated off that row in the UI.

The CSLA .NET base class used in this stereotype is the EditableRootListBase (ERLB). A collection that inherits from ERLB contains editable root objects (though with a twist), so each object can be saved individually rather than in a batch. While a collection that inherits from BusinessListBase saves all its child objects at once, a collection inheriting from ERLB saves each "child" one at a time.

At a high level, the collection is responsible for containing a list of editable root objects. It is also responsible for retrieving all those objects at once, but then saving them individually as the user inserts, updates, or deletes items in the list.

The ERLB base class handles most of the hard work itself, so the template code is focused primarily on retrieving the objects and adding them to the collection. When looking at this code, remember that the child objects are a modified editable root stereotype called dynamic editable root objects.

[Serializable]
public class DynamicRootList :
  EditableRootListBase<DynamicRoot>
{
  #region Business Methods

  protected override object AddNewCore()
  {
    DynamicRoot item = DynamicRoot.NewDynamicRoot();
    Add(item);
    return item;
  }

  #endregion

  #region  Authorization Rules

  private static void AddObjectAuthorizationRules()
  {
    // TODO: add authorization rules
    //AuthorizationRules.AllowGet(typeof(DynamicRootList), "Role");
    //AuthorizationRules.AllowEdit(typeof(DynamicRootList), "Role");
  }

  #endregion

  #region  Factory Methods

  public static DynamicRootList NewDynamicRootList()
  {
    return DataPortal.Create<DynamicRootList>();
  }

  public static DynamicRootList GetDynamicRootList()
  {
    return DataPortal.Fetch<DynamicRootList>();
  }
private DynamicRootList()
  {
    // require use of factory methods
    AllowNew = true;
  }

  #endregion

  #region  Data Access

  private void DataPortal_Fetch()
  {
    // TODO: load values
    RaiseListChangedEvents = false;
    object listData = null;
    foreach (var item in (List<object>)listData)
      Add(DynamicRoot.GetDynamicRoot(item));
    RaiseListChangedEvents = true;
  }

  #endregion
}

As with an editable root collection, you can set AllowNew to true and override AddNewCore() if you want data binding to automatically add new items to the collection. This template includes that functionality, because the typical use of a dynamic collection is to allow the user to add, edit, and remove objects.

The Authorization Rules region is the same as you've seen in the other root templates. The Factory Methods region allows creation of an empty collection, or retrieval of existing data through two different factories. The Data Access region includes the DataPortal_Fetch() method, which is responsible for calling the Data Access layer to get all the data for the objects that will be in the collection. This is the same kind of code you saw for the editable root collection stereotype.

What is interesting here is that the "child" objects being loaded are actually editable root objects. In the next section, I'll discuss the dynamic editable root stereotype. You'll be able to see how the GetDynamicRoot() factory method is implemented.

Dynamic Editable Root Objects

The dynamic editable root stereotype is virtually identical to the editable root stereotype, with one slight twist. Remember that this stereotype is designed to create the "child" objects contained within a dynamic editable collection. While these editable objects are saved individually, they are loaded all at once by the parent collection.

Note

In this section, I assume you are familiar with the editable root template, and I only discuss the few changes required to meet the dynamic editable root stereotype.

This means that the objects must implement an internal factory for use by the parent collection. This factory usually replaces the normal public factory that calls DataPortal.Fetch(). The factory looks like this:

internal static DynamicRoot GetDynamicRoot(object rootData)
  {
    return new DynamicRoot(rootData);
  }

Unlike most factories, this one doesn't call the data portal. This code is being invoked from within the DataPortal_Fetch() of the parent collection object, so there's no need to call the data portal again. And since this is going to be a root object, you can't call the FetchChild() method like you would for a normal child object.

Instead, a constructor is called, and the object's data is passed as a parameter. Notice that the object's data is assumed to have been loaded by the parent collection and passed into this factory method as a parameter. That object may be an entity object from LINQ to SQL or the ADO.NET Entity Framework, or it may be an ADO.NET DataReader or other data object. Normally the parameter value would be strongly typed to match your particular data access mechanism.

This new constructor is in addition to the non-public default constructor that exists in all the templates. It looks like this:

private DynamicRoot(object rootData)
  {
    Fetch(rootData);
  }

All it does is call a private method named Fetch(), which does the real work of loading the object with data.

private void Fetch(object rootData)
  {
    // TODO: load values from rootData
    MarkOld();
  }

This Fetch() method is much the same as a Child_Fetch() method for a child object. It gets the data necessary to load the object's fields as a parameter, and it simply sets the field values.

However, because the data portal didn't call this Fetch() method, the object isn't automatically marked as a child. This is good, because it is a root object, not a child.

The call to MarkOld() is necessary because the object isn't automatically marked as "old" either. In Chapter 7, I'll discuss how business objects manage state such as being "new" or "old." For now, it is enough to know that when an object is retrieved from the database, it is marked as old by the data portal. Since the data portal didn't create this object directly, the business object must do this manually.

Because the object is a root object, at least in terms of being saved, you do need to implement the DataPortal_Insert(), DataPortal_Update(), and DataPortal_DeleteSelf() methods. This object will be saved individually as a root object.

You do not need to implement DataPortal_Delete(), because the ERLB base class does not use immediate deletion to delete its objects, so that method would never be invoked.

Criteria Objects

The root object templates shown in this chapter have all used the SingleCriteria class provided by CSLA .NET. Most objects can be identified by a single criteria value, and that type makes it easy to send any single criteria value through the data portal.

Some objects are more complex, however, and may require more complex criteria. For example, some tables use compound keys, so multiple criteria values are required to match the parts of the database key. As another example, many collections have complex and optional criteria. The user might specify a Name value sometimes, a Region value at other times, and both values at yet other times.

If your object has multiple criteria values, you'll need to create a custom criteria class. Your custom criteria class must simply contain the data that's required to identify the specific object to be retrieved or the default data to be loaded. Since it's passed by value to the data portal, this class must be marked as Serializable.

Criteria classes can be nested classes within the business class, or they can inherit from Csla.CriteriaBase. In most cases, it is simplest to nest the class within the business class. The Csla.CriteriaBase approach is intended primarily for use with code-generation tools.

Tip

Technically, the criteria class can have any name, as long as it's Serializable and is either nested in the business class, implements ICriteria, or inherits from CriteriaBase. Some objects may have more than one criteria class, each one defining a different set of criteria that can be used to retrieve the object.

Since a criteria class is no more than a way to ferry data to the data portal, it doesn't need to be fancy. Typically, it's implemented with a constructor to make it easier to create and populate the object all at once. For example, here's a nested criteria class that includes an EmployeeID field:

[Serializable]
public class MyBusinessClass : Csla.baseclass<MyBusinessClass>
{
  // ...
  #region Data Access

  [Serializable]
  private class Criteria
  {
    private string _employeeId;
    public string EmployeId
    {
      get { return _employeeId; }
    }

    public Criteria(string employeeId)
    { _employeeId = employeeId; }
  }
  // ...

You can create an equivalent criteria class by subclassing CriteriaBase.

[Serializable]
Internal class MyBusinessClassCriteria : Csla.CriteriaBase
{
  private string _employeeId;
  public string EmployeId
  {
    get { return _employeeId; }
  }

  public MyBusinessClassCriteria(string employeeId)
    : base(typeof(MyBusinessClass))
  { _employeeId = employeeId; }
}

All criteria classes are constructed by using one of these two schemes or by creating a class that implements the ICriteria interface directly.

Nested criteria classes are scoped as private, because they are only needed within the context of the business class. The ICriteria interface and CriteriaBase class are typically used by code-generation tools, in which case the class is typically scoped more broadly so that it is available either project-wide or even to the UI.

Note

Code generation is outside the scope of this book. For good information on code generation, including the rationale behind CriteriaBase, please refer to Code Generation in Microsoft .NET by Kathleen Dollard (Apress, 2004), and the index of CSLA .NET-compliant code-generation tools at www.lhotka.net/cslanet/codegen.aspx.

The criteria classes shown thus far include a constructor that accepts the criteria data value. This is done to simplify the code that will go into the business object's factory methods. Rather than forcing the business developer to create a criteria object and then load its values, this constructor allows the criteria object to be created and initialized in a single statement. In many cases, this means that a factory method will contain just one line of code—for instance:

public static Project GetProject(Guid id, Date start)
{
  return DataPortal.Fetch<Project>(new Criteria(id, start));
}

Many criteria classes contain a set of simple values (as in the examples here), but they can also be more complex, providing for more control over the selection of the object to be retrieved. If you have a root collection in which you're directly retrieving a collection of child objects, the criteria class may not define a single object, but rather may act as a search filter that returns the collection populated with all matching child objects.

Another interesting variation is to use BusinessBase as the base class for a public criteria class. In this case, the class must also implement the ICriteria interface so it can act as a proper criteria object. The value of this approach is that you can easily use data binding to create a UI so the user can enter values into the criteria object. The object can even have validation and authorization rules. Here's an example of such a class:

[Serializable]
public class MyCriteria : BusinessBase<MyCriteria>, ICriteria
{
  #region Business Methods

  private static PropertyInfo<int> IdProperty =
    RegisterProperty<int>(typeof(Project), new PropertyInfo<int>("Id"));
  public int id
  {
    get { return GetProperty(NameProperty); }
    set { SetProperty(NameProperty, value); }
  }

  #endregion

  #region ICriteria Members
public Type ObjectType
  {
    get { return typeof(MyBusinessClass); }
  }

  #endregion
}

This use of BusinessBase doesn't follow all the same rules as an editable root or child. For example, there's no need for the Factory Methods or Data Access regions and related code, because this object is never persisted like a normal editable object. It is just a fancy container for criteria values that supports data binding, validation, and authorization.

Conclusion

This chapter provides detailed code templates for each of the object stereotypes directly supported by CSLA .NET. These templates include the outline for the code necessary to implement properties, methods, validation rules, business rules, authorization, and data access.

These templates illustrate the implementation of the concepts discussed in Chapter 3, such as the life cycle of business objects and the process of creating, retrieving, updating, and deleting objects. Additionally, these templates are the basis on which the business objects for the Project Tracker reference application are built, so you can look at the templates and compare them to the fully operational objects in Project Tracker to see the evolution from high-level coding structure to actual implementation.

In Chapters 616, you'll learn how to implement the CSLA .NET framework that supports these stereotypes. In Chapters 17-21, you'll see how to use these stereotypes and the framework to create the Project Tracker application described in Chapter 3.

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

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