Introduction

Sometimes you need to create dynamic pages. You may allow users to build their own applications by selecting the fields they need. In this article, I will show how to build dynamic applications by using ASP.NET and XML. In this example, I use XML for meta-programming or generative programming. I use XML with an XSLT to generate ASP.NET PRE, utilizing intrinsic ASP.NET parsing methods with the XSLT output. Steps that we need to do:
  • Create XML file (schema of our page).
  • Create an XSLT style to transform XML into ASP.NET PRE.
  • Transform XML, and create server controls defined by the generated ASP.NET PRE at runtime.
  • Insert the instantiated controls into the page's control collection.
  • Handle postback events from server controls.
First, what we need to come up with is a good structure of our page in a XML file. A basic page structure is defined below. The example structure consists of field elements, with attributes, properties and listitems.
Collapse
<FORM>
<PAGES>
<PAGE title="Page Title" id="page_1">
<FIELDS>

<!-- create regular TextBox -->
<FIELD type="TextBox" label="Last Name:" required="true">
<PROPERTIES>
<PROPERTY name="ID">LAST_NAME</PROPERTY>
</PROPERTIES>
</FIELD>

<!-- create multiline TextBox (textarea) -->
<FIELD type="TextBox" label="Description:">
<PROPERTIES>
<PROPERTY name="ID">DESCRIPTION</PROPERTY>
<PROPERTY name="TextMode">MultiLine</PROPERTY>
<PROPERTY name="Cols">30</PROPERTY>
<PROPERTY name="Rows">2</PROPERTY>
</PROPERTIES>
</FIELD>

<!-- regular textbox prefilled with text -->
<FIELD type="TextBox" label="Prefilled Contrl:">
<PROPERTIES>
<PROPERTY name="ID">PREFILLED_CONTROL</PROPERTY>
<PROPERTY name="Text">Some Text</PROPERTY>
</PROPERTIES>
</FIELD>

<!-- regular text box which must filled
and allowed Date datata type
-->

<FIELD type="TextBox" label="Start Date:"
required="true" validation="Date">
<PROPERTIES>
<PROPERTY name="ID">START_DATE</PROPERTY>
</PROPERTIES>
</FIELD>

<!-- create select box -->
<FIELD type="DropDownList" label="Title:">
<PROPERTIES>
<PROPERTY name="ID">TITLE</PROPERTY>
</PROPERTIES>
<LISTITEMS>
<LISTITEM value="">Select One</LISTITEM>
<LISTITEM value="1">Architector</LISTITEM>
<LISTITEM value="2">Sr. Developer</LISTITEM>
<LISTITEM value="3">Programmer</LISTITEM>
<LISTITEM value="4">Web Designer</LISTITEM>
</LISTITEMS>
</FIELD>

<!-- radio buttons list -->
<FIELD type="RadioButtonList" label="Are you US citizen?">
<PROPERTIES>
<PROPERTY name="ID">IS_US_CITIZEN</PROPERTY>
<PROPERTY name="RepeatColumns">1</PROPERTY>
<PROPERTY name="RepeatDirection">Vertical</PROPERTY>
<PROPERTY name="RepeatLayout">Table</PROPERTY>
<PROPERTY name="TextAlign">Right</PROPERTY>
</PROPERTIES>
<LISTITEMS>
<LISTITEM value="1">Yes</LISTITEM>
<LISTITEM value="0">No</LISTITEM>
</LISTITEMS>
</FIELD>

<!-- check box list -->
<FIELD type="CheckBoxList" label="Languages:">
<PROPERTIES>
<PROPERTY name="ID">LANGUAGES</PROPERTY>
<PROPERTY name="RepeatColumns">1</PROPERTY>
<PROPERTY name="RepeatDirection">Vertical</PROPERTY>
<PROPERTY name="RepeatLayout">Table</PROPERTY>
<PROPERTY name="TextAlign">Right</PROPERTY>
</PROPERTIES>
<LISTITEMS>
<LISTITEM value="C#">C#</LISTITEM>
<LISTITEM value="Java">Java</LISTITEM>
<LISTITEM value="VB">Visual Basic</LISTITEM>
</LISTITEMS>
</FIELD>

<!-- here is a hyperlink (a) -->
<FIELD type="HyperLink">
<PROPERTIES>
<PROPERTY name="ID">LINK</PROPERTY>
<PROPERTY name="NavigateUrl">javascript:void(alert('Hello ooo'));
</PROPERTY>
<PROPERTY name="Text">Say Hello</PROPERTY>
</PROPERTIES>
</FIELD>

<FIELD type="html" src="file path"/>

<!-- you can place here any other asp controls -->

</FIELDS>
</PAGE>

...

</PAGES>
</FORM>
Next step is to create XSLT style to transform our XML schema The XSLT iterates through each field, first outputting a label for the field as plain text. The stylesheet also checks if the field is required, and adds a RequiredFieldValidator if needed. The stylesheet then creates a Web Control (it could be any valid web control such as TextBox, RadioButtonList, DropDownList, etc.). ListItems are created for each of the ListControls.
Collapse
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:asp="remove">
<xsl:output method="xml" indent="yes" encoding="utf-8" omit-xml-declaration="yes">
</xsl:output>
<xsl:template match="/">

