Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / XHTML

Probably the best way to create and maintain ASP.NET Web Forms - An introduction to the MForm Web Controls

4.85/5 (15 votes)
23 Jul 2009Ms-PL10 min read 162K   463  
The article introduces the reader to the MForm Web Controls, a set of controls that provides a very productive and flexible way of creating ASP.NET Web Forms.

The source code

The project described in this article is hosted at mform.codeplex.com. You will find the most recent version (the project is recently updated) here. However, in case the link fails, you can always download a local copy - 3.42 MB.

Contents

Introduction

It is most likely that you have seen <quote> probably the best beer in the World commercial. Less chances are that you have heard of the MForm Web Controls. And, if you are like me, a web developer struggling with writing web forms that collect and process loads of data, you surely might find the described controls interesting.

Project background

The MForm Web Controls project started in the IT department of Bank Millennium, Poland, a place where I work, and has been recently published under the Microsoft Public License. If you have ever applied for a loan or a mortgage, you already know that banks want to know as much as possible about their clients to minimize the risk of giving too much money to a person that cannot afford returning it in the future. All of this data is filled in by bank consultants into the bank's intranet application during a meeting with the client.

The duty of a bank IT developer is then to:

  • define the data contract (a data structure and data restrictions) for the information that the business department wants to collect;
  • create a web form (or several web forms) that collects the data according to the data contract;
  • validate the fields in the web form against data restrictions;
  • combine the data from the fields into a data structure.

Finally, the data is sent somewhere else, where it is used to make a decision about the client.

What struck me, was that all of these tasks looked like they could be made at least partially automatically. The forms could be generated directly from the data contract (in our case, the data contract was defined using an XML schema), the validations (at least those basic ones as checking a field requirement, its maximal length, matching a pattern, or maximal value) could be also taken from the data contract, finally, the data combining (creating an output XML instead of a collection of fields) could be done automatically.

Still, the solution had to be extensible enough to:

  • allow modifying the created forms visually after the generation;
  • allow adding custom business validations that would work the same way data validations work;
  • make the process of creating the forms as easy as possible, so that even an inexperienced developer could prepare at least a web form sketch;
  • be efficient and use AJAX to eliminate unnecessary postbacks;
  • use generic ASP.NET controls and solutions wherever possible;

Generating your first MForm web form

You can also watch the video that goes through the next steps, here.

1. Get the contract

To be able to generate a form, you first need to have data definitions. It is best if you have these definitions in an XML schema (as an XML schema provides a way to declaratively define data restrictions not currently available in code), but if you are not familiar with XML schemas, you can also generate a form from a managed library.

In the following example, we will use a Customer data definition:

Image 1

The Customer has:

  • first and last names, which cannot be longer than 20 characters,
  • national ID,
  • birth date (of type date),
  • sex, which can be only of value 'Male' or 'Female'
  • and the address, which consists of:
    • street of maximal length 100,
    • house number of maximal length 10,
    • flat number, which is optional,
    • post code, which has to match the pattern two digits - three digits,
    • city of maximal length 50.

Assuming that you have already downloaded the project code, you will find the Customer data definition in the BM.Tools.WebControls.MForm.Example/Schemas/Customer.xsd file.

2. Create a page with the MForm Root control

  1. Open the project in Visual Studio 2008.
  2. Create a new item -> Web Content Form in the BM.Tools.WebControls.MForm.Example project.
  3. Choose Site.Master as the Master page.
  4. A page will open and will look like this:
  5. ASP.NET
    <%@ Page 
        Title="" 
        Language="C#" 
        MasterPageFile"~/Site.Master" 
        AutoEventWireup="true" 
        CodeBehind="WebForm1.aspx.cs" 
        Inherits="BM.Tools.WebControls.MForm.Example.WebForm1" 
    %>
        <asp:content runat="server" 
           contentplaceholderid="ContentPlaceHolder1" id="Content1">
    </asp:content>
  6. Switch to Design mode.
  7. Find the Root control in the toolbox.
  8. Drop the Root control on the page. It should look like this:
  9. Image 2

  10. Change the Designer mode to Edit

3. Click the load a root template from schema button

You will see the generator form:

Image 3

4. Choose the contract source and location

Click the Choose button and select the XML schema, WSDL, or assembly file. Then, click Open. The Element name dropdown list will be propagated with the available elements (an XML schema file may have more than one element definition, same as one assembly may have more than one class).

Image 4

5. Select the element

Once you select one of the elements from the dropdown list, the element tree will be displayed. You have a possibility to decide which definitions should be rendered in the form. By default, the whole tree is prepared to be rendered. To change this, uncheck the checkboxes near the elements:

Image 5

6. Click Generate

Once you click Generate, the Root control is filled in with the content. The Root designer mode is changed to View mode so you can see the result (the rendering in the designer mode is not perfect, the actual page display may vary):

generation_7.png

7. Switch to Source view

The generated form is available in the Source view and can be freely modified. We will take a closer look at the generated code:

ASP.NET
<%@ Page Title="" Language="C#" 
    MasterPageFile="~/Site.Master" AutoEventWireup="true"
    CodeBehind="WebForm1.aspx.cs" 
    Inherits="BM.Tools.WebControls.MForm.Example.WebForm1" %>

<%@ Register Assembly="BM.Tools.WebControls.MForm" 
    Namespace="BM.Tools.WebControls.MForm.Controls"
    TagPrefix="mf" %>
<%@ Register Assembly="BM.Tools.WebControls.MForm" 
    Namespace="BM.Tools.WebControls.MForm.Controls.Additions"
    TagPrefix="mfadd" %>
<%@ Register Assembly="BM.Tools.WebControls.MForm" 
    Namespace="BM.Tools.WebControls.MForm.Controls.ValueHandlers"
    TagPrefix="mfvh" %>
<asp:Content ID="Content1" 
           ContentPlaceHolderID="ContentPlaceHolder1" 
           runat="server">
    <mf:Root ID="Root1" runat="server" Name="Root1">
        <UriMappings>
            <mf:UriMapping Namespace="http://www.w3.org/2001/XMLSchema" Prefix="xs" />
            <mf:UriMapping Namespace="" Prefix="" />
        </UriMappings>
        <Contents>
            <mf:Branch runat="server" Name="Customer">
                <Contents>
                    <mf:Leaf runat="server" Name="FirstName" Ordinal="1">
                        <Additions>
                            <mfadd:Restriction runat="server" 
                               RestrictionType="MaxLength" Value="20" />
                        </Additions>
                    </mf:Leaf>
                    <mf:Leaf runat="server" Name="Surname" Ordinal="2">
                        <Additions>
                            <mfadd:Restriction runat="server" 
                              RestrictionType="MaxLength" Value="20" />
                        </Additions>
                    </mf:Leaf>
                    <mf:Leaf runat="server" Name="Sex" Ordinal="3">
                        <ValueHandler>
                            <mfvh:ListBoxValueHandler runat="server" Value="">
                                <ListBox Rows="1">
                                    <asp:ListItem Value="Female">Female</asp:ListItem>
                                    <asp:ListItem Value="Male">Male</asp:ListItem>
                                </ListBox>
                            </mfvh:ListBoxValueHandler>
                        </ValueHandler>
                    </mf:Leaf>
                    <mf:Leaf runat="server" Name="NationalId" Ordinal="4">
                    </mf:Leaf>
                    <mf:Leaf runat="server" DataType="Date" 
                       Name="BirthDate" Ordinal="5">
                    </mf:Leaf>
                    <mf:Branch runat="server" Name="Address" Ordinal="6">
                        <Contents>
                            <mf:Leaf runat="server" Name="Street" Ordinal="1">
                                <Additions>
                                    <mfadd:Restriction runat="server" 
                                       RestrictionType="MaxLength" Value="100" />
                                </Additions>
                            </mf:Leaf>
                            <mf:Leaf runat="server" Name="HouseNumber" Ordinal="2">
                                <Additions>
                                    <mfadd:Restriction runat="server" 
                                      RestrictionType="MaxLength" Value="10" />
                                </Additions>
                            </mf:Leaf>
                            <mf:Leaf runat="server" MinOccurs="0" 
                                       Name="FlatNumber" Ordinal="3">
                                <Additions>
                                    <mfadd:Restriction runat="server" 
                                       RestrictionType="MaxLength" Value="10" />
                                </Additions>
                            </mf:Leaf>
                            <mf:Leaf runat="server" Name="PostCode" Ordinal="4">
                                <Additions>
                                    <mfadd:Restriction runat="server" 
                                        RestrictionType="Pattern" Value="\d{2}-\d{3}" />
                                </Additions>
                            </mf:Leaf>
                            <mf:Leaf runat="server" Name="Post" Ordinal="5">
                                <Additions>
                                    <mfadd:Restriction runat="server" 
                                       RestrictionType="MaxLength" Value="50" />
                                </Additions>
                            </mf:Leaf>
                            <mf:Leaf runat="server" Name="City" Ordinal="6">
                                <Additions>
                                    <mfadd:Restriction runat="server" 
                                      RestrictionType="MaxLength" Value="50" />
                                </Additions>
                            </mf:Leaf>
                        </Contents>
                    </mf:Branch>
                </Contents>
            </mf:Branch>
        </Contents>
        <Validator Placement="BeforeContent"></Validator>
    </mf:Root>
</asp:Content>

The generated code in ASPX may look a little scary at first. There is quite a lot of data that has been generated. Of course, you are not needed to understand all of the generated code unless you want to change it somehow.

The Root control has three elements inside:

  • UriMappings
  • Contents
  • Validator

UriMappings

The UriMappings element keeps data about prefix and namespace mappings in the data definition. In the case of our example, no namespaces were used, so there is only one mapping, an empty prefix is mapped to an empty namespace.

Validator

