The CSLA Framework; what is in it for me?

BarryMossman<is_at>primos.com.au


Version!! This article relates to CSLA version 1.51 which runs with .NET version 1.1. There is a newer version of this article covering CSLA version 2 available here.

Aim of this article

I set myself the task of examining Rockford Lhotka’s CSLA (Component based, scalable, logical architecture) framework

The things I already knew about CSLA were:

I bought the book, and got started. I found that it is not a small task coming to grips with what CSLA is, what it delivers, and whether it was going to be worth the effort anyway.

The deliverables are of course laid out in the book, and I am not meaning to suggest that Rocky writes unclearly. It is just that the CSLA framework is a large body of work. Rocky has built, and extended, the techniques over a long period of time. He started in 1997 with his first book on the subject using COM through COM+. He then completely re-developed for .NET starting in 2002. The .Net version of the framework is now at it’s 6th release level (1.51). It is a large book, the C# version weighs in at nearly 3 pounds and 840 pages in depth.

If you are new to .Net the challenge is compounded. It is quite a steep learning curve, even if you are familiar with OOP concepts, .NET and ADO.NET basics. It employs reasonably advanced use of .NET facilities such as:

The CSLA framework is an open source style affair, rather than a commercial product, and as such comes without extensive install, on-line help or quick start facilities.

For all of the above reasons I found the exercise a challenge. A very worthwhile challenge, enjoyable even, but definitely more of an expedition than a quick and easy yes|no answer.

The aim of this document is to quickly focus upon the deliverables as I see them. Mostly ignore any “how to” material. Instead try and answer the “What’s in it for me?”, or the “Why bother?” questions. There are links at the bottom of the article pointing to existing how to material, Rocky, and the books themselves.



List of CSLA deliverables

Firstly let me open by saying that this is not a complete list of CSLA's deliverables. It may not even be a list of all the deliverables that could be important to you. It is just a list of those deliverables that are apparent to me, and that I have spent some time examining.

Also you will see that I have used a Windows Forms desktop application to demonstrate CSLA's utility. The BOs could equally be deployed by a Web Forms application, or used via Web Services. I am using C#. The framework and the underlying book, also come in a VB.Net flavour. My demonstration application is based upon version 1.51 of the CSLA framework.

  1. Deliverable: Scalability

An application based upon the CSLA framework has a number of deployment options designed to meet the various scalability challenges. A lot of work has been put into balancing the conflict between scalability, and local UI performance objectives.

In a situation where there is no scalability requirement, our application can be deployed so that the UI, business logic, and data access logic are all serviced in a single Windows process resource, inside the same PC. Our application can still have n logical tiers as per sound programming practice, but while in this mode, the framework incurs next to no runtime cost for it's inter-tier communications.

As scalability becomes an issue our application's logical tiers can be deployed across n hardware tiers. All that is required is a change to a configuration file, and then move the affected tier's dll to the new platform. The framework has pre solved this challenge for us.

By nature, a CSLA BO is a mobile object. CSLA makes advanced use of .NET to transmit the BO between the logical tiers, in the lowest overhead manner that is available, given our choice regarding how the logical tiers have been arranged across any hardware tiers.

A CSLA BO has a dual personality. It will contain the high level functions that will be employed when it is being used inside the UI logical tier. It will also contain any data access code that is required when the object is being used within the framework's data portal tier to access or update a database.

The BO has no need to know how it is to be deployed, as the CSLA framework has abstracted away all inter-tier communication functions. To demonstrate this I have created a simple BO called BOTierLocation whose only behaviour is that it has the capability to report back where it is running from.

Being a WinForms application there is an exe file which is the UI, and then there is also a dll which defines the BO. The CSLA framework itself also contains various dlls to perform it's tasks. If I deploy my demonstration application on a single machine, and then run it, I see this display. This shows me that both the WinForms UI and the framework's data portal are running within a single Windows process.

Next I will simulate deploying our application across another tier. We will have the UI running on the user's PC, but have the BO operating on a centralised application server to provide the BO's behaviour, and to access any database server as required. For this demonstration I will use the same application, without recompilation. The only change I have made is to add the “PortalServer” key to the run-time configuration file as follows. This change will cause that part of the CSLA framework that our UI is interacting with, to look for the data portal on the PC named “clone1”.



<?xml version="1.0" encoding="utf-8"?>
<configuration>
     <appSettings>             
          <add key="Authentication" value="Windows" />
          <add key="PortalServer" value="http://localhost/DataPortalcs/DataPortal.rem" />
     </appSettings>
</configuration>



As I don't have a server here, I have simulated the user's PC and application server using Vmware Workstation. I have set up a VMware team consisting of two PC's:

* Clone1 has .Net and IIS installed. The BO dll and the framework are installed in an IIS virtual directory named DataPortalcs

* Clone2 has .Net, the CSLA framework, my BO dll, and the WinForms exe installed

I have run the WinForms executable upon Clone2. As you can see from the diagram, my application is now successfully running across both tiers. We can therefore easily modify our application to use a centralised application server for our business objects, and thereby greatly reduce the number of concurrent connections to our database via connection pooling.

CSLA has shielded the programmers, of both the UI and the BO, from the .NET Remoting plumbing complexity that is required to achieve this situation.

Rocky has indicated in his web log that the .NET v2 of CSLA will probably add web services, Enterprise Services and Indigo transport options to current single option of .NET Remoting.

