Introduction
While the Part I article of this series demonstrated the use of an Automator
class to auto populate Windows Forms; It just touched upon the concept of separating disparate code into multiple tiers. In this article, we will walkthrough a Account Registration application which will demonstrate reuse of code by conceptualizing some of the various tiers described in my previous article.
NOTE: The code snippets provided in this article are algorithmic bare bones of the actual code to reduce the article complexity. Readers should refer to the source code provided for more details. The source code provided is well documented and self explanatory.
The downloadable source code provided includes the example application. I am using Visual Studio .NET 2003 with Framework SDK 1.1 to create this example.
The Example Application Requirements
The ultimate goal of this exercise is to capture and store registration information via a user interface presented as a Windows Form or ASP.NET Web Form. The registration information consists of the following fields...
Username
- The account username to be used for logging into the system (Required).
Password
- The password that is associated with the Username (Required).
Street
- The street address.
Country
- The country of residence (Required).
The user will enter necessary information on the fields provided by the presentation interface and then click on a "Register" button to request for an account creation. The fields marked as Required are mandatory and have to be filled in. The username entered has to be unique for an account. If the username entered already exists, the user is notified by displaying an appropriate error message. The request for a new account will be declined if the above conditions are not met and the errors will be reported by the application via the user interface. If all the required conditions are met, the information entered will be stored in a persistent data store such as an RDBMS database. The user will be notified after being successfully registered into the system.
Defining the Tiers
Let us start by conceptualizing the various tiers to be defined for our application by analyzing the functional requirements. The Windows Form UI is created as a Windows Application (Win project) while the Web Form UI is created as a ASP.NET Web Application (Web project). The code that performs business logic should be extracted out into a common library (Lib project).
Utility Classes
The Automator
class used in my previous article used reflection to auto-populate the form controls. Since the automators are specific to the UI being populated, we need to create separate automator modules for Windows and Web forms.
namespace Utility
{
public interface IState
{
void Reset();
}
public interface IAutomator
{
void Store();
void Restore();
}
public abstract class AbstractAutomator : IAutomator
{
private IState state;
public IState State
{
get {return this.state;}
}
}
}
namespace Win
{
public sealed class WinAutomator : AbstractAutomator
{
public override void Store();
public override void Restore();
}
}
namespace Web
{
public sealed class WebAutomator : AbstractAutomator
{
public override void Store();
public override void Restore();
}
}
We will create helper classes for getting or setting the value of a property on an object using reflection since this code will be shared by the above Automators. Two classes are used for this purpose viz. ReflectHelper
and PropertyHelper
.
public sealed class ReflectHelper
{
public static void GetProperty(object obj, string name)
{
public static void SetProperty(object obj, string name, object val)
{
public static void Populate(object obj, IDictionary dict)
{
public static void Extract(object obj, IDictionary dict)
{
}
public sealed class PropertyHelper
{
public static void GetProperty(object obj, string name)
{
public static void SetProperty(object obj, string name, object val)
{
}
The PropertyHelper
extends the Reflection technique to be used with nested properties e.g. Let us consider the case where the Registration
class is associated with an Address
. The code snippet below highlights some of the interfaces and their corresponding implementations (Value Objects) which are used to store the state of the UI.
namespace Services
{
public interface IAddress {
public interface IAccount {
public interface IRegistration : IAccount
{
}
namespace Lib
{
public class Address : IAddress {
public class Account : IAccount, IState
{
public class Registration : Account, IRegistration
{
}
Let us assume that the instance of Registration
class is assigned to a variable reg. The controls on the UI can be mapped to properties on the reg by using the naming convention as shown below. Nested properties are separated by an underscore "_" character as using a period "." for naming a UI control is disallowed.
Registration reg = new Registration();
Control Name/ID |
Property Mapping using reg as a reference |
Username |
Username |
Password |
Password |
Addr_Street |
Addr.Street |
Addr_Country |
Addr.Country |
ReflectHelper.SetProperty(reg, "Username", "Popeye");
ReflectHelper.GetProperty(reg, "Username");
ReflectHelper.SetProperty(reg, "Addr_Country", "India");
ReflectHelper.GetProperty(reg, "Addr_Country");
The ValidationHelper
and ValidationException
utility classes provide methods for validating user input.
public sealed class ValidationHelper
{
public static void ValidateRequired(string str)
{
}
Client Tier and Presentation Tier
The Presentation tier components are closely related to the Client tier which suggests that the Presentation tier will consist of two separate code bases viz. Windows Application (Windows Forms) and Web Application (ASP.NET Web Forms).
A Windows Form is created using controls from the System.Windows.Forms.dll. All the code that uses this DLL is grouped under the Win
namespace. This includes the WinAutomator
class.
using System.Windows.Forms;
namespace Win
{
public class WinForm : System.Windows.Forms.Form
{
public WinForm() {
private void RegisterButton_Click(object sender, System.EventArgs e)
{
try
{
catch(ValidationException x)
{
ProcessErrors(this, x.Errors);
}
}
private void ProcessErrors(Control control, IDictionary errors)
{
if(control.Name != null)
{
if(errors != null && errors.Contains(control.Name))
{
else
{
}
}
}
}
An ASP.NET Web Form is created using controls from the System.Web.dll. All the code that uses this DLL is grouped under the Web namespace. This includes the ErrorProvider
and WebAutomator
classes.
using System.Web.UI;
namespace Web
{
public class WebForm : System.Web.UI.Page
{
public WebForm()
{
this.ID = "WebForm";
}
private void RegisterButton_Click(object sender, System.EventArgs e)
{
try {
catch(ValidationException x)
{
ProcessErrors(this, x.Errors);
}
}
private void ProcessErrors(Control control, IDictionary errors)
{
if(control.ID != null)
{
if(errors != null && errors.Contains(control.ID))
{
else {
}
}
}
}
Notice the striking similarity in code for the above two cases. The programmer can now write consistent code for either of the two presentation libraries; Thanks to the common library and multi-tiered strategy approach.
Services Tier
The question to be asked here is "What are the services that the application needs to deliver?" The following code snippet will define the interfaces for the services to be provided by the application.
namespace Services
{
public interface IRegistrationService
{ void Register(IRegistration registration); }
public interface IAuthenticationService
{ void Authenticate(IAccount account); }
public interface IServiceFactory
{
public IRegistrationService RegistrationService
{
get;
}
}
}
namespace Lib
{
public sealed class ServiceFactory : IServiceFactory
{
private AccountManager acctManager =
new AccountManager();
public static IServiceFactory GetInstance()
{
public IRegistrationService RegistrationService
{
get { return acctManager; }
}
}
}
The services to be provided are implemented by a class or group of classes in the Business tier which in our example is Lib.AccountManager
. The Presentation tier will communicate with the application through the Services tier by using the services defined here.
Business Tier
This tier comprises of classes that perform business validation and execute transactions. They communicate with the classes in the Integration tier if necessary to execute business logic and persist data captured by the UI.
namespace Lib
{
public sealed class AccountManager :
IRegistrationService, IAuthenticationService
{
public void Authenticate(IAccount acct)
{
public void Register(IRegistration registration)
{
}
}
Integration Tier
The classes that access back-end resources such as a RDBMS database constitute the formation of this tier. This example provides stubs as shown in the code snippet below to simulate the actual behavior by using an in-memory Hashtable
as a replacement to the actual database. These classes communicate with the Resource tier to persist or retrieve data.
namespace Dao
{
public abstract class AbstractDao
{
public class AccountDao : AbstractDao
{
public bool AccountExists(string username)
{
}
public class RegistrationDao : AccountDao
{
public void Register(IRegistration registration)
{
}
}
Resource Tier
Any resource which can persist data such as a RDBMS database or data store e.g. Microsoft SQL Server or Oracle constitutes in the making of this tier. Data stored in this tier is used by the Business tier via the Integration tier to perform the necessary business logic.
Summary
The tiers described above can be summarized as follows.
- Client Tier - Windows Application and Web Application.
- Presentation Tier - Windows Forms (
Win
namespace) and ASP.NET Web Forms (Web
namespace).
- Services Tier -
Services
namespace consisting of interfaces for Value Objects in the Business tier and services provided by the application.
- Business Tier -
Lib
namespace consisting of AccountManager
which provides implementation for the services mentioned above in the Service tier and Value Objects such as Account
, Registration
etc. which encapsulate the UI state.
- Integration Tier -
Dao
namespace consisting of Data Access Objects which communicate with the Resource tier. Stubs of such classes are provided.
- Resource Tier - Any data source such as Microsoft SQL Server. Not used in this article to avoid complexity.
- Utility Classes -
Utility
namespace consisting of an abstract Automator
and Helper classes.
Test the Application
The source distribution consists of four separate projects with dependencies as shown in the table below.
Utility
- The Utility class Library (Utility namespace).
Lib
- The Services interfaces, Business Logic and Data Acess Library (Services, Lib and Dao namespace).
Win
- The Windows Application (Win namespace).
Web
- The ASP.NET Web Application (Web namespace).
Create a Visual Studio.NET Solution and add the above projects to the solution.
Project Name |
Dependencies |
Utility |
|
Lib |
Utility |
Win |
Lib, Utility |
Web |
Lib, Utility |
Create a virtual directory Web for running the ASP.NET Web Application. Type http://localhost/Web/WebForm.aspx in the browser Address Bar to launch the web application. Windows application can be started from the Win.exe file found in the Win\bin\Release directory. Use the steps mentioned below to test the application (ASP.NET Web or Windows).
- Click on Register button without filling in any information - Validation error for required fields.
- Fill in all the required information and click on the Register button - Registration successful.
- Fill in the same information as above without ending the session and click on the Register button - Validation Error for duplicate account.
Possible Future Enhancements
The ErrorProvider
for ASP.NET Web Forms can be re-written as a custom control (Inheriting from System.Web.UI.Control) similar to the one for Windows Forms providing consistent behavior in both the presentation environments.
Reflection can be used to populate objects in the Integration tier by mapping the property names (including nested properties) to the column names on the database tables. The common functionality in the Resource tier can be extracted out into a reusable framework library.
The ValidationHelper
can be replaced by a validation framework which can be reusable across application boundaries.
Auto-population of UI forms can also be accomplished using custom attributes as suggested by S�bastien Lorion in response to my previous article.
An ASP.NET Web Service can provide the application functionality through a Windows or Web client similar to the one that we have developed.
References/Links