Friday, August 5, 2011

Implementing the Mode Handler of Self-Service Application using Finite State Machine

Implementing ATM/CDM/Kiosk states (Suprvisory, OOS, Offline, In-Service) in C#

Hi,

Some of you will find this post extremely useful, while, some of you will find it completely irrelevant. There is a good reason for both opinions. Self-Service industry is a very specialized domain. Unlike ASP .NET and other commonly used technologies, there is very little or no information available on this subject. Today, I am going to implement a mode handler of Self-Service application using Finite State Machine. Before doing that, we will first go through the basic concepts of SST, Modes, and Mode Handling. I am assuming in this article that you are well aware of the state design pattern and familiar with finite state machines.


Self-Service Terminals

Self-Service Terminals (SST) are pretty common today and come in different forms. You use an ATM to withdraw cash from banks. Kiosks at movie theaters are used to purchase movie tickets. Similarly, kiosks at airports are used for passenger check-in. These are all instances of Self-Service terminals.


Self-Service Transactions

SSTs offer a wide range of transactions. Though, it is not possible to list down all types of transactions, but the following are the most common SST transactions:

i. Cash Withdrawal
ii. Cash Deposit
iii. Check Deposit
iv. Balance Inquiry
v. Utility Bill Payment
vi. Toll/Fine Payment
vii. Mobile Topup/Recharge
viii. Tickets Purchasing
ix. Job Applications
x. Information Viewing


Self-Service Application Modes

Self-Service terminals such as ATMs, CDMs, and Kiosks operate in 4 standard modes. These are:

1. Offline

The connectivity of machine to the network is broken. The SST cannot send and receive messages over the network. This mode is called Offline mode.

2. Out-of-Service

An SST may become Out-of-Service (OOS) for various reasons. The two major reasons are software or hardware failure. However, in most of the cases, OOS mode is caused by a hardware problem. Common examples are notes stuck in Bunch-Note Acceptor, checks stuck in Check Processing Module, or Journal Printer out of paper. In most of the cases, the machine cannot recover itself from OOS mode by itself and requires manual human effort. Someone must come and remove the stuck notes/checks or replenish Journal paper.

3. Supervisory

This is the servicing mode of the SST. Terminal is switched to Supervisory mode when hardware repair is performed. Also, when the terminal is out of cash or paper, the replenishment activity can be only performed when the SST is in supervisory mode. Supervisory mode operations are not only restricted to repair and replenishment, but also include closing of the business cycle and balancing.

A hardware switch/button installed usually inside the SST is used to switch the machine to supervisory mode.

4. In-Service

When SST is not in any of the aforementioned modes, then it means it is ready to complete customer’s transactions. Hence, it is in In-Service mode. The following are the requisites for an SST to become In-Service:

a. Terminal is connected to the network and can send and receive messages
b. All critical devices required to complete a transactions are working
c. Supervisory switch is not turned on.


Mode Handler

A Mode Handler is a controller which is responsible to switch SST to appropriate modes based on the current state of the terminal. Mode Handler is the most critical part of any Self-Service application. Any flaw in Mode Handler can cause devastating results. Take the example of Supervisory mode. This mode should be available to machine supervisors/custodians only. If a customer is given access to the supervisory mode, then the customer can perform actions like clearing the business-cycle, dispense notes from the machine, etc. Scenarios like these may cause huge financial losses.


Implementation of Mode Handler through Finite State Machine

Since each mode of the Self-Service terminal represents the state of the machine, therefore, Mode Handler is implemented in the form of finite state machine. We will write a class to model each state of the machine. In this way, we will have 4 classes. We will also write a controller class to implement state switching.

Since, we are just writing the Mode Handler and not the whole Self-Service application, we will demonstrate the working of Mode Handler through a WinForms application. The WinForms application will contain checkboxes. Each checkbox will represent the condition that triggers state switching.

It is also to be noted that a Self-Service applications should always start in OOS mode. This is the safest mode. The Mode Handler should then check all state switching conditions and should switch SST to appropriate mode. Also, for security reasons, In-Service mode should be the last possible state of the machine after going through all state switching conditions. On the contrary, Supervisory mode should be the top priority mode because if a machine supervisor/custodian has pressed the supervisory switch, the Self-Service application should immediately switch to supervisory mode. The only exception is when a transaction is in progress. In this case, the terminal should first finish the transaction and then switch to supervisory mode.