Now let us look at some of the source code that we have been using here. Firstly we have the UI's Click event handler for the “Access portal” button.. This action requests a new instance of our BOTIierLocation object, and then used it's ServerName and ProcessID properties to populate the lower of the two Textboxes. As you can see the UI is completely unaware of the location of data portal, which is acting in an Object Factory mode to create and deliver the requested BO. It is unaware that it is dealing with a Proxy object which may be on another machine.


private void button1_Click(object sender, System.EventArgs e)
     {
          /* create a new BO; in this case a simple read-only object whose
           only behaviour is that it is able to describe the context
           within which the data portal (data access layer) is running */
          BOTierLocation ro = BOTierLocation.GetNewBOTierLocation();
          // display the context 

          txbDataPortal.Text = 
               String.Format("Machine name = {0}\r\nProcess ID = {1}",ro.ServerName, ro.ProcessID);
     }

Finally we will look at the important sections of the code defining our BOTierLocation class. I will ignore the following items as they are not relevant to what I am demonstrating here:

What we will look at is the GetNewBOTierLocation method used by the UI, and the code that will be run by the data portal tier, which as we have seen may be in-process with the UI, or may be hosted upon a remote machine. Our BO can focus on just the business rules, and doesn't become tangled up with the .Net plumbing required to achieve this scalability capability. We get this capability because we have descended from one of the CSLA frameworks classes (ReadOnlyBase on this occasion).

namespace ExpertBusinessObjects.Library
{
     [Serializable()]
     public class BOTierLocation : ReadOnlyBase 
     {
          // Declare state variables
          private Guid _id = Guid.NewGuid();
          private int _processID;
          private string _machineName;

          #region Business Properties and Methods
          // snipped
          #endregion

          #region System.Object Overrides
          // snipped
          #endregion

          #region Static methods
          public static BOTierLocation GetNewBOTierLocation() 
          {
               return (BOTierLocation)DataPortal.Fetch(new Criteria());
          }
          #endregion

          #region Constructors
          // snipped
          #endregion

          #region Criteria
          // snipped
          #endregion

          #region Data Access
          // called by DataPortal to load state, usually from the database
          protected override void DataPortal_Fetch(object criteria)
          {
               _processID = 
                    System.Diagnostics.Process.GetCurrentProcess().Id;
               _machineName = Environment.MachineName;
          }
          #endregion        
     }
}



  1. Deliverable: Minimise, and simplify, the code in the UI

Usually the UI is that part of our application which is most subject to change. For this reason it is highly desirable to simplify and minimise UI program code as this will ease, deskill, and reduce the risk involved in future modifications. We can help achieve this objective by creating high function Business Objects for our UI to use. This will reduce the complexity, clutter, and general plumbing code that would otherwise be required inside the UI tier.

The use of these BO's will also promote the possibility of reuse, as these objects will be available for other applications, or for the migration of our application across to other platforms (eg. a desktop application migrating onto the Internet, or being offered via web services).

The fact that our UI tier is just dealing with high level BO's will also insulate it from change, should we decide to migrate up to some future Microsoft technology such as the shift from .Net Remoting to Indigo, or a change from ADO .Net to whatever may come next.

I will demonstrate the simplicity that we can deliver to our UI tier via a small application which I have written that uses SQL Server's Northwind database. Firstly let us start very simply by having a listbox that contains the list of countries for which there are customers in the database.

I have created a CustomerCountries class from a CSLA base class that is very easy to use, and appropriate when our BO is to be just a read-only set of value-name pairs. I did this as I wanted to start things off with the simplest case, however this base class doesn't support databinding in the current version of CSLA. Most CSLA base classes do support databinding, as we will see shortly, which would have let me load the listbox by just assigning the DataSource property. Rocky Lhotka's weblog indicates that the base class I am using here will be extended to also support databinding in the next version of CSLA.

So in the meantime I have loaded the listbox with a simple foreach loop, but this still allows us to create the UI with next to no code. The following code snippet shows the declaration of the BO in our Web Form project, and then the use of the BO within the form's constructor.

private ExpertBusinessObjects.Library.CustomerCountries countries = 
        ExpertBusinessObjects.Library.CustomerCountries.GetCustomerCountriesList();

        public frmShowCustomersByCountry()
                {
                        InitializeComponent();

                        foreach (string name in countries) 
                        {
                                listBox1.Items.Add(name);
                        }
                }

Since our application doesn't do much so far, we have had little opportunity to receive benefit from CSLA, but already we can see that the UI is:

Although we are focusing on the UI in this section, let us look quickly at the code that implements the CustomerCountries BO, so that you get a favour of what our UI is interacting with. Thanks to the CSLA base class (NameValueList) I have chosen here, the code to build the CustomerCountries BO is also very simple:

[Serializable]
     public class CustomerCountries: NameValueList
     {               
           #region Static methods
           public static CustomerCountries GetCustomerCountriesList() 
           {
                 return (CustomerCountries)DataPortal.Fetch(new Criteria());
           }
           #endregion

           #region Constructors
           private CustomerCountries() {}          
           #endregion

           #region Criteria
           [Serializable]
           private class Criteria {}               
           #endregion
                
           #region Data Access
           protected override void DataPortal_Fetch(object Criteria) 
           {
                 SimpleFetch("Northwind","Countries","Country","Country");                           
           }
           #endregion
        }

We would have already expected that simple functionality would require negligible UI program code. The good news continues as we extend our application to provide more complex functionality.

Looking to the left we see that our application has progressed forwards. The user has selected a country, and then selected a customer within that country. In this new form we can see the customer's ID, Name, contact, and also all of their Orders.

The application allows us to modify either of the customer names, or any of the order freight values. We can save our changes to the database. There is also a cancel facility which will back out of any changes made to our BO, but not yet saved to the database. No access will be required to either the BO tier, or the database tier, to action this undo capability.

