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

Web User Forms for ASP.NET

0.00/5 (No votes)
18 Sep 2012 10  
User driven runtime dynamic ASP.NET Web Forms

Introduction

The ASP.NET development platform provides to easily design and publish Web forms. For certain applications however, it is necessary to keep those forms dynamically variable and allow to embed them in a dynamic fashion too. There are scenarios, where the creation of those forms is separated from the application development process.

Such dynamic forms might have the following parts:

Web User Forms Parts

Each of these parts has its own requirements in regard to dynamic form management:

Part Requirement
Form Design
  • Integration of business objects
  • Enforce business rules
  • Moderate skills to design forms, as for example lack of object-orientated know how
  • Convenient development environment
  • No limitations in regard to design and layout possibilities
  • Visually appealing and seamlessly integrable in Web solution (look & feel)
  • Possibility to use high level controls, such as for example for date selection
  • Reuse of forms by allowing to nest them into one another
  • Testing and simulation of form data entry
Form Deployment
  • Individual deployment cycles, independent from those of the Web application
  • Remote installation
Data Storage
  • Differing storage media: database, file system, Web-service etc.
  • Standardized format for data exchange
Form Hosting
  • Access to application data at run-time
  • Provision of the available form elements for the form design
  • Run-time security: no access to program code and data
  • No impact on run-time stability  
  • Seamless integration of the forms into the layout of the Web application
  • Enforcement of licensing issues of third party components

Solution Approach

Web User Forms Solution

The Application Provider develops the Form Controls library, which serves for designing the Web forms. The library contains elements for structuring the forms as well as the available form fields. The library will be delivered to the Form Designer in combination with a design Web site.

The actual design of the forms will be done in a Web-enabled version of Visual Studio, such as for example, the freely available Visual Web Developer Express Edition. After installation of the Form Controls library into a Visual Studio Toolbox, the form can be designed in an ASP.NET UserControl. Apart from the provided Form Controls, it is also possible to use the ASP.NET Standard Controls and Validators in the form. Using CSS styles and JavaScript, the designer is free to adapt the form to her individual needs.

Once the design of the form is fixed, it will be deployed. Forms can be copied to the Web application in a conventional way, or might be provided from sources outside the actual Web application itself (FTP, database, etc.). Different mechanisms allow the integration of the forms into the Form Repository.

At run-time, the forms will be loaded dynamically into the Web Application while handling security relevant aspects. Depending on individual needs, run-time controls get invoked, look-ups become initialized, form data gets loaded from Data Storage and run-time variables get expanded. Replacing a placeholder, the UserControl gets loaded in a Web page and served to the Web Browser user.

After the Web user has entered the form data and confirmed them, the collected form data gets transferred to the Data Storage.

Form Models

There exist two representational models for the Web user form:

  • UI model
  • Entity model

The UI model represents each part of a form as a control and is used at form design time as well as at run-time for user data entry. The data model abstracts and contains the field values of the form.

The following overview compares the two models:

Aspect UI model Entity model
Namespace WebUserForms.Controls WebUserForms.Data
Based on System.Web.UI.Control IFormEntity
Form IUserForm IForm
Form header IUserFormHeader IFormInfo
Form fields IUserFormField
IListField
ILookupField
IExpressionField
IFormField
Sub-form IUserForm IFormGroup
Sub-form header IUserFormHeader -
Nesting by UserControl IFormGroup.Entities

Form Controls Library

The Form Controls Library implements the UI model of the form. Based on the ASP.NET UserControl (*.ascx), such a form UserControl contains the following components:

  • 1 form header: collection of form related information
  • 0...n form fields: name and value of a single form field
  • 0...n sub-forms: sub-forms with the same structure as the main form
  • 0...n form commands: control of form specific actions

The form header IUserFormHeader contains various identification related items such as type, name and version. Additionally, run-time information such as creation and modification times are available. By inheriting from HiddenField, the implementing UserFormHeader results in the header data being transferred invisibly in the form.