The following is the sample code of a Mode Handler written in C#.

VariablesStore.cs

namespace SST
{
///
/// Summary description for VariablesStore.
///

public class VariablesStore
{ // implementing global store to save state of the machine
// and other variables to use throughout the application
// provides better control and code-maintenance
public static int SupervisoryOn = 0;
public static int Failure = 0;
public static int NetworkFailure = 0;
}
}


SSTModes.cs


namespace SST
{
abstract class Mode
{ // provides template for child mode classes
protected int MyMode = 0;

private int CheckSupervisorSwitchOn()
{ // check if someone pressed supervisory switch
return VariablesStore.SupervisoryOn;
}

private int CheckFailures()
{ // check if a device has failed
return VariablesStore.Failure;
}

private int CheckNetworkConnectivityFailure()
{ // check if network connection is broken
return VariablesStore.NetworkFailure;
}

public int GetNextMode()
{ // perform SST health check and recommend appropriate mode
// highest priority is supervisory switch

// returning modes based on HC
if (CheckSupervisorSwitchOn() == 1)
return 1;

if (CheckFailures() == 1)
return 2;

if (CheckNetworkConnectivityFailure() == 1)
return 3;

return 4;
}

// needs to be implemented in particular modes only
public abstract int ProcessMode();
}

class SupervisoryMode : Mode
{ // models Supervisory mode of SST

public SupervisoryMode()
{ // this is my mode number
MyMode = 1;
}

public override int ProcessMode()
{
// do HC and see it I am a valid mode
int ValidMode = GetNextMode();

// if I am valid mode, I can start my processing
while (ValidMode == MyMode)
{
// supervisory operations start

// supervisory operations end

// I have ended my processing, now check if I can exit
ValidMode = GetNextMode();
}

// return next valid mode for SST
return ValidMode;
}
}

class OOSMode : Mode
{ // models OOS mode of SST
public OOSMode()
{
MyMode = 2;
}

public override int ProcessMode()
{
// do HC and see it I am a valid mode
int ValidMode = GetNextMode();

// if I am valid mode, I can start my processing
while (ValidMode == MyMode)
{
// OOS operations start

// OOS operations end

// I have ended my processing, now check if I can exit
ValidMode = GetNextMode();
}

// return next valid mode for SST
return ValidMode;
}
}

class OfflineMode : Mode
{ // models Offline mode of SST
public OfflineMode()
{
MyMode = 3;
}

public override int ProcessMode()
{
// do HC and see it I am a valid mode
int ValidMode = GetNextMode();

// if I am valid mode, I can start my processing
while (ValidMode == MyMode)
{
// Offline operations start

// Offline operations end

// I have ended my processing, now check if I can exit
ValidMode = GetNextMode();
}

// return next valid mode for SST
return ValidMode;
}
}

class InServiceMode : Mode
{ // models In-Service mode of SST
public InServiceMode()
{
MyMode = 4;
}

public override int ProcessMode()
{
// do HC and see it I am a valid mode
int ValidMode = GetNextMode();

// if I am valid mode, I can start my processing
while (ValidMode == MyMode)
{
// In-Service operations start

// In-Service operations end

// I have ended my processing, now check if I can exit
ValidMode = GetNextMode();
}

// return next valid mode for SST
return ValidMode;
}
}
}


SSTApp.cs


using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;
using System.Data;

namespace SST
{
///
/// Summary description for SSTApp.
///

public class SSTApp : System.Windows.Forms.Form
{
private System.Windows.Forms.Button btnStart;
private System.Windows.Forms.CheckBox chkSupervisor;
private System.Windows.Forms.CheckBox chkFailure;
private System.Windows.Forms.CheckBox chkConnectivity;
///
/// Required designer variable.
///

private System.ComponentModel.Container components = null;

public SSTApp()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();

//
// TODO: Add any constructor code after InitializeComponent call
//
}

///
/// Clean up any resources being used.
///

protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}

#region Windows Form Designer generated code
///
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
///