<!-- page id parameter if we have more than 1 page in our XML file -->
<xsl:param name="pageid"/>

<!-- start form page -->
<table cellpadding="0" cellspacing="5">

<!-- set title of the current page -->
<tr>
<td colspan="3" align="center" style="font-size:25px">
<xsl:value-of select="FORM/PAGES/PAGE[@id=$pageid]/@title" />
</td>
</tr>
<tr><td colspan="3" style="height:20px"></td></tr>

<!-- iterate through page fields -->
<xsl:for-each select="FORM/PAGES/PAGE[@id=$pageid]/FIELDS/FIELD">

<!-- create row -->
<xsl:element name="tr">
<xsl:attribute name="id">
TR_<xsl:value-of select="PROPERTIES/PROPERTY[@name='ID']"></xsl:value-of>
</xsl:attribute>

<!-- hide the row -->
<xsl:if test="@display='none'">
<xsl:attribute name="style">display:none;</xsl:attribute>
</xsl:if>

<xsl:choose>

<!-- this is the way to place "include" files into your PRE -->
<xsl:when test="@type='HTML'">
<td colspan="3">
<!-- #include file="<xsl:value-of select="@src"></xsl:value-of>" -->
</td>
</xsl:when>

<!-- other controls -->
<xsl:otherwise>

<!-- field label column -->
<td valign="top">
<xsl:value-of select="@label" />
</td>

<!-- field column -->
<td>

<!-- field element -->
<xsl:element name="asp:{@type}">
<xsl:attribute name="runat">server</xsl:attribute>
<xsl:for-each select="./PROPERTIES/PROPERTY">
<xsl:attribute name="{@name}">
<xsl:value-of select="current()"></xsl:value-of>
</xsl:attribute>
</xsl:for-each>
<xsl:for-each select="./LISTITEMS/LISTITEM">
<asp:ListItem value="{@value}">
<xsl:value-of select="current()"></xsl:value-of>
</asp:ListItem>
</xsl:for-each>
</xsl:element>
</td>

<!-- validation message column -->
<td>
<xsl:if test="@required='true'">
<asp:RequiredFieldValidator ErrorMessage="Required" runat="server"
ControlToValidate="{PROPERTIES/PROPERTY[@name='ID']}" />
</xsl:if>

<xsl:if test="@validation='Date'">
<asp:CompareValidator ErrorMessage="Dates Only" runat="server"
Operator="DataTypeCheck" Type="Date"
ControlToValidate="{PROPERTIES/PROPERTY[@name='ID']}" />
</xsl:if>

<xsl:if test="@validation='Number'">
<asp:CompareValidator ErrorMessage="Numbers Only" runat="server"
Operator="DataTypeCheck" Type="Integer"
ControlToValidate="{PROPERTIES/PROPERTY[@name='ID']}" />
</xsl:if>

<xsl:if test="@validation='Currency'">
<asp:CompareValidator ErrorMessage="Currency Only" runat="server"
Operator="DataTypeCheck" Type="Currency"
ControlToValidate="{PROPERTIES/PROPERTY[@name='ID']}" />
</xsl:if>
</td>
</xsl:otherwise>

</xsl:choose>
</xsl:element>

</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
Our XSLT file has a prefix (xmlns:asp="remove") defined for the XSL namespace. This is used to generate plain HTML. Now we are ready to write our Page transformer.
Collapse
...
private readonly string XslFile = @"...\default.xslt";
private readonly string XmlFile = @"...\default.config";

...
private void Page_Load(object sender, System.EventArgs e)
{
string pageId = "page_1";

if (!Page.IsPostBack)
{

/**
* Transform aspx page with fields from xml file
* Get transform result as a string
* Parse controls into a parent control holder
*/


XmlDocument xdoc = new XmlDocument();
xdoc.Load(XmlFile);

// load xslt to do transformation
XslTransform xsl = new XslTransform();
xsl.Load(XslFile);

// load xslt arguments to load specific page from xml file
// this can be used if you have multiple pages
// in your xml file and you loading them one at a time
XsltArgumentList xslarg = new XsltArgumentList();
xslarg.AddParam("pageid", "", pageId);

// get transformed results
StringWriter sw = new StringWriter();
xsl.Transform(xdoc, xslarg, sw);
string result = sw.ToString().Replace("xmlns:asp=\"remove\"",
"").Replace("&lt;","<").Replace("&gt;",">");
// free up the memory of objects that are not used anymore
sw.Close();

// parse the controls and add it to the page
Control ctrl = Page.ParseControl(result);
Page.Controls.Add(ctrl);
}
}

private void BtnSave_Click(object sender, System.EventArgs e)
{
// save data into a data container
// ...
Response.Write (Request.Form["LAST_NAME"]);
}

Conclusion

In this article, we separate data from content to make a cleaner design and for better maintainability. When XML is combined with XSLT, ASP.NET server controls become even more powerful. This opens up numerous possibilities for dynamic and robust systems.