Form fields implement the IUserFormField interface and contain field name, value and an editing status AllowEdit. There exist various specializations of IUserFormField:

  • IListField: Field with a list of values in ListItemColletion
  • ILookupField: IListField field with a look-up name
  • IExpressionField: Field with an expression

The accompanying project Controls provides the following form field controls:

Form field control Represented by control Implements
TextBox TextBox IUserFormField
CheckBox CheckBox IUserFormField
CheckBoxList CheckBoxList IListField
RadioButton RadioButton IUserFormField
RadioButtonList RadioButtonList IListField
ListBox ListBox IListField
LookupListBox ListBox with lookup ILookupField
DropDownList DropDownList IListField
LookupDropDownList DropDownList with lookup ILookupField
Calendar Calendar IUserFormField
HiddenVariable HiddenField IExpressionField
VisibleVariable Label IExpressionField
ComboBox DropDownList
Demo of a run-time control
IListField
LookupComboBox DropDownList
Demo of a run-time control with look-up
ILookupField
DatePicker TextBox
Demo of a run-time control
IUserFormField
TimePicker TextBox
Demo of a run-time control
IUserFormField

The controls HiddenVariable and VisibleVariable represent expressions, which are controlled by the Web application. This allows to provide the form with application data such as for example the name of the active user.

Forms can be supplemented with own or third party controls as necessary. Such controls however, can often not be delivered to the form designer, be it for deployment or licensing related issues. In such situations the design will use a placeholder control instead, which will be replaced by the actual own or third party control at run-time. The controls ComboBox, LookupComboBox, DatePicker and TimePicker demonstrate such placeholder controls.

A form can execute commands through the Button control if required. For this to work, a control has to implement the IUserFormCommand interface.

Design Time Support

The design mode of the form provides the designer with helpful information through coloring the form fields according to their state. Visual Studio allows to control the representation of controls at design time through inheriting from ControlDesigner. The following ControlDesigners are provided:

Field type Control Designer Provides hinting at ...
IUserFormField UserFormFieldDesigner
  • Missing field name
  • Conflicting field names
ILookupField LookupFieldDesigner
  • Missing field name
  • Conflicting field names
  • Missing lookup name
IExpressionField ExpressionFieldDesigner
  • Missing field name
  • Conflicting field names
  • Missing field expression
IUserFormCommand UserFormCommandDesigner
  • Missing command name

The control UserFormInfoControl lists all form information, including sub-forms. For consolidation of the form data, the UserFormInfoRenderer makes use of the UserFormVisitior, which will be described further down.

Web User Form Info

The control UserFormXmlInfoControl renders the XML data of a form.

Side note: to allow debugging a ControlDesigner, the following steps are required:

  1. Setting the desired breakpoints
  2. In the project of the control library below Properties > Debugging > Start external Program, the path to Visual Studio has to be set: ...\Common7\IDE\DevEnv.exe
  3. If not yet the case, the control library has to be marked as Startup Project. Project > Set as StartUp Project
  4. Start debugging: Debugging > Start Debugging
  5. Open the same solution in the new Visual Studio instance
  6. Open the form in design mode - Debugger stops at break point

Form Controls Library Deployment

The project Controls contains some Compilation Symbols (Project > Properties > Build), which allow to determine which controls will be contained in the form controls library.

The Form Controls library has to be installed at the form designer's workstation. The control library can be installed into a Visual Studio Toolbox manually or with a tool such the Visual Studio Toolbox Manager.

Web User Forms Toolbox

For an easy start, the form designer should be provided with a Web site which shows the various aspects of form design. The included project DesignWebSite shows such a Web site.

Form Design