private void InitializeComponent()
{
this.btnStart = new System.Windows.Forms.Button();
this.chkSupervisor = new System.Windows.Forms.CheckBox();
this.chkFailure = new System.Windows.Forms.CheckBox();
this.chkConnectivity = new System.Windows.Forms.CheckBox();
this.SuspendLayout();
//
// btnStart
//
this.btnStart.Location = new System.Drawing.Point(8, 16);
this.btnStart.Name = "btnStart";
this.btnStart.TabIndex = 0;
this.btnStart.Text = "Start";
this.btnStart.Click += new System.EventHandler(this.btnStart_Click);
//
// chkSupervisor
//
this.chkSupervisor.Location = new System.Drawing.Point(8, 64);
this.chkSupervisor.Name = "chkSupervisor";
this.chkSupervisor.Size = new System.Drawing.Size(136, 24);
this.chkSupervisor.TabIndex = 1;
this.chkSupervisor.Text = "Supervisor Switch On";
this.chkSupervisor.CheckedChanged += new System.EventHandler(this.chkSupervisor_CheckedChanged);
//
// chkFailure
//
this.chkFailure.Location = new System.Drawing.Point(8, 96);
this.chkFailure.Name = "chkFailure";
this.chkFailure.TabIndex = 2;
this.chkFailure.Text = "Device Failure";
this.chkFailure.CheckedChanged += new System.EventHandler(this.chkFailure_CheckedChanged);
//
// chkConnectivity
//
this.chkConnectivity.Location = new System.Drawing.Point(8, 128);
this.chkConnectivity.Name = "chkConnectivity";
this.chkConnectivity.Size = new System.Drawing.Size(128, 24);
this.chkConnectivity.TabIndex = 3;
this.chkConnectivity.Text = "Connectivity Failure";
this.chkConnectivity.CheckedChanged += new System.EventHandler(this.chkConnectivity_CheckedChanged);
//
// SSTApp
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(152, 165);
this.Controls.Add(this.chkConnectivity);
this.Controls.Add(this.chkFailure);
this.Controls.Add(this.chkSupervisor);
this.Controls.Add(this.btnStart);
this.Name = "SSTApp";
this.Text = "SSTApp";
this.Load += new System.EventHandler(this.SSTApp_Load);
this.ResumeLayout(false);

}
#endregion

///
/// The main entry point for the application.
///

[STAThread]
static void Main()
{
Application.Run(new SSTApp());
}

private void SSTApp_Load(object sender, System.EventArgs e)
{

}

public void ModeHandler()
{
// be default, SST will start in OOS mode
int CurrentMode = 2;

// init all modes
Mode ThisMode = null;
SupervisoryMode ThisSupervisoryMode = new SupervisoryMode();
OOSMode ThisOOSMode = new OOSMode();
OfflineMode ThisOfflineMode = new OfflineMode();
InServiceMode ThisInServiceMode = new InServiceMode();

while (true)
{
MessageBox.Show("Switching to Mode " + CurrentMode.ToString());

// check which mode to switch to
switch (CurrentMode)
{
case 1:
{ // set Supervisor mode as mode to run
ThisMode = ThisSupervisoryMode;
break;
}
case 2:
{ // set OOS mode as mode to run
ThisMode = ThisOOSMode;
break;
}
case 3:
{ // set Offline mode as mode to run
ThisMode = ThisOfflineMode;
break;
}
default:
{ // If everything is fine,
// set In-service mode as mode to run
ThisMode = ThisInServiceMode;
break;
}
}

// run this mode now
CurrentMode = ThisMode.ProcessMode();

// switch to different mode after 3 secs (not necessary)
Thread.Sleep(3000);
}
}

private void btnStart_Click(object sender, System.EventArgs e)
{ // start mode handler in a separate thread
Thread ModeHandlerThread = new Thread(new ThreadStart(ModeHandler));
ModeHandlerThread.Start();
}

private void chkSupervisor_CheckedChanged(object sender, System.EventArgs e)
{ // simulating through checkbox supervisory switch pressing on the actual SST
if (chkSupervisor.Checked)
VariablesStore.SupervisoryOn = 1;
else
VariablesStore.SupervisoryOn = 0;
}

private void chkFailure_CheckedChanged(object sender, System.EventArgs e)
{ // simulating through checkbox some device failure on the actual SST
if (chkFailure.Checked)
VariablesStore.Failure = 1;
else
VariablesStore.Failure = 0;
}

