Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Coding in Tiers - Part II

0.00/5 (No votes)
24 Sep 2003 1  
Reuse code in Windows and Web forms

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.

// Utility Classes

namespace Utility
{
    public interface IState
    {
        void Reset();   // Method declaration to reset state

    }

    public interface IAutomator
    {
        void Store();   // Method declaration to store UI state

        void Restore(); // Method declaration to restore UI state

    }

    public abstract class AbstractAutomator : IAutomator
    {
        private IState state; // UI State

        public IState State
        {
            get {return this.state;}
        }
    }
}

// Presentation Tier (Windows)

namespace Win
{
    // Automator for auto-populating Windows Forms

    public sealed class WinAutomator : AbstractAutomator
    {
        public override void Store();   // Override to store UI state

        public override void Restore(); // Override to restore UI state

    }
}

// Presentation Tier (Web)

namespace Web
{
    // Automator for auto-populating ASP.NET Web Forms

    public sealed class WebAutomator : AbstractAutomator
    {
        public override void Store();   // Override to store UI state

        public override void Restore(); // Override to restore UI state

    }
}

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) 
      { // Uses Property Helper GetProperty(object, string) }

    public static void SetProperty(object obj, string name, object val) 
      { // Uses Property Helper GetProperty(object, string, object) }

    public static void Populate(object obj, IDictionary dict) 
      { // Populate obj using name-value pairs in the dict }

    public static void Extract(object obj, IDictionary dict) 
      { // Populate dict with properties on the obj }

}

public sealed class PropertyHelper
{
    public static void GetProperty(object obj, string name) 
      { // Return value of the named property on the obj }

    public static void SetProperty(object obj, string name, object val) 
      { // Set value of the named property on the obj }

}

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.

// Value Object interfaces

namespace Services
{
    // Address data

    public interface IAddress { // Declares Street property }


    // User account data

    public interface IAccount { // Declares Username property }


    // Registration data

    public interface IRegistration : IAccount 
      { // Declares Addr property }

}

// Common Library

namespace Lib
{
    public class Address : IAddress { // Implememts Street property }


    public class Account : IAccount, IState 
      { // Implements Username property }


    public class Registration : Account, IRegistration 
      { // Implements Address property }

}

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");    
  // sets Registration.Username to Popeye.


ReflectHelper.GetProperty(reg, "Username");              
  // gets Registration.Username returning Popeye.


ReflectHelper.SetProperty(reg, "Addr_Country", "India"); 
  // sets Registration.Addr.Country to India.


ReflectHelper.GetProperty(reg, "Addr_Country");          
  // gets Registration.Addr.Country returning India.

The ValidationHelper and ValidationException utility classes provide methods for validating user input.

public sealed class ValidationHelper
{
    public static void ValidateRequired(string str)
      { // Validate str for non-null, non-empty content }

}

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.

// Windows Application

using System.Windows.Forms; // Windows Forms


// Presentation Tier (Windows)

namespace Win
{
    // Windows Form

    public class WinForm : System.Windows.Forms.Form
    {
        // Windows Automator declaration


        public WinForm() { // Create an instance of the windows automator }


        // Register the user

        private void RegisterButton_Click(object sender, System.EventArgs e)
        {
            // Store the current UI state using the automator

            try 
              { // Register the user by using the automator 

                // state with the registration service }

            catch(ValidationException x)
            {
                ProcessErrors(this, x.Errors);
                // Reset the automator state

            }
        }

        private void ProcessErrors(Control control, IDictionary errors)
        {
            // Is the control name set?

            if(control.Name != null)
            {
                // Is the control associated with an error message

                if(errors != null && errors.Contains(control.Name)) 
                   { // Display the error message }

                else 
                   { // Reset the error message }


                // Iterate over the nested controls in the collection

            }
        }
    }
}

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.

// ASP.NET Web Application

using System.Web.UI; // Web Forms


// Presentation Tier (Web)

namespace Web
{
    // ASP.NET Web Form

    public class WebForm : System.Web.UI.Page
    {
      // Error Provider for Web Forms creation

      // Web Automator declaration


        public WebForm()
        {
            // Set an ID for the form otherwise the 

            // auto population will fail.

            this.ID = "WebForm";
            // Create an instance of the web automator

        }

        // Members


        // Register the user

        private void RegisterButton_Click(object sender, System.EventArgs e)
        {
            // Store the current UI state using the automator

            try { // Register the user by using the 

                  // automator state with the registration service }

            catch(ValidationException x)
            {
                ProcessErrors(this, x.Errors);
                // Reset the automator state

            }
        }

        private void ProcessErrors(Control control, IDictionary errors)
        {
            // Is the control ID set?

            if(control.ID != null)
            {
                // Is the control associated with an error message

                if(errors != null && errors.Contains(control.ID)) 
                  { // Display the error message }

                else { // Reset the error message }


                // Iterate over the nested controls in the collection

            }
        }
    }
}

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.

// Services Tier

namespace Services
{
    // Register a user by creating an account

    public interface IRegistrationService 
         { void Register(IRegistration registration); }

    // Authenticate the user into the system. 

    // This service is not currently used.

    public interface IAuthenticationService
      { void Authenticate(IAccount account); }

    // Service factory

    public interface IServiceFactory
    {
        // Declare the IAuthenticationService property


        // Declare the IRegistrationService property

        public IRegistrationService RegistrationService
        {
            get;
        }
    }
}

namespace Lib
{
    // A singleton factory to create and 

    // return the services provided

    // Implements AuthenticationService and 

    // RegistrationService property 

    public sealed class ServiceFactory : IServiceFactory
    {
        private AccountManager acctManager = 
          new AccountManager(); // The Lib.AccountManager


        public static IServiceFactory GetInstance() 
           { // Return the singleton instance }


        // Implement the IAuthenticationService property


        // Implement the IRegistrationService property

        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.

// Business Tier

namespace Lib
{
    // An implementation of the services provided

    public sealed class AccountManager : 
         IRegistrationService, IAuthenticationService
    {
        public void Authenticate(IAccount acct) 
            { // Implementation of the authentication service }

        public void Register(IRegistration registration) 
            { // Implementation of the registration service }

    }
}

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.

// Integration (Data Access) Tier

namespace Dao
{
    // An abstraction of the Data Access Object

    public abstract class AbstractDao 
        { // Property to access the databse }


    // Access to Account information

    public class AccountDao : AbstractDao
    {
        public bool AccountExists(string username) 
          { // Check if an account with the specified 

            // username already exists }

    }

    // Access to Registration information

    public class RegistrationDao : AccountDao
    {
        public void Register(IRegistration registration) 
          { // Register the user by persisting the data }

    }
}

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.

  1. Utility - The Utility class Library (Utility namespace).
  2. Lib - The Services interfaces, Business Logic and Data Acess Library (Services, Lib and Dao namespace).
  3. Win - The Windows Application (Win namespace).
  4. 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).

  1. Click on Register button without filling in any information - Validation error for required fields.
  2. Fill in all the required information and click on the Register button - Registration successful.
  3. 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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here