Once the form controls library has been installed as a toolbox, the form designer can create a new form with the following steps:

  1. Open design Web site: File > Open > Project/Solution > DesignWebSite200x.sln
  2. Insert user control: Solution Explorer > Context menu on folder UserForms > New Item > Web User Control > Place code in separate file=off, Name=MyForm1
  3. Insert form header: Toolbox > Web User Forms > UserFormHeader
  4. Setup form header: Properties > Web User Forms > Name=MyFormName, Type=MyFormType
  5. Insert input field: Toolbox > Web User Forms > TextBox
  6. Setup input field: Properties > Web User Forms > FieldName=MyFormField, FieldValue=Hello World
  7. Save changes: File > Save All
  8. Execute Web site: Debug > Start Without Debugging
  9. Modify the value 'Hello World' and press button Update Form Info

Sub-forms can be integrated by inserting the child UserControl into the parent UserControl.

The Web User Forms Properties allow to configure the form elements. Depending on the type of control, differing properties are available:

Form control Property Description Required
IUserFormHeader Name Name of the form Yes
Type Typing information Yes
Description From description No
Version Form version No
IUserFormField FieldName Name of the field Yes
FieldValue Default value No
IListField FieldName Name of the field Yes
FieldValue Default value No
ILookupField FieldName Name of the field Yes
LookupName Name of the lookup Yes
FieldValue Default value No
IExpressionField FieldName Name of the field Yes
FieldExpression Expression for the field Yes
FieldValue Default value No
IUserFormCommand CommandName Name of the command Yes
CommandArgument Command parameter No

The examples DatePicker and TimePicker demonstrate the usage of those control specific properties.

Form Business Rules

Business rules can be integrated in a variety of ways:

  • Control properties as for example TextBox.MaxLength
  • Validators such as for example RequiredFieldValidator
  • JavaScript (see below)

Using any of the CLR programming languages such as C# or VB.NET is not recommended for security reasons. How to suppress the execution of such code in forms at runtime will be shown further below.

The following sample demonstrates the usage of JavaScript in a form:

<script language="javascript">

  // --- init form field ---
  function initFormField()
  {
    var field = document.getElementById( "<%# TextBox1.ClientID %>" );
    if ( field != null )
    {
      field.value = Date();
    }
  }

  // --- show form field ---
  function showFormField()
  {
    var field = document.getElementById( " <%# TextBox1.ClientID %>" );
    if ( field != null )
    {
      alert( field.value );
    }
  }

  // --- register form loading function ---
  function addLoadEventHandler( func )
  {
    var previousHandler = window.onload;
    if ( typeof window.onload != "function" )
      window.onload = func;
    else
      window.onload = function()
      {
        previousHandler();
        func();
      }
  }

  // --- add form init function ---
  addLoadEventHandler( initFormField );


</script>

<button onclick="showFormField()">Show Form Field</button>
<cc1:TextBox ID="TextBox1" runat="server" FieldName="MyField" />

Notice the access to the form control through the binding directive <%#TextBox1.ClientID%>. This will be resolved to the controls ID at runtime using Data Binding (see Data-Binding Expression Syntax).

Form Design Guidelines

The following rules should be respected when designing forms:

  • Any Web Form Control should provide a meaningful ID
  • No usage of images (with the current implementation)
  • Provide size measures in percentages wherever possible (instead of pixels)
  • Avoid font and color assignments
  • Use CSS styles wherever possible
  • Only use input elements of the Form Controls Library
  • Don't initiate postbacks

Form Deployment

Forms can be deployed either statically or dynamically. Static deployment consists of simply copying the *.ascx files into the directory of the Web application. The form repository in this case is represented by the file system of the Web application.

With dynamic deployment, the Solution Provider offers an individual way to the Form Designer to store a form into a form repository. Such a repository can be a database, a remote or FTP folder, a Web service or any other storage means.

Forms at Runtime

Usage of forms at application runtime consists of the following phases:

Web User Forms Runtime

Loading of Form

The class UserFormLoader allows to load the form from a virtual path. To enforce application security, this ensures the form doesn't contain .NET CLR code. UserFormLoader uses reflection to determine the methods and fields of the form class, while ignoring those which are automatically generated by ASP.NET. To exclude other specific methods and/or fields, it is possible to provide a customized instance of a UserFormCodeValidator to the loading process.

