Search  
Monday, September 06, 2010 ..:: Articles » CSLA version 2; what's in it for me? ::..   Login
 CSLA version 2; what is in it for me?
Introduction | Business Objects | Simplify UI | Data Binding | ORM | Business rules | N-level undo | Security | Scalability | Localisation | Best practice | Community | License | Links
 
Show as multiple pages

The CSLA Framework V2; what is in it for me?

BarryMossman<is_at>primos.com.au

This is version 2 of this article. A new version was required because we now have new major version of CSLA. I have rewritten my demonstration program, and the new version is not only smaller, but it is also more functional. This is due to the fact that the quick answer to the “CSLA; what is in it for me” question was “lots”, and now the answer is “lots more”.

Version!! This article relates to CSLA version 2 which runs with .NET version 2. There is an older version of this article covering CSLA version 1 available here.

Source!! The source (C#) for my demonstration program is available as a download from this site, as a number of people wrote asking for it last time.

Printing!! If you wish to print this article see the Printing Tips page within the Articles section of this site.here.

If you already know what CSLA can do for you, and are looking for Chris Denslow's CSLA Desktop Reference site (C# and VB) it is here.

Aim of this article

In 2005 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:

  • it is aimed at facilitating the use of business objects (BO's), built along OO principles, which can be optionally deployed in a distributed-application environment

  • it is built upon .NET (v1.1, now with a new version for .NET V2)

  • it is documented by Rocky's books (see links section)

  • it comes in C# and VB.Net flavours (both the book and the framework source)

  • it's source is distributed for free, with a generous right to use license

  • it is a real-world framework, with a large and active user community

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 7th release level (2.0). It is a large book, the C# version weighs in at nearly 2 and a half pounds and 690 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:

  • Remoting

  • Reflection

  • Serialization

  • Compile time attribute classes

The CSLA framework is an open source style effort, 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 2.0 of the CSLA framework.

  • Smart and mobile Business Objects

  • Minimise, and simplify, the code in the UI

  • Full support of complex data binding

  • Object relational mapping

  • Business rules

  • N-level undo

  • Security

  • Scalability

  • Localisation

  • Best practice

  • Community

  • Licence to use


Deliverable: Business Objects; smart and mobile

CSLA delivers two major contributions towards the use of Business Objects (BO) within our applications:

  • the framework makes it easier for us to make an effective BO, as it provides a very well thought out, and proven, design which removes most of the heavy lifting, deep level of skill, and drudgery that would otherwise be involved.

  • the framework solves the very complex task of how to design and build BO's which can be used by an application that is, or may become, deployed across multiple hardware tiers. This is achieved in a manner that doesn't lose the gains initially sought by adopting an object-oriented approach.

Firstly I would like to briefly discuss the case for using BO's in our applications. As you would expect with a title like “Expert 2005 C# Business Objects”, this topic is covered in great depth by Rocky's book. Much of the case can be summarised by those foundations of the Object-Oriented paradigm; encapsulation and reuse.

Our applications exist to deliver a union of our business data and our business rules to our users. We need the business rules to be available to our UI so that it can provide the users with a rich experience. The availability of the business rules within the UI allows us to give immediate validation and authorisation feedback to the user, without repetitive round-trip delays back to the server. In a desktop application this means that the business rules are available within the WinForm, and in an Internet application they need to be available to the Web Server.

If the application has an Application Server tier, the same business rules are required at that point also. The App Server is the central point of control before data makes it way out of, or into, our database. It needs the business rules to ensure that any UI has correctly and consistently applied validation and authorisation rules.

The business rules are also required to manipulate or interpret the business data within whichever tier is doing the actual work of servicing the user's request.

Even if our application has no current need for physical tiers it should be built with logical tiers. The application will be more easily and safely maintainable,with a longer useful life, if it is segmented into logical tiers. It also means that if we ever need to implement hardware tiers at some later point, the application design is ready for this. The tipping point may an increase in the number of users, a need to pool database connections, or just a need to move what was a desktop application onto the Internet.

The BO needs to be a mobile object, which can move between the various tiers, to meet the needs of each tier . The design and construction of the BO needs to take into account the laws of physics regarding the latency and overheads involved when tier computers are cooperating to complete a job. Careful design is required to avoid the killer performance penalty that will result if the underlying mechanism is overly chatty or inefficient.

Business Objects encapsulate our business data within our business rules. The only way to get or update the data is via this mobile BO, which we design to apply and enforce the business rules. This encapsulation and protection would be lost if our system was having to move data between the tiers in an unprotected container such as in a DataSet or an xml package.

CSLA BO's not only provide better encapsulation and protection for our data than these non-BO alternatives, but they are also more lightweight to move between the tiers. This is due to the fact that they are binary, and don't require the meta-data overhead of DataSets. This lightness is achieved by installing our BO class library's DLL on each of the computers involved, so that only the data needs to be serialised and transmitted. DLL deployment and version update issues can be handled by .NET's ClickOnce facility, now made easier by the Publish Wizard which is built into VS2005.

The reuse angle comes from the fact that the business rules within the BO are reused between tiers of an application, and can also be reused between applications. The case study that Rocky builds during the course of his book show a set of BO's being built then deployed in three separate application modes. Essentially the same application is deployed as a desktop application, a web server application, and then as a set of web services. The same set of BO's are reused for each application. This reuse achieves savings, and also ensures that all applications are applying the same consistent set of business rules.

As I said at the opening of this section, CSLA has two major contributions to make with regard to BO's. The first is the design and implementation of the CSLA framework to meet these challenges, which is a task requiring deep experience and exceptional knowledge of very complex corners of Microsoft's huge .NET framework. This skill level was required by CSLA's designer, but is not required of us to use and deploy the framework.

CSLA's second contribution in this regard is the depth and breadth of the CSLA framework itself. We are left with just the task of writing our business logic. Most of the logic which makes our class into an effective object comes already built into the CSLA framework. I can illustrate this via one of the classes that I use within the demonstration program that comes with the article that you are reading. It is a Customer Order Header class. This class diagram shows that I have implemented a few data properties allowing access to the object's business data. We can see that I have some methods involved with authorisation and the business rules used by the property setters. There are also a couple of methods involved with fetching and updating the order header.

This is all the work that I had to do, but as the following Class View listing shows there is much more to my Order class. This listing shows that my Order class has descended from a rich hierarchy of CSLA classes which enrich my Order class with a lot of other features. Not all of these methods are used by the UI programmer, but the listing demonstrates the breadth of what is delivered by the framework, Examples are the ability of an Order object to clone itself, the IsValid property, or the collection of broken validation rules.




Another benefit coming from the use of BO's is their suitability for unit testing. I don't know how to apply unit testing to a UI, but if we extract our business rules into BO classes it becomes very easy to write comprehensive unit test suites for these rules.

I don't have the up-market version of VS2005, so here is a demonstration using the free utility called NUnit (see links section of this document). The following image shows that I have run the suite of unit tests which have been defined for our BO's, and that one of these tests has failed. The listbox at the middle right shows an identifying error message, and informs us that a value of 88.88 was received, but a value of 36.68 was expected. The list box at the bottom right is showing that failure happened at statement 93 of our testing procedure.

The test suite project is part of our VS2005 solution. We can exhaustively test and re-test our business rules at any time, with a simple press of the “Run” button. If our business rules were scattered through the UI, rather than being centralised in BO's, this would not be possible. NUnit allows us to run all tests by clicking on the root node on the left hand side of the screen as I have done here. Alternatively we can elect to run a a single test, or a sub-set of tests by selecting nodes within the tree itself.

Now I will examine part of the test suite project itself. Firstly this will demonstrate how easy it is to create unit tests for our BO's. NUnit will automatically create a tree node for any method that is marked with the [Test] attribute. This is a facility to run test set up and clean up procedures before each test, or each group of tests. It has a selection of Assert statements to handle a variety of equality, identity, comparison and type tests. Tests can also be easily set-up that will fail unless a specific exception is raised by the code being tested.

The second point of including the following source code is to illustrate how easy the BO are to use. These test procedures deal with the BO's at a simple level to fetch, clone and update data. The programmer has not needed to be concerned with database table structures, ADO, nor SQL.

using System;
using CslaWIIFM2.library;
using Csla; 
using NUnit.Framework; 
 
namespace NUnitTests 
{ 
 [TestFixture] 
 public class OrderUpdateTests 
 { 
  // fields 
  Customer _customerFromUSA; 
  Customer _customerFromUSAClone; 
  Customer _customerFromElsewhere; 
  Customer _customerFromElsewhereClone; 
 
  private readonly string _updateDate = "27/09/2006"; 
  private readonly float _updateFreight = 36.68f; 
 
  /// <summary> 
  /// This setup routine runs before each test (methods marked with [Test] attribute. 
  /// </summary> 
  [SetUp] 
  public void Init() 
  { 
   _customerFromUSA = Customer.GetCustomer("HUNGC"); 
   _customerFromUSAClone = _customerFromUSA.Clone(); 
 
   _customerFromElsewhere = Customer.GetCustomer("BLONP"); 
   _customerFromElsewhereClone = _customerFromElsewhere.Clone(); 
 
   CslaWIIFM2.library.Security.SecurityPrincipal.Logout(); 
   CslaWIIFM2.library.Security.SecurityPrincipal.Login("Doris in Despatch", ""); 
   System.Security.Principal.IPrincipal user = 
   Csla.ApplicationContext.User; 
   Assert.IsTrue(user.Identity.IsAuthenticated, "Login failure durinit Init"); 
  } 
 
  /// <summary> 
  /// These methods runs after each test. 
  /// </summary> 
  [TearDown] 
  public void CleanUp() 
  { 
   CslaWIIFM2.library.Security.SecurityPrincipal.Logout(); 
   CslaWIIFM2.library.Security.SecurityPrincipal.Login("Doris in Despatch", ""); 
 
   /* Clone is the object value before the test. Save this object to restore the db 
    * to it's initial state. Need to make the clone appear changed, so update, then 
    * reset a property to it's initial value. */ 
   float xxx = _customerFromUSAClone.Orders[1].Freight; 
   _customerFromUSAClone.Orders[1].Freight = 0f; 
   _customerFromUSAClone.Orders[1].Freight = xxx; 
   _customerFromUSAClone.Save(); 
 
   xxx = _customerFromElsewhereClone.Orders[1].Freight; 
   _customerFromElsewhereClone.Orders[1].Freight = 0f; 
   _customerFromElsewhereClone.Orders[1].Freight = xxx; 
   _customerFromElsewhereClone.Save(); 
   CslaWIIFM2.library.Security.SecurityPrincipal.Logout(); 
 
  } 
 
  /// <summary> 
  /// One of my business rules is that freight is free within the USA.  
  /// This test ensure that the USA based Customer object becomes  
  /// invalid if a freight value is applied. It also tests that 
  /// non-USA customer can be charged freight.  
  /// </summary> 
  [Test] 
  public void TestFreeUsaFreightRule() 
  { 
   _customerFromUSA.Orders[1].Freight = 0.01f; 
   Assert.IsFalse(_customerFromUSA.IsValid,  
    "Ensure freight invalid for USA"); 
   _customerFromUSA.Orders[1].Freight = 0.00f; 
   Assert.IsTrue(_customerFromUSA.IsValid,  
    "Ensure reset USA freight to 0 makes object valid again"); 
 
   _customerFromElsewhere.Orders[1].Freight = 0.01f; 
   Assert.IsTrue(_customerFromElsewhere.IsValid,  
    "Ensure invalid freight rule only applicable within the USA"); 
  } 
 
  [Test] 
  public void TestOrderUpdates() 
  { 
 
   Order order = _customerFromElsewhere.Orders[1]; 
   Assert.AreNotEqual(order.Freight, _updateFreight,  
    "test needs these to start out as being unequal"); 
   Assert.AreNotEqual(order.ShippedDate, _updateDate); 
   order.Freight = _updateFreight; 
   order.ShippedDate = _updateDate; 
   _customerFromElsewhere.Save(); 
 
   Customer cu = Customer.GetCustomer(_customerFromElsewhere.Id); 
   Assert.AreEqual(cu.Orders[1].Freight, 88.88m, "Update error"); // <== cause error 
   //Assert.AreEqual(cu.Orders[1].Freight, _updateFreight, "Update error"); 
   Assert.AreEqual(cu.Orders[1].ShippedDate, _updateDate); 
  } 

Our BO's can have much more function than the simple ones that I have developed for this article. My BO's do little more than provide an simple editable view of our data. A real-world set of BO's would contain all of the functions that could be performed with our application. An example could be “amalgamate all unshipped orders for this customer”.


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

The part of our application which is most subject to change is usually our UI. 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 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 achieve in our UI tier via a small application and a few BO's which I have written using SQL Server's Northwind database.

Firstly let us start small by having a dialogue box that displays the list of those countries for which there are customers. We select a country, and the form changes to a list of customers within that country. When we select a customer the dialogue box auto-closes, returning the ID of the chosen customer to the calling program.

There is a facility to go back and chose a different country before selecting our customer.

Here is the VS2005 designer for the above dialogue box. On the left we can see that my application has a number of BO's available to it. At the bottom of the form we can see that I have dragged two of these data sources onto my form. This has created a BindingSource component for the CountryList BO, and the another for the CustomerList BO.

The designer doesn't show the listbox which will contain the data as I have created a generic User Control for these, and they will be added into the form a run time.

The constructor for the User Control is as follows. The user control contains a listbox. We can see that the listbox's data binding properties are being set so that it knows which datasource field to display in the list, and which field to use as the displayed row's ID. At this stage the listbox hasn't been informed of the identity of the datasource.

public CountryCustomerSelectionList(
   string displayMember,
   string valueMember,
   Color backColour)
  {
   InitializeComponent();

   this.BackColor = this.BackColor;
   this.Dock = DockStyle.Fill;
   this.listBox1.DisplayMember = displayMember;
   this.listBox1.ValueMember = valueMember;
  } 

Now back to the dialogue form whose designer window we saw above. Here is it's Load event. We can see that we have added two of the user controls to our form, one for each of the BindingSource components that we dragged onto the form. If you refer back to the designer window you can see that the property names Name, Id, Value and Key are coming from our BO's.

private void CountrySelect_Load(object sender, EventArgs e)
  {

   _companyList = new CountryCustomerSelectionList(
    "Name",
    "Id",
    this.BackColor);
   groupBox1.Controls.Add(_companyList);

   _countryList = new CountryCustomerSelectionList(
    "Value",
    "Key",
    this.BackColor);
   groupBox1.Controls.Add(_countryList);

   _listBeingDisplayed = _countryList;

   SetUIForViewMode();
  }

Here is the class diagram for the CustomerList BO. We can see that it has a GetList method which is marked public and static. Since the method is static it can be used without needing to firstly create a CountryList instance. We can also see that it returns a CountryList object. This is how we get our list of countries. We simply call CountryList's GetList method, and we get passed back populated list of countries. We see this in the following method.

At the close of the above event handler we set the _listBeingDisplayed variable to initially refer to the Country list, and we then called the SetUiForViewMode method. Here is that method. We now get to use our BO's. Initially we will flow into the 1st else clause. We can see that the country list is acquired and passed into the BindingSource's DataSource property.

private void SetUIForViewMode()
  {
   if (_listBeingDisplayed == _countryList)
   {
    if (_countryList.ListAlreadyLoaded)
    _countryList.BringToFront(); // use cached copy.
    else
    {
    countryListBindingSource.DataSource = CountryList.GetList();
    _countryList.LoadList(countryListBindingSource);
    }

    selectButton.DialogResult = DialogResult.None;
    resetCountryButton.Enabled = false;
    this.Text = "Select a country";
   }
   else
   { // is "customer" mode
    customerListBindingSource.DataSource = CustomerList.GetList(_countryId);
    _companyList.LoadList(customerListBindingSource);

    selectButton.DialogResult = DialogResult.OK;
    resetCountryButton.Enabled = true;
    this.Text = String.Format("Select a customer from {0}.", _countryId);
   }
  }

The second else clause in the above method shows us acquiring the customer list for the selected country. We call the CustomerList's static GetList method passing the selected country's ID. We are returned the populated list of customer for that country.

The listboxes were populated by the user control's LoadList method. Essentially it just assigned the Binding source to the listbox's DataSource property. The use of our BO's has made the tasks of getting the list of countries and customer very easy for the UI.

public void LoadList(BindingSource dataBindingSource)
  {
   this.listBox1.SuspendLayout();
   this.listBox1.DataSource = null;
   this.listBox1.DataSource = dataBindingSource;
   this.listBox1.ResumeLayout();
   _listAlreadyLoaded = true;

   this.BringToFront();
  }

The dialogue box did have a couple of buttons. Here are their click handlers to complete the picture of what is happening.

/// <summary>
  /// The user has selected something.
  /// The first time through they have selected a country. We toggle the list
  /// from a list of countries, into a list of that country's customers. When
  /// the user has also selected a customer the form will auto-close as the
  /// "select" button will have been set-up to return a modal result.
  /// </summary>
  private void selectButton_Click(object sender, EventArgs e)
  {
   // store selection; is either a country or a customer
   if (_listBeingDisplayed == _countryList)
   {
    _countryId = _listBeingDisplayed.listBox1.SelectedValue.ToString();
    _listBeingDisplayed = _companyList;
    SetUIForViewMode();
   }
   else
    _customerId = _listBeingDisplayed.listBox1.SelectedValue.ToString();
  }

  /// <summary>
  /// Offer the user the option of changing their country selection.
  /// </summary>
  private void resetCountryButton_Click(object sender, EventArgs e)
  {
   _listBeingDisplayed = _countryList;
   SetUIForViewMode();
  }

We have seen quite a bit of code, but almost all of it is just setting up and controlling the UI. This is good. The use of BO's has extracted away all of the logic required to obtain the data from our data store. All we saw was:

  • use of data binding to connect our form control to the BO: ..... this.listBox1.DataSource = dataBindingSource;

  • tell the form control which properties to display: ..... this.listBox1.DisplayMember = displayMember; etc

  • ask the BO for the data: ..... CountryList.GetList();

We should also take note of what we didn't see.:

  • no coupling between our UI and our current data store (we use SQL SERVER currently, but who knows if this will always be the case?)

  • no SQL, ADO, nor any logic to handle cross tier transport

  • no mapping showing just which table rows get used for what purposes

We have been allowed to just focus upon UI issues. This means that the UI programs will be clearer, and we have minimised the risk should we need to open up the UI again to make changes to it.

We would have expected that simple functionality would only require negligible UI program code. The good news continues as we extend our application to provide more complex functionality. Here we have a form allowing to update our selected customer and also their order headers. We have done some data entry, but have broken some of the business rules. We have blanked out the Contact name field and we can see that this is not allowed. We have also erroneously set a freight cell to something that is neither a zero nor a positive number. The error indication fields have explanatory tooltips.

We can also see that the save button has been disabled as the BO is now in an invalid state. The save button normally operates to save any customer level, and/or order level, changes to the database. We can also see a cancel button which would undo all data entry changes, which haven't already been committed via an earlier successful press of the save button. This undo function is achieved without the need to hit the database again to refresh the data.

We get a lot of assistance from VS2005 and our BO's when designing such a form. Here is what VS2005's drag and drop support has automatically created for me when I dragged the Customer BO, and then it's embedded Orders BO, onto a blank Winform. As you can see it is more a matter of tailoring defaults, and deleting what you don't want, than building the UI from scratch.

The following code handles the calling of our dialogue box to select the customer to be further processed. It then uses the Customer BO to obtain the customer by ID. As we saw in the above Data Sources window, the customer BO contains an embedded Orders BO which contains all of the customer's orders. All of this is delivered to us by the statement “Customer.GetCustomer(selectForm.CustomerId);” We then connect the customer and it's embedded Orders BO to the BindIngSource components created by the drag and drop mentioned above, This will populate all of the customer level fields, and the grid of orders.

using (SelectCountryForm selectForm = new SelectCountryForm())
    {
    if (selectForm.ShowDialog() == DialogResult.OK)
    {
     try
     {
      _customer = Customer.GetCustomer(selectForm.CustomerId);

      if (_customer != null)
      {
       _customer.BeginEdit();
       customerBindingSource.DataSource = _customer;
       customerOrdersBindingSource.DataSource = _customer.Orders;

       if (!CustomerGroupBox.Visible)
        CustomerGroupBox.Visible = true;

       SaveButton.Enabled = _customer.IsValid;

       _formTitlePart2 = string.Format("processing a customer from {0}.", selectForm.CountryId);
       this.Text = this.FormTitle;
      }
     }
     catch (Csla.DataPortalException ex)
     {
      MessageBox.Show(ex.BusinessException.ToString(),
      "Data load error", MessageBoxButtons.OK,
      MessageBoxIcon.Exclamation);
     }
     catch (Exception ex)
     {
      MessageBox.Show(ex.ToString(),
      "Data load error", MessageBoxButtons.OK,
      MessageBoxIcon.Exclamation);
     }
    }
    }

Here is the code which ensures that the “save” button becomes disabled and enabled as the user makes, and then corrects, any data entry changes which violate our business rules.

private void customerBindingSource_CurrentItemChanged(object sender, EventArgs e)
  {
   SaveButton.Enabled = _customer.IsValid;
  }

  private void customerOrdersBindingSource_CurrentItemChanged(object sender, EventArgs e)
  {
   SaveButton.Enabled = _customer.IsValid;
  }

Here is the code which handles our “undo” feature.

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

And here is the code handling data updates to the database. A successful update will return us a new object. This is to allow for any triggered updates which may be generated from within the database server, or by our business rules. It would also required if we had implemented a “first write wins” concurrency scheme, as we would need a new set of timestamps from the database server.

The recommended technique is to attempt save a cloned version of our BO. Provided the save is successful, we then rebind to the new object which is returned by the save operation. Should the save fail for any reason, the UI is still attached to the original BO which cannot have been partially corrupted by the aborted save operation. This topic is outside the scope of this article, but as general indication remember that our save of the customer BO is potentially updating multiple rows in the Orders SQL table as well as a row in the Customer table.

private void SaveButton_Click(object sender, EventArgs e)
  {
   customerBindingSource.RaiseListChangedEvents = false;
   customerOrdersBindingSource.RaiseListChangedEvents = false;

   /* Close off undo support for any changes to our BO. Note that
    * we need to do this for the Orders collection as well as at
    * the Customer level. Although the Customer level call will 
    * have handled the imbedded collection, .Net data binding will
    * have started an implcit undo session for the cuurent row.
    * We need to close off this implicit edit as well as the one
    * that we explicitly caused by our BeginEdit. */
   customerBindingSource.EndEdit();
   customerOrdersBindingSource.EndEdit();

   Customer temp = _customer.Clone();
   try
   {
    _customer = temp.Save();
    customerBindingSource.DataSource = null;
    customerOrdersBindingSource.DataSource = null;
    customerBindingSource.DataSource = _customer;
    customerOrdersBindingSource.DataSource = _customer.Orders;
   }
   catch (Csla.DataPortalException ex)
   {
    MessageBox.Show(ex.BusinessException.ToString(),
    "Error saving", MessageBoxButtons.OK,
    MessageBoxIcon.Exclamation);
   }
   catch (Exception ex)
   {
    MessageBox.Show(ex.ToString(), "Error saving",
    MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
   }
   finally
   {
    customerBindingSource.RaiseListChangedEvents = true;
    customerOrdersBindingSource.RaiseListChangedEvents = true;
   }

  }

Again our UI is free from any SQL or database related code.

We also need to notice that it didn't contain any of our business rules either. These have been encapsulated inside our BO's

The error indicator that came on and off, as data entry changes broke and re-fixed the business rules, virtually come for free. We drag an Error component on the form, and connect it's datasource property to the BindingSource component created by the VS2005 drag and drop mentioned above. That is all we need to do. Since the CSLA base classes implement the IDataErrorInfo interface, the rest is handled for us, including the display of the tooltip messages coming from our BO's business logic.

The UI may also include some logic to handle authorisation. Depending upon the user's authorised roles, some properties, or whole BO's, may need to hidden or made read-only.. This task is also greatly assisted by the CSLA framework and will be shown in a separate section of this article, but hopefully I have already made my point, which was that the adoption of the framework has given strong assistance towards the goal of simplifying the UI sections of our application.

I have only shown a WinForms example here. Rocky's book, and downloadable sample application, extend this into a Web Forms application and Web Services interface. His example is also much more sophisticated that the simple example I have shown here.


Deliverable: Full support of data binding

All CSLA based BO's fully support data binding both at design time, and at run time, in both Windows and Web form applications. There has been a lot of work put into the CSLA framework to support all of the interfaces required to make this happen. The result is that we have framework allowing our BO's to work so easily and intuitively, that it is easy to overlook all the hard work and expertise that we are taking for granted. The book documents the inner workings of CSLA, and reading this makes it clearer the size of this deliverable. I have the eBook version, and Adobe tells me there is 775 instances of the word "bind" contained within the text.

We get design time data binding. On the left I have a new blank form, and I am preparing to drag my Customer BO onto it. Firstly I can specify whether I want VS2005 to create a“detail” level control for each Customer property, or a single grid containing a column for each property. I have chosen detail level controls.

Next, as you can see on the right, I can override which type of control will be created on a property by property basis. You should note that the framework has helpfully excluded base class properties such as IsValid and IsNew from our BO's browse list as we are unlikely to want to have these showing on our form.

I then drag and drop my Customer BO onto the blank form, and VS2005 automatically creates and connects all of the controls now shown.

I am using a Windows forms project for my demonstration, and pretty much the same facility is available for a Web Form project. This is a great boost to the productivity of UI programmers.

Now to runtime data binding support. This has mostly been set up automatically, so there is not a lot to show you. We can see from our grid's properties that it's Data Source points to the BindingSource component that was automatically created by the design time drag and drop discussed above.

We call also see that the columns are connected to underlying BO's properties. That is that is all that is needed to use BO data within our programs. We are getting the same degree of data binding services that we would receive from a heavyweight object such a 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.

When the UI creates it's BO instance, it then just needs to connect the BO into the bindingsource controls. The form controls are automatically populated with data.

customerBindingSource.DataSource = _customer;
customerOrdersBindingSource.DataSource = _customer.Orders;

Another example of data binding can be seen in the Save button, whose enabled property is bound to the IsValid property of my BO.

SaveButton.Enabled = _customer.IsValid;

A data binding event will toggle the save buttons enabled status as the user breaks and corrects the BO's underlying business rules.

private void customerBindingSource_CurrentItemChanged(object sender, EventArgs e)
  {
   SaveButton.Enabled = _customer.IsValid;
  }

Data binding is also automatically taking care of toggling on and off the error icons and tooltips which appear whenever a BO's business rule is broken. The display on the left shows that there are currently two business rules which have be violated. The Contact Name field has no value, and an order line has an invalid freight amount.


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.

An example is my CountryList BO, for which there is no table in the Northwind database. The BO's data is obtained from as follows from the Customers table:

SELECT DISTINCT Country
FROM   Customers

Another simplistic example is my Customer BO. 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 which contains a collection of all of the customer's orders. My Customer BO is editable. 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. The CSLA framework has a number of transactional options:

  • None, where we are fully responsible for setting up and controlling our own transactions

  • TransactionScope, where the BO will be run within a normal ADO-type transactional context unless it's SQL call is using more than one database, in which case MS DTC will be employed using a two-phase commit transaction to correctly handle a situation where one database can commit, but another cannot and does a rollback.

  • EnterpriseServices, where the framework will run the BO in a COM+ distributed transactional context

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 which contains a collection of the customer's orders headers as well as customer name and contact properties. Firstly you may recall that our UI was able to create, and load, our BO as simply as follows.

_customer = Customer.GetCustomer(selectForm.CustomerId);

Now to the definition of the 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 away 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<Customer>
 {
  #region Business Methods

  private string _id;
  private string _companyName = string.Empty;
  private string _contactName = string.Empty;
  private string _country;

  private Orders _orders = Orders.NewOrders();

  [System.ComponentModel.DataObjectField(true, true)]
  public string Id
  {
   get
   {
    CanReadProperty(true);
    return _id;
   }
  }

  // snip other property getter and setters 

  #endregion

Then there is the static GetCustomer factory method that the UI used to obtain the BO. Ignore the authorisation logic for now as it covered in a separate section of this article.

#region Factory Methods

  public static Customer GetCustomer(string id)
  {
   if (!CanGetObject())
    throw new System.Security.SecurityException(
    "User not authorized to view a customer");
   return DataPortal.Fetch<Customer>(new Criteria(id));
  }

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

BEGIN
EXEC dbo.sp_executesql @statement = N'CREATE PROCEDURE dbo.GetCustomer 
 @CompanyID nchar(5)
AS
 /* SET NOCOUNT ON */
 SELECT  CustomerID, CompanyName, ContactName, Country
 FROM   Customers
 WHERE  (CustomerID = @CompanyID)

SELECT  OrderID, ShippedDate, Freight
FROM   Orders
WHERE  (CustomerID = @CompanyID)

return' 
END

I have then provided an implementation for the framework's DataPortal_Fetch method used by our GetCustomer method.. This code is part of my Customer class, but is run by the data portal (data Access tier) to access the data. As you can see I have used ADO .Net to access the database, and then populated my state variables. No part of the CSLA framework is tied to ADO or SQL server. I could have obtained my data from other data stores such as Oracle, MySQL, Firebird, or even just some xml files.

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

private void DataPortal_Fetch(Criteria criteria)
  {
   using (SqlConnection cn = new SqlConnection(Database.NorthwindConnection))
   {
    cn.Open();
    using (SqlCommand cm = cn.CreateCommand())
    {
    cm.CommandType = CommandType.StoredProcedure;
    cm.CommandText = "GetCustomer";
    cm.Parameters.AddWithValue("@CompanyID", criteria.Id);

    using (SafeDataReader dr = new SafeDataReader(cm.ExecuteReader()))
    {
     dr.Read();
     _id = dr.GetString("CustomerID");
     _companyName = dr.GetString("CompanyName");
     _contactName = dr.GetString("ContactName");
     _country = dr.GetString("Country");

     // load child objects
     dr.NextResult();
     _orders = Orders.GetOrders(dr, _country);
    }
    }
   }
  }

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.


Deliverable: Business rules

The CSLA framework helps us to abstract the handling of business rules away from the UI. The rules become 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 a BO is deployed.

The BO is automatically in an invalid state if any of it's business rules are broken. The UI can detect the BO's validity, and display information regarding the collection of any broken rules. This collection is automatically kept updated by any change to the BO, including any use of the BO's CancelEdit facility.

My demo application implemented rules for both the Customer & Order BO's. 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:

_customer = Customer.GetCustomer(selectForm.CustomerId);

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

SaveButton.Enabled = _customer.IsValid;

and that data binding would maintain a live relationship between the BO's validity and the save button's enabled state. Regardless of the enabled status of the save button, the BO would refuse to save itself while it was in in invalid state anyway.

private void customerBindingSource_CurrentItemChanged(object sender, EventArgs e)
  {
   SaveButton.Enabled = _customer.IsValid;
  }

Any entries that fail our validation rules are auto-indicated within the UI as shown here. We have two invalid order lines. The tool tip shows us that the 1st error order has a freight amount less than $0.00. If we were to hover the mouse over the error indicator on the 2nd error line we would see that the error has been caused by the fact that this customer should not be charged freight, as we have a business rule stating that freight is free to customers within the USA. We can see that the Save button has been automatically disabled until both of these errors are corrected.

The display of the error messages, and the toggling on and off of the error indications, has been automatically achieved because the CSLA framework has implemented the various interfaces behind the scenes to make this happen. The UI has also been allowed to focus upon just presentation matters, because the rules have been delegated to the BO's themselves.

Now let us look at that part of the Order BO which implements it's rules and rule handling. We attach the business rules to the BO by having our BO class provide an override implementation for the framework's AddBusinessRules method. This method will be automatically invoked by the framework when required.

In this example we used one of the rules pre-defined within the CSLA framework, and have also used a rule which have created for ourselves. The framework includes various rules based upon equality, minimum, maximum, entry length and regular expression conditions. I have used a rule based upon a minimum value. As we have seen in the tooltip above, the rule outputs a meaningful warning message if the rule is violated.

protected override void AddBusinessRules()
  {

   ValidationRules.AddRule(
    Csla.Validation.CommonRules.MinValue<System.Single>,
    new Csla.Validation.CommonRules.MinValueRuleArgs<System.Single>("Freight", 0));
   ValidationRules.AddRule(FreightInUsaIsFree, "Freight");
  }

Following is the definition of our own business rule, requiring that freight be free within the USA. This rule is specific to my Order BO, and is contained within my Order class. It is therefore able to use my class fields such as this.CountryCustomerBelongsTo. Alternatively I could have written a generic rule which could be used by multiple BO's. This of course is what Rocky has done with the Csla.Validation.CommonRules methods. In these cases the rule uses reflection to obtain BO data during the validation checking.

private bool FreightInUsaIsFree(object target, Csla.Validation.RuleArgs e)
  {
   if (_freight == 0)
    return true;
   if (this.CountryCustomerBelongsTo == "USA")
   {
    e.Description =
    "Freight is free within the USA.";
    return false;
   }
   else
    return true;
  }

Here are the business rules for the Customer BO.

protected override void AddBusinessRules()
  {
   ValidationRules.AddRule(
    Csla.Validation.CommonRules.StringRequired, "CompanyName");
   ValidationRules.AddRule(
    Csla.Validation.CommonRules.StringMaxLength,
    new Csla.Validation.CommonRules.MaxLengthRuleArgs("CompanyName", 40));

   ValidationRules.AddRule(
    Csla.Validation.CommonRules.StringRequired, "ContactName");
   ValidationRules.AddRule(
    Csla.Validation.CommonRules.StringMaxLength,
    new Csla.Validation.CommonRules.MaxLengthRuleArgs("ContactName", 30));
  }

We cause the validation rules to be evaluated within our property setters by a call to CSLA's PropertyHasChanged method as shown below. The framework will determine which property has triggered the change via reflection. This helps make our code robust, as I could change the property name without having to remember to change the name string being passed into the generic validation rule. Alternatively I could have avoided the small reflection overhead by calling PropertyHasChanged(“CompanyName”).

public string CompanyName
  {
   get
   {
    CanReadProperty(true);
    return _companyName;
   }
   set
   {
    CanWriteProperty(true);
    if (value == null) value = string.Empty;
    if (!_companyName.Equals(value))
    {
    _companyName = value;
    PropertyHasChanged();
    }
   }
  }

The call to PropertyHasChanged will also cause our BO to be marked dirty, and will trigger our BO's PropertyChanged event used by data binding.

Our BO can also manually invoke rule checking at other times if necessary. An example could be co-dependant properties when a change to one property may potentially invalidate other property's settings, or perhaps we want to implement a belt and braces approach by re-validating data as our BO populates itself from the database.

My Customer BO contains an embedded Orders BO, which in turn may contain a collection of Order BO's. We need to connect these such that if one of the orders becomes invalid, this in turn make the Customer BO invalid. We do this within the Customer BO by overriding the CSLA's default implementation of the IsValid and IsDirty properties.

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

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

Deliverable: n-level undo

Let us start with the simple 2-level undo that we have already seen in my demo application. This was where the program allowed an undo of changes made to both the customer, as well as any of the customer's order headers.

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

_customer.BeginEdit();

Data at both levels was then turned over to data binding for use by the UI.

customerBindingSource.DataSource = _customer;
customerOrdersBindingSource.DataSource = _customer.Orders;

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

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. The database update is essentially triggered via the following.

customerBindingSource.EndEdit();
customerOrdersBindingSource.EndEdit();
_customer.Save();

If the user wanted to discard the changes, this could be done by pressing 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. The first form can open the modal form, passing it the active grid row's Order BO from the Customer's collection. The modal form will display that order's details, and allow us to update the freight value.

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

I then clicked the order's ID link field, opening the modal form showing just the altered order. The freight value showed the changed value of $11.11. I used the modal form to change the freight amount again, this time to $22.22. 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 $11.11 which was the value that it 1st saw, but would not take me back to the original $71.07 which was loaded from the database. The modal screen's “cancel” action also caused the underlying main screen's value to be reset to $71.07.

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

The main form regained focus, with the second order line showing the $22.22 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 $11.11. 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 order details are children of the customer BO. They are obtained from the database by the customer BO, and can only be updated back to the database by the Customer BO.


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 customerOrdersDataGridView_CellContentClick(object sender,
   DataGridViewCellEventArgs e)
  {
   if (e.ColumnIndex == iDDataGridViewLinkColumn.Index)
   {
    /* close off 1-level undo support for the current grid row
     * implicitly started by .Net data binding */
    customerOrdersBindingSource.EndEdit();

    using (OrderDetailForm ordersForm =
     new OrderDetailForm((Order)customerOrdersBindingSource.Current))
    {
     ordersForm.ShowDialog();
    }
   }
  }

The modal form's constructor is as follows. We really only need focus upon the incoming parameter, and the line where we issue a BeginEdit call against that order. Most of the remainder of the method deals with obtaining the order detail lines for display purposes.

public OrderDetailForm(Order order)
  {
   InitializeComponent();

   _order = order;

   this.readWriteAuthorization1.ResetControlAuthorization();

   this.Text = "Update freight for order number " + _order.Id.ToString();

   _order.BeginEdit();
   orderBindingSource.DataSource = _order;
   try
   {
    OrderLineList _orderLines = OrderLineList.GetOrderLines(_order.Id);
    if (_orderLines != null)
    {
     orderLineListBindingSource.DataSource = _orderLines;
    }
   }
   catch (Csla.DataPortalException ex)
   {
    MessageBox.Show(ex.BusinessException.ToString(),
    "Data load error", MessageBoxButtons.OK,
    MessageBoxIcon.Exclamation);
   }
   catch (Exception ex)
   {
    MessageBox.Show(ex.ToString(),
    "Data load error", MessageBoxButtons.OK,
    MessageBoxIcon.Exclamation);
   }
  }

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 SaveButton_Click(object sender, EventArgs e)
  {
   /* commit change, then make a new snapshot so that any further changes
    * are undoable.*/  
   orderBindingSource.EndEdit();
   _order.BeginEdit();
  }

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

  private void OrderDetailForm_FormClosing(object sender, FormClosingEventArgs e)
  {
   _order.CancelEdit();
  }

  private void orderBindingSource_CurrentItemChanged(object sender, EventArgs e)
  {
   SaveButton.Enabled = _order.IsValid;
  }
 }

The n-level undo was achieved via the main form's “cancel” button.

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

Deliverable: Security

The CSLA framework comes with two run time security options.

  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. The data portal should be set-up to disallow anonymous access.

  2. What Rocky calls “custom authentication”, where 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 only offer options which the user is authorised to use, and the data portal can double-check to protect against any UI weakness. The security checking in the data portal also allows our BO's to be used in environments where there is no UI, such as with web services.

I will focus upon the second of these two security options; “custom authentication “. The CSLA framework implements a role-based security facility that can be used as-is, or extended for our own specific needs. The framework has done the hard work of maintaining security once the user has been identified and authenticated. We can do whatever we like about the default implementation, but it is likely that we only need to tailor the authentication criteria and role identification mechanism. These areas will be unique for most people, and are fully exposed to facilitate tailoring without affecting the rest of the framework.

The framework provides for both object and field level security.

These two images show the same customer being displayed by two different users. Alan, on the left, is authorised to roles which allow update to both the Company and Contact name fields, but he is not authorised to view the order freight field. Stan's roles, on the right, have not authorised him to change the customer name, but he can change the contact name as well as the order freight column.

The framework allows us to encapsulate the authorisation business rules into the BO, and allows the UI to focus upon presentation issues. The framework is able to do more work for us with detail-level data-bound controls such as a TextBox, than it can for grids or non-bound controls such as buttons. Even so it is all fairly straight forward.

The following method includes elements of both object and field level authorisation. The customerOrdersDataGridView's visibility, as a whole, is conditioned upon whether the logged in user has authority to read the Order BO. This is an example of object level authorisation. There are equivalent methods for BO insertion, editing and deletion.

The field level authorisation is not so easy to spot in this method. The CSLA framework allows us to detect whether the logged in user has read and/or write rights for any specific BO property as we shall see shortly. We then use this information to condition the Visible and ReadOnly properties of our controls. This task is automated for our detail level data bound controls such as a TextBox. The framework includes a ReadWriteAuthorization control which can be dragged onto our form. This causes a new property to be appear against our form's controls (see hi lite property for the companyNameTextBox control in this diagram). We set the property to true on all of the controls whose UI we want the framework to control. The framework will now automatically control the visibility and editablity of these controls according to the rights of the logged in user. This facility does not extend to collection type controls such as grids, and does not extend to controls which are not data bound, such as buttons. These need to be controlled manually as we will see shortly in the SetupGridColumnUi method which is called by the following method.

private void SetupUiForLoggedInUser()
  {
   if (!CustomerGroupBox.Visible)
    CustomerGroupBox.Visible = true;

   if (_customer != null)
    SaveButton.Enabled = _customer.IsValid;

   /* sets up the ui properties of data bound controls. Doesn't handle
   * grids or buttons though. */
   this.readWriteAuthorization1.ResetControlAuthorization();

   customerOrdersDataGridView.Visible = Order.CanGetObject();

   /* the read property of the grid columns can only be set once this
   * user has accessed a customer which actually has orders to display.
   * This set up may have already been done, may be done at this time, 
   * or may be deferred until later, depending upon whether there is a 
   * customer currently on view, and whether that customer has orders. */
   if ((customerOrdersDataGridView.Visible)
    & (!_gridColumnRightsHaveBeenSetupForUser))
    _gridColumnRightsHaveBeenSetupForUser = SetupGridColumnUi();

   System.Security.Principal.IPrincipal user =
    Csla.ApplicationContext.User;

   if (user.Identity.IsAuthenticated)
   {
    this.toolStripStatusLabel1.Text = "Logged in as " +
    user.Identity.Name;
    logInOutButton.Text = "Log Out";
   }
   else
   {
    this.toolStripStatusLabel1.Text = "Not logged in";
    logInOutButton.Text = "Log In";
   }

  }

This next method shows how to condition the visibility and editability status of collection-level and non-data-bound controls. The CSLA is still working at high level for us. The UI does not need to concern itself with which roles the logged in user is authorised for, nor what the authorisation implications of those roles may be. These business rules are contained in our BO's. The UI is just told what the logged in user can, or cannot, do with a specific field.

/// <summary>
  /// Here we enforce the read and write permissions for the logged in user for each
  /// of the grid columns. This part of the UI set-up has been extracted from the rest 
  /// of the UI set-up as it needs to be called after the user has obtained his|her 1st 
  /// customer order. The set-up of the UI is deferred until this time as we need to 
  /// have a populated Order object before we can determine the user's permissions 
  /// for the order properties. 
  /// We should also remember that not all customers have orders, so this may
  /// not occur on the 1st customer viewed by the user.
  /// Return true once the grid properties have been set up for this User.
  /// </summary>
  private bool SetupGridColumnUi()
  {
   bool result = false;
   if ((_customer != null) && (_customer.Orders.Count > 0))
   {
    result = true;
    freightDataGridViewTextBoxColumn.Visible =
    _customer.Orders[0].CanReadProperty("Freight");
    if (_customer.Orders[0].CanWriteProperty("Freight"))
    {
    iDDataGridViewLinkColumn.Visible = true;
    iDDataGridViewTextBoxColumn.Visible = false;
    freightDataGridViewTextBoxColumn.ReadOnly = false;
    }
    else
    {
    iDDataGridViewLinkColumn.Visible = false;
    iDDataGridViewTextBoxColumn.Visible = true;
    freightDataGridViewTextBoxColumn.ReadOnly = true;
    }
    shippedDateDataGridViewTextBoxColumn.ReadOnly =
    !_customer.Orders[0].CanWriteProperty("ShippedDate");
   }
   return result;
  }

We are protected even if the UI mistakenly tries to allow an unauthorised user to read or update a field. The BO's property setters and getters include authorisation checks, eg. CanReadProperty(true). The true parameter will cause an exception to be raised if there is an attempted authorisation rules violation due to a UI shortcoming

public string CompanyName
  {
   get
   {
    CanReadProperty(true);
    return _companyName;
   }
   set
   {
    CanWriteProperty(true);
    if (value == null) value = string.Empty;
    if (!_companyName.Equals(value))
    {
    _companyName = value;
    PropertyHasChanged();
    }
   }
  }

My demonstration application has side stepped the task of setting up a security database as it adds nothing to points that this article is trying to make. I have hard coded the user ID's into the radio button text properties as all I am trying to show is how the framework supports the login process.

Here is the code within the Log In form. The OK and cancel buttons close the form via a modal result. The form has a public property to return the new user ID.

#region Fields and Properties
  private string _userId = string.Empty;

  public string UserId
  {
   get { return _userId; }
  } 
  #endregion



  public LogInForm()
  {
   InitializeComponent();
  }

  private void userRadioButton_CheckedChanged(object sender, EventArgs e)
  {
   _userId = ((Control)sender).Text;
  }

The main form calls the Log In form and handles the incoming user ID as follows. The ID, and usually the password, are handled to our security class. The call to SecurityPrincipal will log the user in, if the log in criteria are correct. In this case I am only using a user ID.

System.Security.Principal.IPrincipal user =
    Csla.ApplicationContext.User;

    using (LogInForm logonForm = new LogInForm())
    {
    if (logonForm.ShowDialog() == DialogResult.OK)
    {
     CslaWIIFM2.library.Security.SecurityPrincipal.Login(
      logonForm.UserId, "password not used in this simple demo");    
    }
   }

Here is the class diagram of the security class. My class is on the left. I have descended from the class in the middle which is part of the CSLA framework. The interface is part of .NET.

Below is the definition of my SecrurityPrincipal class. It provides implementation of the Log In/Out and role identification procedures, and uses my SecurityIdentity class to access the security database.

namespace CslaWIIFM2.library.Security
{
 [Serializable]
 public class SecurityPrincipal : Csla.Security.BusinessPrincipalBase
 {

 // constructor
  private SecurityPrincipal(IIdentity identity)
  : base(identity) { }

 public static bool Login(string username, string password)
 {
  SecurityIdentity identity = 
  SecurityIdentity.GetIdentity(username, password);
  if (identity.IsAuthenticated)
  {
  SecurityPrincipal principal = new SecurityPrincipal(identity);
  Csla.ApplicationContext.User = principal;
  }
  return identity.IsAuthenticated;
 }

 public static void Logout()
 {
  SecurityIdentity identity = SecurityIdentity.UnauthenticatedIdentity();
  SecurityPrincipal principal = new SecurityPrincipal(identity);
  Csla.ApplicationContext.User = principal;
 }

 public override bool IsInRole(string role)
 {
  SecurityIdentity identity = (SecurityIdentity)this.Identity;
  return identity.IsInRole(role);
 }

 }
}

The SecurityIdentity class authenticates the user against the security database. The database access would normally get the user's record by ID, from the table of users, and at the same time load in the list of roles which the user is authorised for from some kind of a UserRoles table. There would be similar ADO code to that shown in the Customer BO, where a customer and all it's order headers were loaded. To simplify this demo application I have hard coded the user id's and roles into a switch block.

[Serializable()]
 public class SecurityIdentity :
  ReadOnlyBase<SecurityIdentity>, IIdentity
 {
  #region Business Methods

  private bool _isAuthenticated;
  private string _name = string.Empty;
  private List<string> _roles = new List<string>();

  public string AuthenticationType
  {
   get { return "Csla"; }
  }

  public bool IsAuthenticated
  {
   get { return _isAuthenticated; }
  }

  public string Name
  {
   get { return _name; }
  }

  protected override object GetIdValue()
  {
   return _name;
  }

  internal bool IsInRole(string role)
  {
   return _roles.Contains(role);
  }

  #endregion

  #region Factory Methods

  internal static SecurityIdentity UnauthenticatedIdentity()
  {
   return new SecurityIdentity();
  }

  internal static SecurityIdentity GetIdentity(
  string username, string password)
  {
   return DataPortal.Fetch<SecurityIdentity>
   (new Criteria(username, password));
  }

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

  #endregion

  #region Data Access

  [Serializable()]
  private class Criteria
  {
   private string _username;
   public string Username
   {
    get { return _username; }
   }

   private string _password;
   public string Password
   {
    get { return _password; }
   }

   public Criteria(string username, string password)
   {
    _username = username;
    _password = password;
   }
  }

  private void DataPortal_Fetch(Criteria criteria)
  {
   /* Normally there would be verification against a security database here.
   * Probably check ID in Users table, and then get the list of user roles
   * from a roles table. */
   string userName = "";
   switch (criteria.Username)
   {
    case "Doris in Despatch":
    userName = criteria.Username;
    _roles.Add("Despatch");
    break;
    case "Alan in Admin":
    userName = criteria.Username;
    _roles.Add("Admin");
    break;
    case "Stan at Service desk":
    userName = criteria.Username;
    _roles.Add("ServiceDesk");
    break;
    case "Molly the Manager":
    userName = criteria.Username;
    _roles.Add("Admin");
    _roles.Add("Despatch");
    break;
    default:
    break;
   }

   if (userName != "")
   {
    _name = criteria.Username;
    _isAuthenticated = true;
   }
  }
  #endregion
 }

Deliverable: Scalability

Warning: !! This section of the article has not been updated from my CSLA V1 article yet, as I haven't had time to set up a virtual machine with .Net 2. The concepts and deliverables are still the same, however the configuration file settings may have changed with the new release.

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:

  • it's ServerName and ProcessID properties,as these are just standard property get accessors

  • it's overrides of the Object class' Equals, ToString and GetHashCode methods

  • it's constructor (empty, and private so that the UI cannot create our object without going through the data portal)

  • definition of the criteria class that would be used to identify our BO in cases where we were creating, changing or deleting it.

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

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 seventeen 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 a form which displays 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 a 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.


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. IF our application is to be distibuted there is also .Net Remoting or WCF (it's replacement).

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.

  • Best practice: There has obviously been a lot of work put into understanding .Net's capabilities. The CSLA framework has been designed to extend upon .Net, to give a rich framework for both the BO and UI programmers. The book does an excellent job of explaining the reasons behind the various design decisions, and the trade-offs that were involved.

  • Practical: The book goes beyond just theory, and illustrates the concepts by working through a project to build the CSLA framework. It then goes on to use the framework by developing some Business Objects, and then to use those same objects in desktop, web server, and then web service situations.

  • Real-world: The CSLA framework is not just some academic, lightweight framework-example that exists in just a book. It has been deployed by numerous people in a wide variety of situations (see links).

  • Continuous improvement: We can't be sure of “continuous” in the “forever” sense, but we can see that there has been an impressive effort so far. There have been 7 new versions since the VB flavour of the framework was originally published in June of 2003. We now have a major overhaul for .Net v2 as of .March 2006. The new versions which we have already received have implemented many new features as well as bug fixes. The areas of improvement so far include:

    a fundamental level of reworking to take full advantage of .NET 2 improvements such as better type safety and reduced boxing overhead (generics), much better data binding support (.NET's new BindingSource component, PropertyChanged event and bi-directional binding in ASP.NET), reduced overhead in multi-database situations (ADO.NET 2 promotable transactions)

    field level authorisation added to the existing object level authorisation facilities

    role based authorisation rules move from the UI into the BO

    remove dependancy upon .NET Remoting, and make ready for WCF (Windows Communication Foundation; codename Indigo; the future replacment for .NET Remoting). Microsoft haven't released this replacement for .NET Remoting yet, but Rocky already has a downloadable project demonstrating CSLA use against the WCF beta.

    the demonstration application has been extended to handle first-write-wins concurrency, also a nice multi-document SDI UI built using user controls, also to use the new .NET components such as the DataGridView and ToolStrip

    changes to better facilitate the use of code generators to build our BO's

    complete overhaul, and extension of,of the rules tracking facilities

    localization facilities

    enhancement to date handling capabilities

    enhancement to exception handling capabilities

    handle .NET v1.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 April 2006, reports 3183 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.

As of May 2006 the forum has a new home as the previous platform had issues. At the point of writing people are still migrating and enrolling into the new forum.

The forum archive is accessible by a content search facility. As of April 2006 there are over 260,000 posts from over 4,700 threads in there, all relating to CSLA thanks to the housekeepers. 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)

  • Chris Denslow has created a CSLA reference web site which acts as an on-line help facility for the framework. Chris has both C# and VB sections in his site.

  • Peter Kozul has developed a complementary extension to the framework, which implements Observer and Publish/Subscribe design patterns to strengthen the notification between parents and their children. It also implements his own custom binding allowing applications to validate business rules on a per key stroke basis, rather than at loss of focus as with standard binding.


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.

If you have any corrections, comments or suggestions regarding this article or it's associated demonstration program, you can contact me via this site's feedback facility.



Links:


      

Copyright 2005 by Primos Computer Services   Terms Of Use  Privacy Statement