The Validator element is responsible for displaying the form validation errors. It plugs the MForm tree in the ASP.NET page validation mechanism.

The Contents property

Finally, the Contents element is the most important part of the Root control. The Contents property is a template that is instantiated on page rendering, so it may contain any valid ASPX script code: HTML code, Web Controls, etc.. By default, it only contains the generated controls. You can see a Branch control with a property Customer. This control also has the Contents property, which again may contain any ASPX script code.

Branches and leafs

The Branch controls are those that represent data definitions that have children. The data definitions that collect text are represented by the Leaf controls. The Leaf controls do not contain the Contents property. Instead, they may contain the ValueHandler element, which provides a communication between the MForm tree and a control that should actually collect the data.

Value handlers

By default, all data is collected using TextBoxes, so the default value handler is TextBoxValueHandler. The MForm framework provides value handlers for the generic .NET form Web Controls. If you want to use your own custom control with the MForm framework, you have to implement a value handler for this control.

An example for a different value handler is in a Leaf called Sex. The data definitions allow only two values in this element: Male or Female. This has been ported to the form as a ListBox control that has two corresponding list items. If you prefer using a RadioButtonList instead of the ListBox, you can change the value handler to a RadioButtonListValuehandler, and its inner control to RadioButtonList.

Data type

Do you remember that our customer data definition had a BirthDate field, which was of type date. You can now see that the BirthDate leaf has a property DataType of value Date. This asserts that this field data must be provided in a valid date format.

Additions

Another definition that may be found inside the Leaf control is Additions. Additions may contain a list of controls that inherit from the Addition control. The Addition control adds some logic to the MForm control; it can, for example, add a data constraint. The Addition controls that can be found directly after generation are data restrictions taken from XML schema. So, if in our data contract, the PostCode field had to match the pattern: 2 digits - 3 digits, we will have a Restriction addition of restriction type Pattern and value \d{2}-\d{3} in a leaf called PostCode.

The Ordinal property

One last thing that is worth explaining is the Ordinal property.

The MForm Web Controls were designed to maximally facilitate the creation of Web Forms, but still to give the possibility of editing these forms after generation. We can freely modify a generated form, only remembering that the generated MForm children cannot be moved outside their parents.

So inside the Contents property, it is allowed to:

  • add some HTML code or controls;
  • place MForm child control inside these added controls (e.g., all Leaf controls can be placed inside a table);
  • extract a part of a generated code and place it in an external user control, and add a reference to this control inside Contents;
  • change the order of MForm siblings.

When creating an output XML from the tree of MForm controls, two factors are taken into account:

  1. The hierarchy of the XML nodes - if MForm children are not moved outside their parents, the MForm hierarchy remains intact, so the hierarchy of XML nodes will also be valid.
  2. The order of XML nodes - because the order of controls can be changed, the Ordinal property is generated. When the output XML is rendered, the MForm controls are sorted by the Ordinal property value, which asserts that the order of XML nodes is valid.

If you do not care about the order of XML nodes (e.g, deserializing XML to objects using the .NET XmlSerializer class in most of the cases totally ignores the order of XML nodes), you can turn off generation of the Ordinal property in the generator under the Options tab.

8. Run the page

Add an ASP Button to the page. Then set it as the start page and run the project. You will see that the form works and is validated. You can also see that the data is persisted during postbacks.

Image 7

By default, each of the restrictions has a default error text that is displayed. If you want to customize the displayed error text (in the example above, informing about the unmatched Regular Expression would not be very convenient for most users), just set the restriction's ErrorText property.

If you get the OutputXml property of the Root control after the form was filled in, you will get an XML document that conforms to the XML schema from which we have generated the form. This is much more convenient than taking care of each of the fields separately. Getting the OutputXml property does not require the form to be validated. So, if you clicked a button with no validation, the output XML of the form above would be:

XML
<?xml version="1.0" encoding="utf-16"?>
<Customer>
  <FirstName>Andrew</FirstName>
  <Surname>Broomstick</Surname>
  <NationalId>APD400003</NationalId>
  <BirthDate>1979-03-01</BirthDate>
  <Address>
    <Street>Universal</Street>
    <HouseNumber>34</HouseNumber>
    <PostCode>00-33</PostCode>
    <Post>Warsaw</Post>
    <City>Warsaw</City>
  </Address>
</Customer>

What's next?

I will try to post some more articles concerning the MForm Web Controls. The project is still in alpha stage, but we are very close to our first stable release. Until that time, you are welcome to familiarize yourself with the MForm Web Controls using the BM.Tools.WebControls.MForm.Example project. We will be most grateful for submitting bugs and comments at mform.codeplex.com. You can also see the example project at mform.org.

History

  • 2009-07-19 - The initial version of this article.
  • 2009-07-20 - Added a link to a video that follows the steps described in the article.
  • 2009-07-21 - Added a local copy of the source code, added contents to the page.
  • 2009-07-24 - Added a screen from running a page along with the output XML.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)