|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionThis is the first article in a series of three which will explain step-by-step how to implement UIPAB Version 2. The benefits of UIPAB will be demonstrated by firstly examining the non UIPAB approach. Also, UIPAB will be demonstrated within the broader context of Patterns and Practices and .NET application Blocks. BackgroundAn understanding of the ESP Layered pattern is advantageous. The code relies on various App Blocks, so an understanding of DAAB (Data Access App Block v2), CMAB (configuration Management App Block) and EMAB (Exception Management App Block) will be helpful. Using the codeThe code download consists of a SQL script to set up the database and four C# projects: UIPABBE (contains the Business Entity classes); UIPABData (contains the Data Access Logic Classes which utilize the DAAB); UIPABWin1 (which which the basic forms and stub code - this application does not work but is used as the basis for all subsequent working applications in this series); UIPABWin2 (a windows application that does not use UIPAB).
OverviewI recently delivered a talk for http://www.sadeveloper.net/ at Microsoft South Africa’s offices in Johannesburg. The audience comprised of Johannesburg’s more informed and talented .NET developers. I asked members of the audience if anyone had used the User Interface Process Application Block (UIPAB) and no one raised his or her (there were only three hers in the audience) hand. My talk was an hour and in that time I had to give a rushed explanation of UIPAB. My aim was to show the benefits of using the Patterns and Practices Application Blocks and my basic message was that the App Blocks takes .NET to an entirely new level of versatility, sophistication and, in my eyes, elegance. In the little time I had at my disposal, and judging from the responses I have subsequently received, I think I got my message across. I managed to convince my audience that App Blocks, and indeed the entire PP initiative, is crucial to serious, high-powered .NET development. Unfortunately, because of time constraints, I was unable to explain the delicate mechanics of UIPAB implementation in sufficient detail so as to allow my audience to go away and begin experimenting with the UIPAB. Some in the audience admitted that they had looked at the UIPAB and had tried to master it with the supplied literature, but found the task too daunting or the help file too complicated or too technical for quick practical implementation. Aside from the .PDF manual and the Help file supplied with the download, there is no other UIPAB literature. The documentation is thorough, but clearly too thorough for the novice or too dense for the time-constrained, dead-line-pressurized developer. The UIPAB, like the other App Blocks, comes with a wealth of examples, unfortunately, none of these examples are documented and you are forced to deconstruct the code on your own – for a new paradigm, a daunting task in and of itself. What is needed, therefore, is a step by step approach which will slowly introduce the potential UIPAB user to the benefits of using the App Block. These benefits can be shown using the minimum features of UIPAB, especially when we can show how an old style front end can be refactored using the UIPAB. While it is important to understand the UIPAB under the hood, such an in-depth understanding is not essential to start working with it. The documentation is by and large devoted to under-the-hood detail, and this is probably the reason for the general reticence I have encountered in using it. What I propose to do in this series of articles on the UIPAB is a step-by-step guidance to using the UIPAB. It is important to understand that the UIPAB is part of the PP initiative. I think it is wrong to study the UIPAB in isolation from the overall PP picture. This first article will therefore attempt to place UIPAB within the context of PP. I will briefly touch on the ESP .NET Layered Pattern and demonstrate what I think is the right approach in developing the Business and Data layers. I think that the true power of the App Blocks is unleashed when they work together in the same application, and so I will include the Data Access App Block (DAAB), the Configuration Management App Block (CMAB), and the Exception Management App Block (EMAB) in my Business and Data layers. I will then present the simple WindowsForm demo-example I will be using. I will show the various forms, controls and work-flow of the demo which will be common to all versions of the demo I will canvass in this series. Then I will present a non-UIPAB version of the demo where I will point out the drawbacks of using this old way of doing the UI. In next article I will introduce the MVC pattern and show how this pattern attempts to solve the problems encountered in the old way of doing things. Also I want to emphasize that an appreciation of MVC is central to understanding the UIPAB. I will then examine the UIPAB using a step-by-step didactic approach instead of the detailed, technical approach found in the UIPAB documentation. I want to give an overview of the UIPAB objectives, its basic building blocks and a brief checklist on how to start developing simple UIPAB UI’s. I will then refactor the original WindowsForm application presented in the previous article this time using UIPAB. Lastly, I will show how the same refactored code can be reused in an ASP.NET application. In this first example I will be stripping UIPAB of all its detail and focusing on its core, basic features. In the third article, I will build on the example we used to show UIPAB in more detail. Whereas the first example will deal with a single UI Process or Task (basically a Use Case), the second example will deal with two UI Processes and show how they can be linked. Also, the first example will not deal with snap-shot State persistence, the second example will. The last article in the series will again refactor my example so as to demonstrate the various navigational abilities of UIPAB. The other examples use the Navigation Graph; the final example will show the use of all the UIPAB navigational capabilities. Also, in this final article, I will briefly discuss customizing the UIPAB. I suggest at this stage that you download the UIPAB from here if you have not done so already and install it on your development machine. You will need an installation of .NET Framework 1.1 and Visual Studio .NET 2003. THE ESP .NET LAYERED PATTERNThe first sentence of Chapter 1 of Fowler’s Patterns of Enterprise Architecture is a succinct description of what layering is: “Layering is one of the most common techniques that software designers use to break apart a complicated software system” It is important to stress what Layering it is not. A “layer” does not refer to a physical entity or separation. The term used for referring to such a physical entity is “tier”. A “multi-tiered” system is one which comprises a number of physical tiers. A “layer” therefore is a logical or conceptual software entity. A layer is deployed to one or more tiers. I have dealt with the .NET Enterprise Solutions Pattern adoption of the layered pattern in my article The Microsoft Patterns & Practices Initiative: Part 2 – The Layered Pattern and I refer interested readers to that article for more detail. (The article can be downloaded from http://www.sadeveloper.net/ or http://www.saarchitect.net/ or from http://www.pdev.co.za/ under Resources then Ayal’s Articles). To summarize: There are three main layers each with its own sub-layers as follows:
A Visual Studio Application Architecture Template Policy has been released that enforces the ESP layered pattern within the VS.NET 2004 IDE. The Template Policy can be downloaded from: http://www.pdev.co.za/ under Resource then Architecture (registration is required). I will be using this Template in all the examples. (I am unable to find the original URL at http://www.gotdotnet.com/ where this policy was originally released). THE SAMPLE APPLICATIONFor this first article I have deliberately chosen a simple example. All the sample application does is display a list of persons. The user can then add new persons to the list or update the details of an existing person. The person’s details are also simple: each person has a first name and last name; furthermore, each person can have one or more addresses or contact numbers associated with him. The sample application contemplates a future refactoring into a Service Oriented Application which will use the Offline Application Block, so I have tried to cater for this future development into the architecture from the start. When the application starts up, a list of all existing persons are retrieved from the data-store. This list is presented to the user where it is persisted. The user can now add new persons to the localized list; if the user updates an existing person already in the list, the application checks whether the selected person’s details have been retrieved from the data store; if the details have not been retrieved, these details are retrieved and presented to the user; if the details have already been retrieved, the person is presented to the user for editing. Only when the user has finished working with the list of persons, is the entire list sent back to the data-store. Newly inserted persons are registered with the data store; persons whose details have been updated are updated on the data-store. The architecture therefore is one of an initial batch-down load; working locally on the UI and then a batch up-load to the data-store again. This is a disconnected paradigm which aims to keep data-access to the minimum. The example does not deal with concurrency or transactional issues.
The demo application will make use of a number of Application Blocks( Data Access, Configuration Management and Exception Management). I will not be discussing the mechanics of these other App Blocks but I thought it appropriate to include them in this demo. I repeat: It is my belief that the true power of the App Blocks is enhanced when you begin to use them together and it is best to start using this technique from the outset. (Hopefully – time and energy permitting – I will have occasion to examine these App Blocks in other articles.) THE DATA STORAGEThe Data Schema is fairly straight forward. There are three tables: Person, Address and Contact – each with a Primary Key as an Identity Field.
There are three types of stored procedures defined for each table:
The Person table has an extra stored procedure which retrieves a list of all rows in the table.
The SQL Script to set up the data-base schema and the stored procedures is supplied in the download. The application assumes a SQL authentication mode for database access. The specific of the data-base access string will be configured using the Configuration Management App Block (CMAB) – so you don’t have to worry about it at this stage. THE BUSINESS LAYERBecause the sample is a pretty simple application, there are no real business rules or processes involved. I am therefore only going to be developing the Business Entity sub-tier. There is a lot of discussion about what is the best way to develop .NET business entities within the context of the ESP Layered pattern. For a thorough treatment of the subject I refer you to the following:
My approach is derived from two overriding considerations: Firstly, like Martin Fowler, I am an object bigot and therefore tend to insist that that my business entities adhere to strict OO rules. Secondly, I am also a Layered Fundamentalist which means I try to stick to the ESP Layered Pattern as far as possible. Being an object bigot, there can be no place for ADO.NET classes and structures in my business entities i.e. no room for datasets, data tables, data rows or data relations. Being a Layered Fundamentalist, my business entities should not be doing what my other Business Layer sub-layers (Business Components and Business Workflows) should be doing. Practically speaking this means that my business entities are classes with attributes (properties) and nothing much more. Where operations (methods) are required, they are simple, straight-forward and serve merely to qualify a property. Business process, rules and workflows belong in the other sub-layers. This approach is conducive to reuse of my business entities; if the business rules were embedded into them, their reuse would be precluded. By being a Layered Fundamentalist I can reuse my business entities even when the business rules change. In the real world, this is what happens: the business entities are more-or-less static but the business rules are constantly changing. All bigots are disadvantaged and object bigots are no exception. Because my business entities are pure they have no trace of ADO.NET components i.e. no DataSets, DataTables, DataRows and DataRelations, which also means no seamless ability to serialize into XML. Furthermore, whenever I access a relational data store to populate my business entity or update the data store with my business entities data, I will have to go through a long and cumbersome Object-Relational-Mapping (ORM) exercise. To add to my problems, at the end of the day, I want to display my business entities to the end-user, without ADO.NET the rich feature set of data-binding is not as readily available to me. So bigotry comes at a cost. The advantage of strict OO, however, far outweighs the disadvantages. The purist approach is aesthetically superior. When you mix models (relational with OO) the code tends to get messy and is inelegant. Also, the more complex the demands made on your business entities, the more you will come to realize that the short-term benefits of a hybrid can turn into a trap. If there is anything I have learned as a developer, it is that elegance and aesthetics is the best guarantee of longevity and performance. All my business entities inherit from a base business entity class:
The BaseBE encapsulates properties that are common to all business entities. There are four properties and one protected method. The method, The four properties are as follows:
namespace UIPABBE
{
[Serializable]
public abstract class BaseBE
{
private string id;
protected bool valid, dirty, isnew;
public BaseBE():this(0){}
public BaseBE(int id)
{
this.id = id.ToString();
this.dirty = false;
}
public string ID
{
get{return id;}
set{id = value;}
}
public bool Valid{get{return valid;}}
public bool isDirty
{
get{return dirty;}
set{dirty = value;}
}
public bool isNew
{
get{return isnew;}
set{isnew = value;}
}
protected abstract void validate();
}
}
As you can notice the BaseBE is marked with the The sample Business Entity layer consists of three simple classes, each representing a table in the data base: Person, Contact and Address. The classes are available in the source code of the UIPABBE project.
We will examine the Person business entity to get an understanding of the basic mechanics of what a business entity object does. using System;
using System.Collections;
namespace UIPABBE
{
[Serializable]
public class Person: BaseBE
{
#region PRIVATE FIELDS
private string first, last;
private ArrayList contact = new ArrayList();
private ArrayList ad = new ArrayList();
#endregion
#region CONSTRUCTORS
public Person():this(0, "", ""){}
public Person(int id, string first, string last):base(id)
{
this.first = first;
this.last = last;
validate();
}
#endregion
#region PROPERTIES
public event EventHandler FirstChanged;
public event EventHandler LastChanged;
public string Firstname
{
get{return first;}
set
{
if(this.first != value)
this.dirty = true;
first = value;
if(FirstChanged != null)FirstChanged(
this, EventArgs.Empty);
validate();
}
}
public string Lastname
{
get{return last;}
set
{
if(this.last != value)
this.dirty = true;
last = value;
if(LastChanged != null)LastChanged(this,
EventArgs.Empty);
validate();
}
}
public ArrayList Contacts
{
get{return contact;}
set{contact = value;}
}
public ArrayList Addresses
{
get{return ad;}
set{ad = value;}
}
#endregion
#region OVERRIDES
public override string ToString()
{
return this.Firstname + ", " + this.Lastname;
}
protected override void validate()
{
if(this.Firstname.Length > 0 &&
this.Lastname.Length > 0)
this.valid = true;
else
this.valid = false;
}
#endregion
}
}
Pay attention to the following features:
THE DATA LAYERAccess to the data storage is strictly through the Data layer and the Data Access Logic Components (DALC) sub-layer to be more precise. The DALC layer utilizes the Data Access Application Block (DAAB v2) and so the DAAB should be referenced. Also, the DALC layer will be receiving and retrieving business entities, therefore the business entity project must also be referenced. (According to the Team Developer P&P guideline, best practice is to reference the project and not the specific project .DLL). Strictly speaking the DAAB is a DALC best-practices implementation. So the project I am going to create in the DALC layer is a project-specific façade to the DAAB. (Again, the enterprise version of the DALC will be different from the version I am going to present here. By and large, the main difference is to centralize common behaviour in a base class and then make sure that all the DALC classes inherit for this base class.). In the current version, I created a Data Access class for each business entity. The idea is to isolate data-access on a per-table, per-entity basis. Because the data store is relational, this sort of strict isolation is impossible; it is here where the relational world unavoidably encroaches on the OO world. (In a later series I intend to show how products like Matisse – which is a post relational data base with a .NET binding – can avoid this conundrum.) Where the data access is from a “join-table” i.e. a table which contains elements from two other tables, I place the data access code in the data access entity class which is predominant in the operation. For example: when I have to retrieve a person’s contact details, the predominant data that is retrieved is the contact information and so the Accordingly there are 3 Data Access classes: using System;
using System.Collections;
using System.Data;
using System.Data.SqlClient;
using UIPABBE;
using Microsoft.ApplicationBlocks.Data;
namespace UIPABData
{
public class AddressData
{
private static string con = DataConString.ConString();
public static ArrayList getPersonAddresses(string id)
{
ArrayList list = new ArrayList();
string proc = "getAddressesByPerson";
SqlParameter[] sparams = new SqlParameter[]{
new SqlParameter("@id", int.Parse(id))};
DataSet ds = SqlHelper.ExecuteDataset(con, proc, sparams);
foreach(DataRow dr in ds.Tables[0].Rows)
list.Add(new Address((int)dr["ID"], dr["Street"].ToString(),
dr["Province"].ToString(), dr["Country"].ToString()));
return list;
}
public static string insAddress(Address add, string id)
{
string proc = "insAddress";
SqlParameter[] sparams = new SqlParameter[]{
new SqlParameter("@personid", int.Parse(id)),
new SqlParameter("@street", add.Street),
new SqlParameter("@province", add.Province),
new SqlParameter("@country", add.Country)};
return Convert.ToString(SqlHelper.ExecuteScalar(
con, proc, sparams));
}
public static void upAddress(Address add)
{
string proc = "upAddress";
SqlParameter[] sparams = new SqlParameter[]{
new SqlParameter("@id", int.Parse(add.ID)),
new SqlParameter("@street", add.Street),
new SqlParameter("@province", add.Province),
new SqlParameter("@country", add.Country)};
SqlHelper.ExecuteNonQuery(con, proc, sparams);
}
}
}
Pay attention to the following:
You will notice two extra classes in the DALC layer: the THE PRESENTATION LAYERNow that I have set up the Business and Data Layers, I can now concentrate on the Presentation Layer. The end user (and probably your managers and bosses) see and judge your application from the perspective of the Presentation Layer. From my experience, no matter how much I try talk myself and my team into saying that look and feel is not important, I have found that my projects are ultimately judged by the end-user experience, and they make their judgment on look, feel, intuitive work-flow and ease of use. As serious developers we might feel a bit cheated in having our work judged by what we think as the most inconsequential part of the application – the front end. But that is the way most of the world will judge us. The Layered Pattern can help us achieve our objective in this regard. It mandates that we develop our Business and Data layers independently from our front ends; it also envisages less change to these layers than to the Presentation layer. When we come to the Presentation Layer, we should not worry about business and data concerns; this will allow us to concentrate our skills, talent and time into developing pretty, intuitive and user-friendly user interfaces. Also, because the UI is independent of the other layers, we can easily change our UI’s without having to change our business and data access code. The Presentation Layer is a sub-system in itself, with its own workflows, data requirements and business rules and processes in addition to look and feel. For example: Form A must launch Form B and From B must return us to the Main Form – this navigational route is workflow specific to the Presentation Layer. Without the UIPAB, we have to embed these UI specific business requirements into the UI code. As we will see, this is severely limiting and goes a long way to defeat the purpose of the Layered pattern, which we have so faithfully adopted in the Business and Data layers. In the remainder of this article and the next article, I propose to do the following:
THE DEMO UIThe Demo UI elements can be found in the source code as UIPABWin1.proj - this application is not intended to work at this stage. The project comprises of 4 Windows forms: Form1, frmAddress, frmContact, frmList, frmPerson. Throughout all versions of the UI, we will be sticking to these 4 basic UI elements without changing their look or their constituent controls.
The UI work-flow is as follows:
In UIPABWin1, I have hooked up all the UI events. These events will trigger and control the work-flow, and will be consistent throughout all the UI variations we will be examining.
In summary UIPABWin1 does the following:
As I move onto the demo version without the UIPAB, you will notice that I am forced to add a lot of code and extra procedures to the basic outline I have set up in UIPABWin1. When I do the version with UIPAB, you will notice that I return back to the basic skeleton code of UIPABWin1, and all I will be doing is adding a few lines of code in the event handlers. THE UI WITHOUT UIPABUIPABWin2 is the demo version without the UIPAB. You will find it in the source code download as UIPABWin2.proj. UIPABWin2 is a fully working application. In fact, you will see when we demonstrate the UIPAB version of this application, that both versions work exactly the same and look exactly the same. The code and structure is vastly different. Befoe you run UIPABWin2 make sure you configure the config. file with the correct SQL Data Access information. In the config file locate the XMLConstring XML element. This element has a child element DataConString. Which in turn has the child elements: Server, UID, DB and PWD. You will have to enter the correct UID and PWD. The UID and PWD should reflect a valid SQL User name for SQL authentication to the UIPAB database. UIPABWin2 has added references, code, new variables and methods to each form. The purpose of all these additions is to achieve the following:
I don’t want to labour the point by examining all the code in UIPABWin2. For the purposes of illustration, I will deal with frmList only. You should examine the rest of the code in the other forms on your own, and you will understand the point I am trying to make. First off note the project references:
The code in Form1’s Button click event handler is as follows: private void btnStart_Click(object sender, EventArgs e)
{
//Navigation Management embedded UI
Form frm = new frmList(this);
frm.Show();
this.Hide();
}
This is navigational code. It is navigating the application from Form1 to frmList. I am passing Form1 to frmList; this is also for navigational purposes. When frmList is dismissed, it will now know to navigate back to Form1. frmList has two class level variables: //********************************
//State management in UI
private ArrayList list;
private Form1 frm;
//********************************
These are for state and navigational management. frmList has two extra methods in addition to the event handlers. private void bind()
{
//Accesses DALC layer
list = PersonData.getPersonsList();
lb.DataSource = list;
}
//*********MODEL & WORKFLOW BEHAVIOUR*************
internal void upPerson()
{
CurrencyManager man = this.BindingContext[list] as CurrencyManager;
if(man != null) man.Refresh();
}
//************************************************
The The new Person button event handler in frmList looks like this: private void btnNew_Click(object sender, EventArgs e)
{
//**********MODEL, WORK FLOW & STATE ********************
Person person = new Person();
person.ID = Guid.NewGuid().ToString();
person.isNew = true;
this.list.Add(person);
Form frm = new frmPerson(this, person);
frm.ShowDialog(this);
//******************************************************
}
In this code snippet, I am doing business processing, state management and navigation. I first create a new person object, assign it a new GUID and mark it as new – this is business processing embedded into the UI. I then add the new person to my class level When an existing person is selected in frmList’s private void lb_DoubleClick(object sender, EventArgs e)
{
if(lb.SelectedItems.Count == 0)return;
Person person = (Person)lb.SelectedItems[0];
//***************WORK FLOW & STATE
if(!person.isNew)
{
if(person.Addresses.Count == 0)person.Addresses =
AddressData.getPersonAddresses(person.ID);
if(person.Contacts.Count == 0)person.Contacts =
ContactData.getPersonContacts(person.ID);
}
Form frm = new frmPerson(this, person);
frm.ShowDialog(this);
//************************************
}
The first line checks whether we have selected an item in the list; if we have not we return from the method. The next line extracts the person object from the item selected. Both these lines of code belong in the UI. None of the rest of the code in this method should be in the UI. I next check if the Person is not new; if he is not new, I check if I have retrieved his details from the data store; if I have not, I retrieve his contact and address details. This is business logic and data access logic code embedded into the UI. I then instantiate a new frmPerson, pass it a reference of frmList and the extracted person object and navigate to frmPerson. This is state management and navigation embedded into the UI. frmList’s btnEnd Click event handler is as follows: private void btnEnd_Click(object sender, EventArgs e)
{
//***********USES DATA LAYER***************
PersonData.upList(list);
frm.Visible = true;
this.Close();
//*****************************************
}
The code first calls the Data Layer and initiates the crucial back end process of updating the data base. Next, I show the referenced Form1 form and close frmList. Business logic and navigation is here embedded in the UI. CONCLUSION & WHAT’S NEXTIn this first article we have barely touched on the UIPAB. Instead, I have coded the Business and Data Layers which are going to be reused throughout. I have used a number of PP App Blocks in these layers, with the purpose of getting you used to using the App Blocks together from the start. I have given the basic UI elements and work-flow and the objective of the demo application. I then created a Windows Application Demo UI without using the UIPAB to demonstrate how domain logic and user process logic is embedded into the UI. In the next article I will be examining the overall objective of the UIPAB and the basic UIPAB building blocks. The purpose will not be to give a thorough technical analysis (the App Block documentation does this better than I could) but, rather, to equip you with the basic knowledge and techniques to begin using the UIPAB. I will then refactor the Windows Demo from this article using UIPAB. Lastly, we will reuse the refactoring to build an identical Asp.Net Web UI. In later articles I will delve into more features and details of the UIPAB. I realize that this has been a particularly round-about, and long-winded way to introduce UIPAB. But, I think it should be introduced gradually. UIPAB is not for the feint hearted and if my goal is to make it the standard for all .NET UI development, I have to lay out may case carefully.
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||