private void chkConnectivity_CheckedChanged(object sender, System.EventArgs e)
{ // simulating through checkbox network connectivity failure
if (chkConnectivity.Checked)
VariablesStore.NetworkFailure = 1;
else
VariablesStore.NetworkFailure = 0;
}
}
}


UI Details

When you launch the application, you will see the main form. You need to click on Start button to start the Mode Handler. Use checkboxes to simulate state switching conditions. Whenever the application switches state, it will display a message box with state number. The following are the state numbers:

Supervisory = 1
OOS = 2
Offline = 3
In-Service = 4



I have included a 3 second delay in state switching to make it easier to observe.

If you work in Self-Service industry, this post will certainly help you in understanding:

1. Fundamental concepts related to SST
2. What are the Modes in which SST operates
3. What is a Mode Handler
4. How Mode Handler eorks
5. How to implement a Mode Handler

To get the complete code, please send me an email and I will reply to you with a copy of the working project.

Cheers!
JS

Thursday, August 4, 2011

Iterating through rows in SQL Server

Iterate through each row in table using while loop in SQL

Hi,

You may face a situation in which you need to iterate through rows in a table one at a time and perform some operation on the data of the row. Note that, set based operations are highly efficient in SQL server and row based operation is not recommended. Suppose, you have a table and you need to run an SP on each row of the table. One possible solution is to use cursors, which are complex to write and also not very efficient. An alternative and easier method is to use the while loop.

You will need to copy rows from the original table into a temp table. Let’s create a temp table to use in this example:

create table #tempList(Seq int, Color varchar(10))

insert into #tempList values(1, 'Red')
insert into #tempList values(2, 'Green')
insert into #tempList values(3, 'Blue')
insert into #tempList values(4, 'Black')
insert into #tempList values(5, 'White')

Here, we are manually inserting rows, you can use select into to insert rows into temp table from the original table. Now, let’s use while loop to go through each row of this temp table and print it. You can store column values in some variables and do your operations like calling SPs.

declare @Seq int
declare @Color varchar(10)

