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