The diagram on the right shows that the application also enforces several business rules. Neither of the customer names can be set to blank, and a freight value may not be set to a value less than $0.00. Errors are automatically detected, and highlighted, when the user shifts focus to the next control on the form. The UI has disabled the Save button as the business object is in an invalid state. The Cancel button will cause the BO to restore itself to the state of a snapshot taken before the user started making changes, and will refresh the UI and error indicators. The Esc and Enter keys work as expected within the Orders grid, and error indications toggle on and off automatically as the user makes, or corrects, errors detected by our business rules.

As would be expected our UI tier now has more code, however it is just as important to focus upon what code is not there. What we do see is code that is appropriate to the job of defining and managing the form's UI. We should notice that our BO's have worked together nicely with WinForms databinding and error reporting interfaces to allow us to receive a high level of default services during field update, Esc|Cancel operations, and when valid data is supplied to correct errors already detected by our business rules.

We should remember the lack of plumbing code and dependencies as I mentioned with my first example above, and also notice:

Here is the UI code that implements the above behaviours:

 public class frmUpdateCustomer : System.Windows.Forms.Form
 {

      private CheckBox hack;
      // snip all other form control declarations
                
      private ExpertBusinessObjects.Library.Customer _customer;

      public frmUpdateCustomer(string customerID)
      {
             InitializeComponent();

              /* Make sure that Windows databinding receives our
               * IsDirtyChanged event whenever one of our BO properties
               * change, so that there will be a screen refresh.
               * Hack required until .Net V2 (Whidbey) removes the need
               * for individual property change notification events, 
               * and gives us the INotifyPropertyChanged interface with
               * it's global PropertyChanged event. In the meamtime we
               * rely on that fact that any change to our BO  will also
               * cause a change to IsDirty. We will bind this invisible
               * dummy control to IsDirty. */
              hack = new CheckBox();
              hack.Enabled = false;                   
              hack.Size = new Size(0, 0);             // make invisible
              this.Controls.Add(hack);

              /* Create a BO for our customer, and load it's data from the
               * database. The BO will also contain a collection of the
               * customer's orders. */
              _customer =
           ExpertBusinessObjects.Library.Customer.GetCustomer(customerID);

              /* Cause our BO to take a snapshot that would be used if the
               * user requests a restoration of state via the Cancel
               * button. */
              _customer.BeginEdit();

              // private method to bind form controls to our BO.
              DataBind(_customer);

      }

      #region Windows Form Designer generated code
      // snip         
      #endregion

      /// <summary>
      /// Bind our object properties to UI controls.
      /// </summary>
      /// <param name="customer"></param>
      private void DataBind(Customer customer) 
      {
              BindField(hack, "Checked", _customer, "IsDirty");
              BindField(txtID, "text",_customer,"ID");
              BindField(txtName, "text",_customer,"CompanyName");
              BindField(txtContact, "text",_customer,"ContactName");
              BindField(lbxShowErrors, "DataSource", _customer,
                        "BrokenRulesCollection");
              lbxShowErrors.DisplayMember = "Description";
              BindField(btnSave, "Enabled", customer, "IsValid");

              if (grdOrders.DataSource == null) 
                      BindField(grdOrders, "DataSource", customer,
                                "Orders");
              else 
              {       
                      /* force the grid to repaint itself if the user
                       * pressed the Cancel button */
                      BindField(grdOrders, "DataSource", customer,
                                "Orders");
                      grdOrders.Invalidate();
              }
      }

      private void btnSave_Click(object sender, System.EventArgs e)
      {
              try 
              {
                      Cursor.Current = Cursors.WaitCursor;

                      /* Inform the framework that Editing of the BO is
                       * complete, the snapshot can be discarded  */            
                      _customer.ApplyEdit();

                      /* Cause the data portal to commit the changes to
                       * the database. */
                      _customer = (Customer)_customer.Save();
                      Cursor.Current = Cursors.Default;
              }
              catch (Exception ex) 
              { 
                      Cursor.Current = Cursors.Default;
                      MessageBox.Show(ex.ToString());
              }

              /* The data portal will return a new object following the
               * save. This allows us the possibility of performing 
               * some data manipulation in the application server or
               8 database server tiers. Examples would be setting of
               * default values, or maybe a new primary key during an
               * update that involved insertion of a new row somewhere.
               * We need to bind to the new object. */ 
              DataBind(_customer);
      }

      private void btnCancel_Click(object sender, System.EventArgs e)
      {
              _customer.CancelEdit();
              DataBind(_customer);
              _customer.BeginEdit();
      }

      private void frmUpdateCustomer_Closed(object sender, System.EventArgs e)
      {
              _customer.CancelEdit();         
      }

      /// <summary>
      /// Binds a control to a data source property, making sure
      /// that a previous binding doesn't already exist.
      /// This code, copied directly from Rocky's book, would normally be
      /// in a Utilities library.
      /// </summary>
      private static void BindField(Control control, string propertyName, 
                 object dataSource, string dataMember)
      {
             Binding bd;
             for(int index = control.DataBindings.Count - 1; index >= 0; index--)
             {
                     bd = control.DataBindings[index];
                     if(bd.PropertyName == propertyName)
                            control.DataBindings.Remove(bd);
             }
             control.DataBindings.Add(propertyName,dataSource,dataMember);
      }               
 }

The use of high level BO's has allowed our UI to remain simple. We can see from the following class diagram that our Customer class contains a collection of CustomerOrders. Our UI therefore just had to interact with a Customer class, which knows when and how to interact with it's own Orders.