In case the form contains .NET CLR code which is not allowed, the loading process will raise a FormSecurityException to prevent the use of that form. This effectively prevents the use of source code in scripts and embedded blocks:

<script runat="server" language="C#">

  public void MyPublicMethod( object sender, EventArgs e )
  {
    System.IO.DirectoryInfo dir = new System.IO.DirectoryInfo( @"C:\" );
    // do something with dir
  } // MyPublicMethod

  protected void MyProtectedMethod( object sender, EventArgs e )
  {
    System.Diagnostics.Process.Start( @"C:\AUTOEXEC.BAT" );
  } // MyProtectedMethod

  private void MyPrivateMethod( object sender, EventArgs e )
  {
    System.Threading.Thread.CurrentThread.Abort();
  } // MyPrivateMethod

</script>

<%Response.Write( "Embedded Code generated output." ); %>

The method UserFormLoader.LoadUnsafe() offers a way around this security measure and allows to load a form without this check. Use at your own risk ...

The forms get loaded from a virtual path, which normally represents a file in the local application directory. By inheriting from the class VirtualUserFormProvider, forms can be loaded from any other source:

// ------------------------------------------------------------------------
public class MyUserFormProvider : VirtualUserFormProvider
{

  // ----------------------------------------------------------------------
  public MyUserFormProvider( string basePath )
    : base( basePath )
  {
  } // MyUserFormProvider

  // ----------------------------------------------------------------------
  protected override Stream LoadUserForm( string virtualPath )
  {
    MemoryStream stream = null;

    // load Web-form from you source: file system, database, Web-Service...
    string formControlData = GetWebFormFromMySource( virtualPath );
    if ( !string.IsNullOrEmpty( formControlData ) )
    {
      stream = new MemoryStream( Encoding.Default.GetBytes( formControlData ) );
    }

    return stream;
  } // LoadUserForm

} // class MyUserFormProvider

The included VirtualUserFormFileProvider allows to load forms from any directory (see example VirtualPage in the RuntimeWebSite project).

Form Runtime Controls

In case the form allows runtime controls, the RuntimeControlsAdapter actually replaces the placeholder controls by the corresponding runtime controls. This process replaces all controls which implement the IPlaceholderControl interface.

The provided project RuntimeControls demonstrates the generation of runtime controls in the class ControlsAdapter. The property DatePicker.DateFormat demonstrates how to adapt to design time values.

Form Scripting

For forms which access a controls ID with Javascript at runtime using <%#TextBox1.ClientID%>, the page has to ensure proper binding by calling Page.DataBind():

// --------------------------------------------------------------------------
public partial class ClientScriptFormPage : System.Web.UI.Page
{

  // ------------------------------------------------------------------------
  protected override void OnLoad( EventArgs e )
  {
    base.OnLoad( e );
    DataBind(); // resolve data-binding expressions
  } // OnLoad

} // class ClientScriptFormPage

Lookups

Control of the forms lookups happens with the LookupAdapter, which uses the following objects:

Web User Forms Lookups

The LookupFieldCollector extracts all ILookupFields from a UserControl. By implementing an ILookupProvider, such lookup data can be transferred into the form from any sources:

// --------------------------------------------------------------------------
public partial class LookupsPage: System.Web.UI.Page, ILookupProvider
{

  // ------------------------------------------------------------------------
  protected override void OnLoad( EventArgs e )
  {
    // load form control
    UserCotrol userForm = new UserFormLoader( "~/MyUserForm.ascx" ).Load();

    // lookup
    LookupAdapter.Apply( this, userForm );

    FormPlaceHolder.Controls.Add( userForm );

    base.OnLoad( e );
  } // OnLoad

  // ------------------------------------------------------------------------
  ILookupValueCollection ILookupProvider.GetLookup( string lookupName, string formType )
  {
    LookupValueCollection lookup = null;
    switch ( lookupName )
    {
      case "MaritialStatus":
        lookup = new LookupValueCollection( lookupName );
        lookup.Add( new LookupValue( "Single" ) );
        lookup.Add( new LookupValue( "Married" ) );
        lookup.Add( new LookupValue( "Separated" ) );
        lookup.Add( new LookupValue( "Divorced" ) );
        lookup.Add( new LookupValue( "Widowed" ) );
        lookup.Add( new LookupValue( "Engaged" ) );
        lookup.Add( new LookupValue( "Annulled" ) );
        lookup.Add( new LookupValue( "Cohabitating" ) );
        lookup.Add( new LookupValue( "Deceased" ) );
        break;
    }
    return lookup;
  } // ILookupProvider.GetLookup

} // class LookupsPage

Form Variables

Expressions and variables in a form will be handled by the class VariableAdapter, using the following objects:

Web User Forms Variables

The ExpressionFieldCollector extracts all IExpressionFields from a UserControl. By implementing an IVariableProvider, such variable replacements can be provided from any source:

// --------------------------------------------------------------------------
public partial class VariablesPage: System.Web.UI.Page, IVariableProvider
{

  // ------------------------------------------------------------------------
  protected override void OnLoad( EventArgs e )
  {
    // load form control
    UserCotrol userForm = new UserFormLoader( "~/MyUserForm.ascx" ).Load();

    // expand variables
    VariableAdapter.ExpandVariables( this, userForm );

    FormPlaceHolder.Controls.Add( userForm );

    base.OnLoad( e );
  } // OnLoad

  // ------------------------------------------------------------------------
  IVariableSet IVariableProvider.GetVariables( string formType )
  {
    VariableSet variableSet = new VariableSet();

    variableSet.MapContentToVariable( "CurrentDate", DateTime.Now.ToString() );
    variableSet.MapContentToVariable( "CurrentUser", "John Doe" );

    return variableSet;
  } // IVariableProvider.GetVariables

} // class VariablesPage

Form Persistence

Form data can be loaded and saved (and thus exchanged) through the entity model. The class UserFormAdapter provides the necessary bridging between the UI and entity models. The following example demonstrates how to load form data from an XML file into its entity model representation and transfer that into the UI model (and from there back again into the XML file):

// --------------------------------------------------------------------------
public partial class PersistentPage : System.Web.UI.Page
{

  // ------------------------------------------------------------------------
  protected override void OnLoad( EventArgs e )
  {
    // load form control
    this.userForm = new UserFormLoader( "~/UserForms/MyUserForm.ascx" ).Load();

    FormPlaceHolder.Controls.Add( this.userForm );
    if ( !Page.IsPostBack )
    {
      LoadFormData();
    }

    base.OnLoad( e );
  } // OnLoad

  // ------------------------------------------------------------------------
  private void LoadFormData()
  {
    // file check
    string fileName = MapPath( virtualFileName );
    if ( !File.Exists( fileName ) )
    {
      return;
    }

    // load form data
    using ( StreamReader streamReader = new StreamReader( fileName ) )
    {
      IForm form = FormXml.Instance.Load( streamReader );
      UserFormAdapter.ApplyForm( this.userForm, form );
    }
  } // LoadFormData

  // ------------------------------------------------------------------------
  private void SaveFormData()
  {
    IForm form = UserFormAdapter.ExtractForm( this.userForm );

    string fileName = MapPath( virtualFileName );
    if ( File.Exists( fileName ) ) // update existing form
    {
      File.Delete( fileName );
      form.MarkUpdated( DateTime.Now, "DemoUser" );
    }
    else // new form
    {
      form.FormId = "1";
      form.SetCreated( DateTime.Now, "DemoUser" );
    }

    // ensure directory
    string directory = new FileInfo( fileName ).DirectoryName;
    if ( !Directory.Exists( directory ) )
    {
      Directory.CreateDirectory( directory );
    }

    // save data
    using ( StreamWriter streamWriter = new StreamWriter( fileName ) )
    {
      FormXml.Instance.Save( form, streamWriter );
      UserFormAdapter.ApplyForm( this.userForm, form );
    }
  } // SaveFormData

  // ------------------------------------------------------------------------
  protected void SaveButton_Click( object sender, EventArgs e )
  {
    SaveFormData();
  } // SaveButton_Click

  // ------------------------------------------------------------------------
  protected void LoadButton_Click( object sender, EventArgs e )
  {
    LoadFormData();
  } // LoadButton_Click

  // ------------------------------------------------------------------------
  // members
  private UserControl userForm;

  private const string virtualFileName = "~/Data/UserFormData.xml";

} // class PersistentPage

Form Commands

Handling of form commands is achieved through the class UserFormCommandManager. Aside from providing the event for command execution, the command manager also controls the enabling of the command(s):

// --------------------------------------------------------------------------
public partial class CommandPage : System.Web.UI.Page
{

  // ------------------------------------------------------------------------
  protected override void OnLoad( EventArgs e )
  {
    // load form control
    this.userForm = new UserFormLoader( "~/UserForms/MyUserForm.ascx" ).Load();

    this.commandManager = new UserFormCommandManager( this.userForm );
    this.commandManager.Command += new CommandEventHandler( FormCommand );

    FormPlaceHolder.Controls.Add( this.userForm );
    UpdateCommands( UserFormAdapter.ExtractForm( this.userForm ) );

    base.OnLoad( e );
  } // OnLoad

  // ------------------------------------------------------------------------
  private void UpdateCommands( IForm form )
  {
    this.commandManager.EnableCommand( commandFormLock, !form.IsLocked );
    this.commandManager.EnableCommand( commandFormUnlock, form.IsLocked );
  } // UpdateCommands

  // ------------------------------------------------------------------------
  private void ChangeFormLock( bool isLocked )
  {
    IForm form = UserFormAdapter.ExtractForm( this.userForm );
    form.IsLocked = isLocked;
    UpdateCommands( form );
  } // ChangeFormLock

  // ------------------------------------------------------------------------
  private void FormCommand( object sender, CommandEventArgs e )
  {
    switch ( e.CommandName.ToLower() )
    {
      case commandFormLock:
        ChangeFormLock( true );
        break;
      case commandFormUnlock:
        ChangeFormLock( false );
        break;
    }
  } // FormCommand

  // ------------------------------------------------------------------------
  // members
  private UserControl userForm;
  private UserFormCommandManager commandManager;

  private const string commandFormLock = "formlock";
  private const string commandFormUnlock = "formunlock";

} // class CommandPage

Form Utilities

There exist several helpers to control the editing status:

  • UserFormVisitor: Base class for iterating the UI model, based on the Visitor Pattern
  • Determining the available forms can be accomplished through an IUserFormProvider interface implementation (see VirtualUserFormProvider and VirtualUserFormFileProvider)
  • An implementation of the IFormProvider interface provides available form data from any data source
  • FieldEditEnabler: Activates or deactivates the editing mode of all form fields corresponding to the state of the corresponding IUserFormField.AllowEdit
  • ValidationEnabler: Activates or deactivates all validators
  • The base classes XmlBase/XmlSchemaBase used by FormXml/FormXmlSchema can be used to serialize arbitrary objects into/from XML

Future Extensions

The component introduced herein provides room for manifold extension possibilities which would go way beyond the scope of this article:

  • FormImageProvider: Insertion of images in forms
  • GridViewField: Table form field
  • Caching of virtual forms

History

  • 19th September, 2012 - v1.1.0.0
    • Removed support for Visual Studio 2005
    • Enhanced sample application
    • ReSharped source code
  • 3rd April, 2012
    • Added projects and solutions for Visual Studio 2010
    • ReSharped source code
    • Fixed article formatting
  • 21st January, 2009
    • Rewritten chapters Introduction and Solution Approach
    • UserFormCommandManager: changed binding using the control load/unload events
    • Refactored
  • 18th November, 2008
    • Changed article description
    • Updated article images
  • 7th November, 2008
    • Initial release

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