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:
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
- Open the project in Visual Studio 2008.
- Create a new item -> Web Content Form in the BM.Tools.WebControls.MForm.Example project.
- Choose Site.Master as the Master page.
- A page will open and will look like this:
<%@ 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>
- Switch to Design mode.
- Find the
Root
control in the toolbox. - Drop the
Root
control on the page. It should look like this:
- Change the Designer mode to Edit
3. Click the load a root template from schema button
You will see the generator form:
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).
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:
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):
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:
<%@ 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 TextBox
es, 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:
- 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.
- 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.
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:
="1.0"="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.