We have inherited from a CSLA base class which provides other properties, methods, events and interfaces which our UI can use to keep it's own job simple. Examples are IsSavable, IsNew, BrokenRules collection, Clone(), OnIsDirtyChanged etc etc.

We could easily extend our BO's by adding our own methods, events or properties. For example I could add an HasUnshippedOrders property, or an AmalgamateUnshippedOrders method.

My aim has been to demonstrate the capability to minimise and simplify code in the UI, so there is no need to examine the code required to create the BO's at this point. Much of the BO code will be revealed as we look at the other deliverables that the CSLA framework makes available.



  1. Deliverable: Availability of complex data binding

CSLA based BO's, other than the simplistic name-value pairs, support data binding. This is good, as data binding reduces UI code, and can perform faster than manual loading of multi-record controls according to Rocky.

Some of the benefits of data binding are demonstrated in my example above. For instance, the following piece of code binds our “Save” button's Enabled property to our BO's IsValid property. Data binding will maintain a connection between the button, and our BO's validity, to automatically disable and enable the button as the user keys in data that violates, or then re-satisfies, our business rules. The button's status will always reflect the BO's validity, regardless of where, or how, the user broke or re-satisfied a business rule. We can add further rules to our BO, and the button status will also respect those new rules without any change to our UI.

       BindField(btnSave, "Enabled", customer, "IsValid");

That was an example of “simple binding”. Our CSLA objects also support “complex binding” to our collection BO's. The following piece of code binds our Orders grid to our Customer BO's Orders property. Since this is a WinForms application we get two-way data binding. The “Orders ” property within our BO is an updatable collection of child “Order” BO's. The Order class has read|write properties for the “Freight” property, so the grid will automatically acquire an updatable “Freight” column. Data binding will ensure that when a freight cell loses focus, any modification will be be reflected back into our BO. This will then cause any business rules at the Order class level to be applied, and any change to the cell's validity will be automatically reflected back to the grid's row's error indicator. If this cell change caused the whole Customer BO to move between a Valid|Invalid status, then the “save” button's Enabled status will be automatically toggled..

       BindField(grdOrders, "DataSource", customer, "Orders");

If our BO was invalidated programatically by the UI as in the following, the resulting change to the underlying BO's state would be automatically reflected in our grid, error indicators, and “save” button status.

                        _customer.Orders[1].Freight = -3;

Any changes are just happening within our BO. The BO knows how to persist itself to the database should all business rules be satisfied, and the user elect to press the Save button.

The binding services that we are getting from our CSLA based BO's exceed that which we would get from binding to a simple array. This is because unlike a simple array, the CSLA base classes implement the IBindingList interface's numerous properties, methods and events required to achieve complex data binding. We are getting the data binding capabilities of a heavyweight data source such as DataSet, however our BO's are more lightweight to transport than a DataSet, and have enhanced features such as business rules as we shall soon see.

  1. Deliverable: Object-Relational Mapping

The CSLA framework allows us to design our BO classes without being constrained by the design of our database. Object-Relational mapping is about accommodating for possible differences between the normalised relational design that we would want for our data base, and the high-function user-perspective design we would like for our BO's.

My Customer BO is a simplistic example of this. The Northwind database contains a Customer table and a separate Orders table. I have made life simple for my UI by creating a Customer BO that contains a collection of all of the customer's orders. My Customer BO is updatable. Behind the scenes the BO is mapping which data changes need to go to which database table.

This concept can be extended so that a seemingly simple BO can actually be comprised of data from separate databases, maybe from separate platforms, maybe also incorporating data from non-database sources. There is an Enterprise Services flavour of the framework's data portal, which means that we can have a two-phase commit for use when we need to ensure that all databases, affected by a single update, remain instep.

CSLA is not claiming to be full ORM product, but it does allow us to achieve the above benefits. The main reason that I am discussing this deliverable so early in the list, is that I need to go into a little more detail about “what a BO is”. This article is not really meant to be a “how to” article, but we need to understand a little about how a BO works so that we can understand some of the points that I am making in the following sections of this document.

Let us look at my Customer class. Firstly you may recall that our UI was able to create, and load, our BO as simply as follows:

/* Create a BO for our customer, and load it's data from the database.
 * The BO will also contain a collection of the customer's orders. */
_customer = ExpertBusinessObjects.Library.Customer.GetCustomer(customerID);