while (select count(*) From #tempList) > 0
begin

-- get the row
select top 1 @Seq = Seq, @Color = Color from #tempList

-- perform some operation
select @seq, @Color

-- delete it from temp table
delete from #tempList where Seq = @Seq

end

We are selecting top 1 row and then performing some operation on it (printing in our case). Then, we delete this row from the temp table and repeat the aforementioned steps until there are no rows left in the temp table.

This method may not be the best one in terms of execution time and dependency on temp table, but works well for one-time tasks such as quick report generation directly through SQL Server Management Studio.

Cheers!
JS

Wednesday, August 3, 2011

How to Create Dynamic Forms in .NET (WPF, ASP .NET)

Creating forms through .Net code-behind

Why do we need Dynamic Forms

Generating data entry forms in WPF is not difficult. In fact, it is very easy and convenient to generate forms dynamically. Form generation at runtime has three major advantages over forms designed at design time:

1. You can add/remove controls without changing the code.
2. Control names and other properties can be changed without changing the code.
3. Layout of the form can be changed without changing the code.


Let’s take the example of a simple membership subscription form. The following are the initial UI requirements of this form as communicated by the customer:

1. The form will contain a label and a textbox for email address.
2. The form will contain a label and a textbox for password.
3. The form will contain a label and a textbox for first name.
4. The form will contain a label and a textbox for last name.
5. The form will contain a scrollable read-only multi-line textbox displaying the terms of use.
6. The form will contain a checkbox which says Agree to Terms
7. The form will contain a submit button.


This simple form can be designed within minutes by any .NET programmer. However, if tomorrow, the customer comes back and requests an additional text box to capture user’s phone number, the programmer needs to change the code to include these UI elements. If the customer comes back after every one month and requests the same type of change, then the work becomes redundant and a lot of useful time is wasted in adding text or check boxes or other UI elements.

The solution to this problem is to switch from creating static to dynamic forms.


What are Dynamic Forms

A dynamic form is one about which we are not sure how it will look like at runtime. A dynamic form is created in code-behind and in design-view may appear vacant because UI elements are not included in the form by the programmer at design-time.

It is painted dynamically at runtime. The controls to be painted on the form are usually stored in a database. The application simply fetches the list of UI elements to be painted from the database and for each element, creates an appropriate control on the form.

If any change is required in UI, all we need to do is add/remove/update records in the database. The client can be given a simple tool/script to do the changes in the database or the programmer could do so for them. If the programmer is charging for 5 hours to make this change but he is spending only 1 hour due to dynamic form, then it implies that the company is getting free money for additional 4 hours. This is a great strategy to increase revenues.


How to Implement Dynamic Forms

We will implement a sample dynamic form in WPF client application. The following are the assumptions:

Database

The database has the following tables:

a. Table to store form information
Form(FormId, Title, Description)

b. Table to store UI elements properties
Controls(ControlId, Name, DisplayName, Type, IsReadonly, DefaultValue)

c. Table to store values of controls like combo box/drop down list, etc.
ControlValues(ControlId, Value)

d. Table to associate many-to-many relationship of forms and controls
FormControls (FormId, ControlId, DisplayOrder )

Here is the sample data:

Form:
(1, 'Subscription', 'Membership Subscription Form')


Controls:
1, 'txtEmail', 'Email Address', 2, 'N', NULL
2, 'txtPassword', 'Password', 2, 'N', NULL
3, 'txtFName', 'First Name', 2, 'N', NULL
4, 'txtLName', 'Last Name', 2, 'N', NULL
5, 'txtTerms', 'Terms of Use', 2, 'Y', 'Some agreement text'
6, 'chkAgree', 'Agree to Terms', 6, 'N', NULL
7, 'btnSubmit', 'Submit', 10, 'N', NULL


FormControls (FormId, ControlId, DisplayOrder )
1,1,3
1,2,4
1,3,1
1,4,2
1,5,5
1,6,6
1,7,7
So, we defined a form in which the controls will appear in this order:

First Name, Last Name, Email Address, Password, Terms of Use, Agree to Terms, and Submit.

Application

The data access layer is available to read data from the tables. The layer can be written in ADO .NET or LINQ, or some other technology, all we need is a list a methods to perform operations on database tables.

We will use Stack Panels to display controls on the form. A stack panel is basically a container which can contain multiple controls to display either horizontally or vertically. We will display controls in rows, each row will be implemented through a stack panel, so that all the controls contained in one stack panel will appear in one row.

StackPanel1
StackPanel2
StackPanel3
StackPanel4
StackPanel5

StackPanel6
StackPanel7

The following code demonstrates how we are fetching the list of controls through database and how are we painting them dynamically on the form:


public void PaintForm(int FormID)

{

InitializeComponent();



// get form details

Form ThisForm = GetFormDetailsFromDB(FormId);

// fill up static content on the form first

lblHeading.Content = ThisForm.Title;

lblPurpose.Content = ThisForm.Purpose;

lblDescription.Content = ThisForm.Description;



// get the list of controls to paint on this form

var ControlsSet = (List)ThisContext.GetFormControls(FormId);



foreach (var control in ControlsSet)

{

StackPanel sp = new StackPanel();

sp.Orientation = Orientation.Horizontal;

sp.Margin = new Thickness(3);



switch (control.Type)

{ // text box

case 2:

{

TextBlock textBlock = new TextBlock();

textBlock.Text = control.DisplayName + ": ";

textBlock.Width = 80;



TextBox textBox = new TextBox();

textBox.Name = control.Name;

textBox.Width = 150;

this.RegisterControl(textBox.Name, textBox);



if (control.DefaultValue != String.Empty)

textBox.Text = control.DefaultValue;



if (control.IsReadOnly != null)

{

if (control.IsReadOnly.ToUpper() == "Y")

textBox.IsReadOnly = true;

}



sp.Children.Add(textBlock);

sp.Children.Add(textBox);



break;

}



case 3:

{ // combo box

TextBlock textBlock = new TextBlock();

textBlock.Text = control.DisplayName + ": ";

textBlock.Width = 80;



ComboBox comboBox = new ComboBox();

comboBox.Width = 80;

comboBox.Name = control.Name;

this.RegisterControl(comboBox.Name, comboBox);



var values = GetControlValues(control.Id);



foreach (var ThisValue in (IEnumerable)values)

{

comboBox.Items.Add(ThisValue.Value);

}



if (control.DefaultValue != String.Empty)

comboBox.SelectedValue = control.DefaultValue;



if (control.IsReadOnly != null)

{

if (control.IsReadOnly.ToUpper() == "Y")

comboBox.IsEnabled = false;

}



sp.Children.Add(textBlock);

sp.Children.Add(comboBox);



break;

}



case 5:

{ // date picker

TextBlock textBlock = new TextBlock();

textBlock.Text = control.DisplayName + ": ";

textBlock.Width = 80;



DatePicker datePicker = new DatePicker();

datePicker.Name = control.Name;

this.RegisterControl(datePicker.Name , datePicker);



if (control.DefaultValue != String.Empty)

datePicker.SelectedDate = Convert.ToDateTime(control.DefaultValue);



if (control.IsReadOnly != null)

{

if (control.IsReadOnly.ToUpper() == "Y")

datePicker.IsEnabled = false;

}



sp.Children.Add(textBlock);

sp.Children.Add(datePicker);



break;

}



case 6:

{ // check box



CheckBox checkBox = new CheckBox();

checkBox.Name = control.Name;

checkBox.Content = control.DisplayName;

this.RegisterControl(checkBox.Name , checkBox);



if (control.DefaultValue != null)

{

if (control.DefaultValue == "Y")

checkBox.IsChecked = true;

else

checkBox.IsChecked = false;

}



if (control.IsReadOnly != null)

{

if (control.IsReadOnly.ToUpper() == "Y")

checkBox.Enabled = false;

}



sp.Children.Add(checkBox);



break;

}





case 10:

{ // button

Button button = new Button();

button.Name = control.Name;

button.Content = control.DisplayName;

button.Width = 50;



sp.Children.Add(button);



break;

}



default:

{

break;

}

}



FormBase.Children.Add(sp);

}

}



private void RegisterControl(string ControlName, Control ThisControl)

{ // register control because we will need to read the value entered by user on form submission

if (this.FindName(ControlName) != null)

this.UnregisterName(ControlName);

this.RegisterName(ControlName, ThisControl);

}


You must be wondering that how are we determining the order of controls to display in rows(stack panels). Well, the method GetFormDetailsFromDB should do a select query on FormControls table and order by DisplayOrder. In this way, we will simply start painting controls in the same order as that of the record-set we received.

Reading Forms Data Entered by User
 The logic is pretty simple here. Go through all stack panels in the form and search each control by its name. The following code will read the values entered by user in the form. We are storing the values in a hashtable ControlValues for future use.


public void ReadFilledForm(int FormId)
{
Hashtable ControlsValues = new Hashtable();
var ControlsMap = GetFormControls(FormId)

// now save form values
foreach(var control in ControlsMap)
{
foreach (StackPanel sp in FormBase.Children)
{
Control ThisControl = (Control) FindName(control.Name);
if (ThisControl != null)
{
if (ThisControl.GetType().ToString().Equals("System.Windows.Controls.TextBox"))
{
TextBox NewTextBox = (TextBox)ThisControl;
ControlsValues.Add(control.Id, NewTextBox.Text);
}
if (ThisControl.GetType().ToString().Equals("System.Windows.Controls.ComboBox"))
{
ComboBox NewComboBox = (ComboBox)ThisControl;
ControlsValues.Add(control.Id, NewComboBox.SelectedValue);
}
else if (ThisControl.GetType().ToString().Equals("System.Windows.Controls.CheckBox"))
{
CheckBox NewCheckBox = (CheckBox)ThisControl;
if (NewCheckBox.IsChecked == true)
ControlsValues.Add(control.Id, "Y");
else
ControlsValues.Add(control.Id, "N");
}
if (ThisControl.GetType().ToString().Equals("System.Windows.Controls.DatePicker"))
{
DatePicker NewDatePicker = (DatePicker)ThisControl;
ControlsValues.Add(control.Id, NewDatePicker.SelectedDate);
}

break;
}
}
}
}


I hope the above code snippets will help readers in getting acquainted with dynamic forms creation in WPF. The same code can be reused with little changes in ASP .NET web forms.

Cheers!
JS