Now to the definition of our Customer BO. Firstly we have some variables to hold the state for the class, and then some property getters and setters which I have snipped to conserve space. We can see that amongst the state variables we have one that will contain the collection of the customer's orders. Everything is empty at this stage.

     [Serializable()]        
     public class Customer : BusinessBase
     {
           #region State variables
           string _id = string.Empty;
           string _companyName = string.Empty;
           string _contactName = string.Empty;

           CustomerOrders _orders = CustomerOrders.NewCustomerOrders();
           #endregion

           #region Business Properties and Methods         
                // [SNIP: the property setters and getters]
           #endregion

Then there is the GetCustomer factory method that the UI used to obtain the BO:

       public static Customer GetCustomer(string id)
       {
                return (Customer)DataPortal.Fetch(new Criteria(id));
        }

In the background I have a SQL stored procedure to obtain the required data from the Customer and Orders tables:

CREATE PROCEDURE getCustomerOrders
        (
                @ID nchar(5)
        )
AS
        SELECT CustomerID, CompanyName, ContactName
              FROM customers 
             WHERE CustomerID = @ID

        select OrderID, ShippedDate, ShipCountry, Freight, ShipVia
              from Orders 
             where CustomerID = @ID

        RETURN
GO

I have then provided an override implementation for the framework's DataPortal_Fetch method which our GetCustomer method called.. This code is part of my BO, and is run by the data portal (data Access tier) to read in data. As you can see I have used ADO .Net to access the database, and then populated my state variables. Alternatively I could have obtained my data from other data stores such as Oracle, MySQL, Firebird, or just some xml files.

We have tuned performance by obtaining all the data we need in one hit against the database, but logic pertaining to the CustomerOrder object, and the CustomerOrders collection object, is fully encapsulated away into in their own classes as we would hope.

  protected override void DataPortal_Fetch(object criteria)
  {
       Criteria crit = (Criteria)criteria;
       using(SqlConnection cn = new SqlConnection(DB("Northwind")))
       {
            cn.Open();
            SqlTransaction tr = 
                    cn.BeginTransaction(IsolationLevel.ReadCommitted);
            try
            {
                 using(SqlCommand cm = cn.CreateCommand())
                 {
                      cm.Transaction = tr;
                      cm.CommandType = CommandType.StoredProcedure;
                      cm.CommandText = "getCustomerOrders";
                      cm.Parameters.Add("@ID", crit.ID);

                      using(SafeDataReader dr = 
                         new SafeDataReader(cm.ExecuteReader()))
                      {
                           dr.Read();
                           _id = dr.GetString(0);
                           _companyName = dr.GetString(1);
                           _contactName = dr.GetString(2);

                           // load child objects
                           dr.NextResult();
                           _orders = CustomerOrders.GetCustomerOrders(dr);
                           _orders.ListChanged += new System.ComponentModel.ListChangedEventHandler(this.OnListChangedHandler);
                      }
                 }
                 MarkOld();
                 tr.Commit();
            }
            catch
            {
                 tr.Rollback();
                 throw;
            }
       }
  }

There is more of the above to handle changes, insertions and deletions against our database(s), also the definition of the Order and Order collection BO's. I won't show any of this as I think that I have made my point, which was; we can design our BO without being constrained by how, or where, the underlying data is stored.

  1. Deliverable: Business rules

The CSLA framework abstracts the handling of business rules, so that our business objects can easily apply rules to validate the object’s state. The rules themselves are part of our BO's. This means that the rules can be reused, and be consistently applied at all points within our application, and across all applications or platforms where the BO is deployed.

There is very little code required in the UI. The BO is automatically in an invalid state if any of it's business rules are broken. There is facility for the UI to detect the BO's validity, and to display the collection of any broken rules. These items are automatically kept updated by any change to the BO, or by any use of the BO's CancelEdit facility.

My demo application implemented rules for both the Customer & Order BO's. Let us start by looking at the UI. We have already looked at the UI code. If you didn't notice any logic to implement the business rules, that is because there is none there. The UI just requested the creation of our BO:

    /* Create a BO for our customer, and load it's data from the database.
     * The BO will also contain a collection of the customer's orders. */
    _customer = ExpertBusinessObjects.Library.Customer.GetCustomer(customerID);

Then relied upon the fact that the save button would be disabled if the BO was set to an invalid state:

     BindField(btnSave, "Enabled", customer, "IsValid")


Any errors that arose from invalid data at the parent Customer level were auto-displayed in a listbox (left) by the following code snippet:


     BindField(lbxShowErrors,  "DataSource", _customer,
               "BrokenRulesCollection");
     lbxShowErrors.DisplayMember = "Description";

Any invalid rows within the Customer's Order collection grid were auto-indicated as an error with the red exclamation mark, as the CSLA framework has implemented the IDataErrorInfo interface.

We could have enhanced the original UI to also show the error message from the current grid row as shown alongside. The error text is accessible as follows:

     tbxOrderError.Text = _customer.Orders[grdOrders.CurrentRowIndex].BrokenRulesString;

So we can see that the UI has been allowed to focus upon just presentation matters, and the rules have been delegated to the BO's themselves. Now let us look at that part of the Customer BO that implements it's rules and rule handling. We attach the business rules to the BO by providing an override to the framework's implementation of AddBusinessRules method. This method will be automatically invoked by the framework when required.

In this method we have created a rule handler object, then attached that rule to the BO properties that we want it to protect. I can group rules, using the middle parameter of the AddRule method, so that later just a subset of a BO's rules can be tested. I have chosen to just group my rules by the property to which they pertain.

     /// <summary>
     /// This overrided method attaches all the business rules that govern
     /// our BO's state data.
     /// </summary>
     protected override void AddBusinessRules() 
     {
         // set up connection so that the rules manager can access our
         // BO's data
         BrokenRules.SetTargetObject(this);
         BrokenRules.RuleHandler rule = new BrokenRules.RuleHandler(StringIsRequired);   
         BrokenRules.AddRule(rule, "company", "CompanyName");            
         BrokenRules.AddRule(rule, "contact", "ContactName");
     }

Now let us look at the definition of the rule handler that I have used above.

Since my method is generic it obtains the name of the property that it is protecting from the incoming arguments, then obtains the property value via reflection, and then returns a result showing whether the string had the required non-empty value.

The Attribute at the top of the rule handler provides the format to be used by the framework's Rules Manager when supplying a message string to be used when the rule is violated. In this case the {1} will be substituted with the name of the property which has caused the rule violation, eg. “CompanyName is required”.

[BrokenRules.DescriptionAttribute("{1} is required.")]
private bool StringIsRequired(object target, BrokenRules.RuleArgs e) 
{
     bool retval = true;

     PropertyInfo propertyToCheck = (typeof(Customer)).GetProperty(e.PropertyName);
     string propertyValue = (string)propertyToCheck.GetValue(target,null);

     if (propertyValue == "") 
     {       
             retval = false;
     }

     return retval;
}

Now that we have defined our business rules, we use them as required. Firstly we include a call to the CheckRules method in our property setters. Here, in the CompanyName's property setter, I am checking all rules with the generic name of “company”. The framework will update the BrokenRules collection to show any violation, and set the Customer's Invalid property.

  public string CompanyName
  {
         get
         {
                 return _companyName;
         }
         set
         {                               
                  if(value == null) value = string.Empty;
                  if(_companyName != value)
                  {
                       _companyName = value;
                       BrokenRules.CheckRules("company");  // <<<=== here
                       MarkDirty();
                   }
         }
  }

We can also call the CheckRules method, without a parameter, in our data portal's Update methods. This will invoke all of the BO's rules as a double check just in case the UI did not disallow use of the Save method while the BO was invalid, or we forgot a call to CheckRules in a property setter.

This mechanism provides us with the tools needed to build a comprehensive set of business rules, including complex rules. Other noteworthy features are:

We have seen the implementation of the business rules within my Customer class. There is similar code within the Order class to implement a GreaterThanZero rule. The last thing we need to do is to set up so that any rule broken, in one of the customer's orders, is reflected up into our customer object itself. This requires a couple of overrides in our Customer class definition.

We need to ensure that our Customer's IsDirty and IsValid properties take into account the state of the collection of Orders that it contains. We do this by overriding the standard CSLA property getters.

  public override bool IsValid
  {
            get
            {
                     return base.IsValid && _orders.IsValid;
            }
    }

    public override bool IsDirty
    {
            get
            {
                      return base.IsDirty || _orders.IsDirty;
            }
    }

Lastly we need to fire the Customer's OnIsDirtyChanged event whenever a change is made to an order within the Customer's order collection. This is needed to allow data binding to automatically update the UI's error indications whenever an order is changed between valid and invalid states.

   private void OnListChangedHandler(object sender, System.ComponentModel.ListChangedEventArgs e) 
   {
           this.OnIsDirtyChanged();                                                
   }



  1. Deliverable: n-level undo

Let us start with the simple 2-level undo that my application has already demonstrated. I was able to undo changes at both the Customer, and the embedded Customer Order collection levels, with a single line of code addressed to the Customer BO.

Firstly the application set things up by instructing the CSLA framework to take a snapshot of the Customer BO before allowing the user to gain access to it:

   /* Cause our BO to take a snapshot that would be used if the user 
    * requests a restoration of state via the Cancel button. */
    _customer.BeginEdit();

The user then was allowed to make changes to the customer level data, and it's collection of customer orders. Data binding updated our Customer BO and the embedded collection. Data binding worked behind the scenes to issue BeginEdit and ApplyEdit instructions at the Order level, as the user changed, then departed from, order rows.

At this stage nothing is persisted to the database unless the user presses the “Save” button, which calls the following line of code. The Customer object knows how, and when, to save any changed rows in it's Order collection. The UI can therefore ignore the Order objects, as from it's perspective it is dealing with a Customer object that has an Orders property.

   /* Inform the framework that user editing of the BO is complete, 
    * so that the snapshot can be discarded. */
   _customer.ApplyEdit();

   /* Cause the data portal to commit the changes to the database. */
   _customer = (Customer)_customer.Save();

If the user wants to discard their changes, they could instead press the CancelEdit button, which allows us to achieve a 2-level undo with the following instruction. All data, including any changed orders, is reset to the snapshot.

   _customer.CancelEdit();

To proceed beyond a 2-level undo we need to extend our application to also open a modal form allowing us to see, and change, the freight amount for a single order. The first form can open the modal form, passing it the active grid row's Order BO from the Customer's collection.

To run this demonstration I opened the main screen, and then altered the freight amount of the second order item from the database's original $13.99 to $15.00. I did not press the “save” button, so although the Customer BO had been modified I still had the option of undoing my change.

I then caused the modal form to open, showing just the altered order line. The freight value showed the changed value of $15.00. I used the modal screen to change the freight amount again, this time to $55. The change was reflected back into the underlying main form, as both forms are working with the same Order BO. The modal form's “Cancel” button took me back to the modified value of $15.00 which was the value that it 1st saw, but would not take me back to the original $13.99 which was loaded from the database. The modal screen's “cancel” action also caused the underlying main screen's value to be reset to $15.00.

I changed the modal screen's value back to $55 again, pressed the modal screen's “save” button, and then closed the modal screen.

The main form regained focus, with the second order line showing the $55, which the modal screen had updated into my BO. When I pressed the main form's “Cancel” button the freight amount was reset back to the original $13.99. This action restored us to the original value, wiping out changes made in n-levels (ie. both the main and modal forms).

All of this was achieved without referring back to the database. The database did not receive any of the updates as the main form's “save” button was not pressed.




The code required to achieve this n-level undo capacity is very simple. The main form loads the modal form, passing it the Order BO shown by the current grid row.

  private void btnShowOrderDetail_Click(object sender, System.EventArgs e)
  {
        int i = grdOrders.CurrentRowIndex;
        CustomerOrder order = _customer.Orders[i];
        using (frmUpdateOrderDetail frm = new frmUpdateOrderDetail(order)) 
        {
                frm.ShowDialog();
        }
  }

The modal form's constructer is as follows. We need the same hack as mentioned earlier.

                public frmUpdateOrderDetail(CustomerOrder order)
                {
                        InitializeComponent();

                        hack = new CheckBox();                  
                        hack.Size = new Size(0, 0);     
                        hack.Enabled = false;
                        this.Controls.Add(hack);
                        
                        _order = order;
                        _order.BeginEdit();
                        DataBind(_order);
                }

The remainder of the modal program is as follows. Really, the only point to notice from all this is that the CSLA framework has given us n-level undo without any effort on our part.

      private void DataBind(CustomerOrder order) 
      {
            Util.BindField(txtOrderID, "text",order,"OrderID");
            Util.BindField(txtFreight, "text",order,"Freight");
            Util.BindField(hack, "Checked", order, "IsDirty");
            Util.BindField(btnUpdate, "Enabled", order, "IsValid");
      }

      private void frmUpdateOrderDetail_Closing(object sender, System.ComponentModel.CancelEventArgs e)
      {
            Util.ClearBindings(this);
      }

      private void frmUpdateOrderDetail_Closed(object sender, System.EventArgs e)
      {
            _order.CancelEdit();    
      }

      private void btnCancel_Click(object sender, System.EventArgs e)
      {
             _order.CancelEdit();
             _order.BeginEdit();
      }

      private void btnUpdate_Click(object sender, System.EventArgs e)
      {
             _order.ApplyEdit();
             _order.BeginEdit();
      }

The n-level undo was achieved via the main form's “cancel” button. The code behind this was unchanged from my original implementation.

                private void btnCancel_Click(object sender, System.EventArgs e)
                {
                        _customer.CancelEdit();
                        DataBind(_customer);
                        _customer.BeginEdit();
                }

Just for completeness I should explain the ClearBindings method used above. It was required to release the “hack” Checkbox's subscription to the Order's OnIsDirtyChanged event. Without this we would receive a “Cannot access disposed object” exception when the main form tried to update the Order BO. The ClearBindings code is as follows:

     public static void ClearBindings(Control control)
     {
            control.DataBindings.Clear();
            if (control.Controls.Count > 0)
            foreach (Control c in control.Controls) 
            {
                   ClearBindings(c);
            }
     }



  1. Deliverable: Security

The CSLA framework has two security options for us to choose from:

  1. Windows integrated security: in this mode we rely upon the environment (Windows) to authenticate the user. The framework can mostly ignore security considerations as it is assumed that Windows security has been set up appropriately. In this mode the data portal should be set-up to disallow anonymous access.

  2. What Rocky calls “custom, table-based security”: here the framework uses .Net's security constructs so that the UI passes the user's identity to the data portal for each call. The data portal will then configure itself to impersonate the user. This means that we can check identity at all points; the UI can be set-up to offer only options which the user is authorised to use, and the data portal can provide a double-check. The security checking in the data portal also allows our BO's to be used in other environments, such as web services, where there is no UI.

BO's built with the CSLA framework can be tolerant of either of the above security options. We can move between the above two security options with just a change to a runtime configuration file. This means that our security choice is just a deployment option, depending upon whether the application is being used within a secure environment, or being offered to the wider public such as over the Internet.

I will now focus upon the second of these two security options; “custom, table-based security”. The framework includes a sample login authentication facility using a simple database containing a Users, and a Roles tables. The users are loaded into the Users table with ID's and passwords. The Users are then assigned one or more roles in the Roles table.

It is intended that we should modify, and extend, this basic facility for our own requirements. The framework has done the hard work of maintaining security once the user has been identified and authenticated. All we need to do is modify the authentication criteria.

To demonstrate how the framework supports the authentication process I have built a simple form that allows a user to log in just by pressing a button.

I have hardcoded the user's ID and password into the OnClick event handler as what I am just trying to show is how the framework supports the login process.

The CSLA.Security.BusinessPrincipal.Login method will validate the UserID and password combination using the sample facility mentioned above. If the login is successful .Net's role-based security objects are populated with the roles that the user is authorised for. This information will be available throughout our application.



 private void tlB_ButtonClick(object sender,  System.Windows.Forms.ToolBarButtonClickEventArgs e)
 {
    // Evaluate the Button property to determine which button was clicked.
    switch(toolBar1.Buttons.IndexOf(e.Button))
    {
         case 0:
              using (frmShowCustomersByCountry frm = 
                   new frmShowCustomersByCountry()) 
              {
                      frm.ShowDialog();
              }
              break;
         case 1:
              LogIn();
              break;
         case 2:
              LogOut();                       
              break;
    }               
                        
 }

 private void LogIn() 
 {
    CSLA.Security.BusinessPrincipal.Login("MyID","MyPassword");
    ShowLogInStatus();
 }

 private void LogOut() 
 {
    // reset to an unauthorized principal
    Thread.CurrentPrincipal = 
          new GenericPrincipal(new GenericIdentity(""), new string[] {});
    ShowLogInStatus();
 }

 private void ShowLogInStatus() 
 {
    statusBarPanel1.Text =
       (Thread.CurrentPrincipal.Identity.IsAuthenticated) 
       ? "You are logged in.": "You are logged out.";
 }

I have changed the down-stream Customer Update form to disallow use of the “show order detail” button unless the user has logged with a User ID that has a “Project Manager” role.

The following code shows the changes required to achieve what we are seeing here. Firstly there was an addition to the form's constructor.



      if (Thread.CurrentPrincipal.IsInRole("ProjectManager"))
           btnShowOrderDetail.Enabled = true;
      else
           btnShowOrderDetail.Enabled = false;
      ShowLogInStatus();

The ShowLogInStatus method is:

   private void ShowLogInStatus() 
   {
     statusBarPanel1.Text =
     (Thread.CurrentPrincipal.IsInRole("ProjectManager")) 
     ? "You are logged in as an Project Manager.": 
       "Only Project Managers can use the Show Order Details facility.";
   }

The above changes show how we can use security status information in our UI. We can also use these security facilities inside our BO's. This allows us to double-check our UI, and allows our BO's to be offered via web services.

If I wanted to only allow Project Managers to update our Customer BO's I would override the default implementation of it's Save method as follows:

  public override BusinessBase Save()
  {                       
       if(!Thread.CurrentPrincipal.IsInRole("ProjectManager"))
           throw new System.Security.SecurityException(
                     "User not authorized to update a customer");

       return base.Save();
  }

If my UI somehow allowed an unauthorised user access to the “save” button the BO would protect our security as seen to the left.

We could likewise override the default actions for read, delete, or insert operations.



  1. Deliverable: Localization

The CSLA framework gets the textual content for any message it needs to issue from a resource file. Rocky wrote the original English language resource file, and then the user community has contributed translations into other languages. Currently there are resource files for eight additional languages.

To demonstrate this capability I have forced the framework to output an exception message by intentionally violating one of it's rules.

So that we can see what is going on, I have enhanced the UpdateCustomer form to display the user's culture in the right hand side of the form's status bar. This was done by the following line of code in the form's constructor:

                statusBarPanel2.Text = Thread.CurrentThread.CurrentCulture.DisplayName;

Now, here is the line of code that causes the intentional error.

                        _customer.Orders[0].Delete();

This is will cause a runtime error because my Order BO has been set-up as a “child” object. It cannot be directly deleted. It must be deleted via it's collection object.

The line of code results in the exception to the left. The status bar panel in the right corner shows that I have my PC set-up within an English speaking region.

The exception has been generated by the framework itself. The exception's text (“Can not directly” ... etc) has been obtained from the English language resource file that is relevant to my culture.





Now let us see how this error would be experienced by a German speaking user. We can simulate this by changing the PC's regional setting via Window's Control Panel's “Regional and Language Options” applet.





If we rerun the same unaltered, uncompiled application. We now see that the exception's message has been presented with German text.

Hopefully our application will not be causing the CSLA framework to issue many exceptions, but the mechanism that Rocky has given us can be utilised so that we can also internationalise our own strings. The implementation is sophisticated, as the user's culture is passed into the data portal for each call. I have not tried it, but I am assuming that this means that a remote App Server can service clients of differing cultures, and communicate with each in their own language.

  1. Deliverable: Best Practice

It is not easy to come to .Net, and immediately adopt best practice. The issue is that .Net is such a huge framework. It has to be so flexible as it is required to be all things to all people.

There are just so many options, and it doesn't just stop at the .Net framework. There is also ADO .Net, ASP.Net and OO design techniques that also need to be mastered.

There is plenty of help available, but the sheer quantity of this help is part of the problem. A quick look around my PC shows 130mb of help and quick start materials within the .Net directories, and then there is further 950mb in my MSDN directory. There is so much help, that it is almost no help. It is hard to get the broad overview needed to make architecture design choices. The help material seems to quickly descend into fine detail, which seems to open up more questions than provide answers when I am in architecture-design mode of thinking.

Of course this broad overview is hard earned, and can only really come from years of experience of .Net, and the tools which precede .Net. Rocky has this, which is why he wrote the book, and I am just the reader.

There are many, many books and sites providing “help” also. The big question is “which book”, and “which site”. In my opinion, most books get us up and going too fast with their “Foo”, or lightweight “customer” and “order” examples. It is easily apparent how I can write a .Net application, but it is extremely hard to see how I should write one. My future self would thank me if I could just slow down, and get the architecture and basic framework right before rushing into start coding the application. A great aim, but so hard to achieve!

I think that Rocky's books have a great deal to offer here.

  1. Deliverable: Community

An active community has gathered around the CSLA framework. The community includes some people that are very knowledgeable about CSLA, OO, .Net, and frameworks in general. The community is also very welcoming, and helpful to newbies.

There is a forum which, as of November 2005, reports 2725 current members. It is an active forum. I have registered to receive a daily email summary of any new postings. A typical day consists of about 20 new postings, so questions get regularly answered.

The forum archive is accessible by a content search facility. I have found the answer to all of my questions just by searching the archive.

Rocky is often involved in forum discussions. He seems to have a good open policy of discussing future directions for the framework, seeking feedback. I am sure that the depth of experience, and commitment within the community, is a contributing factor in the framework's depth, breadth, and success.

There have been a couple of notable contributions from community members (see links)

  1. Deliverable: Licence to use

Rocky has made the CSLA framework, and it's source, available for use under very generous terms. Basically it is free to use for any purpose, including commercial purposes, with the sole exception being that we “may not use it in whole or in part to create a commercial framework product ”. There are links to the licence agreement and a discussion of the intent of the licence in the links section of this document.

Conclusion

It is well worth the effort and time taken to come to grips with CSLA. Any difficulty experienced is, in part, the general pain of coming to grips with .NET itself. This is a big job, but Rocky has prepared the way for us by striking a best practice path through the maze of good and bad .NET options and byways.

The CSLA framework is fit to use as is, for small or large applications. It is also very well documented and explained by Rocky's book, allowing us to tailor the framework for any of our own specific requirements.

There is an active and friendly community to provide assistance.

As I started, and worked through this exercise, I focussed mainly upon the framework itself. Now that I have completed the task I have moved to the view point that the journey is worth at least as much as the destination itself. Rocky's book was an excellent investment in terms of both the framework that I now have to build applications upon, but also in the improved practical understanding I have developed while working through the book itself.



Links: