We will go through the following steps to get the things
done:
1.
Prepare XML document
2.
Prepare XSLT document to transform XML data
3.
Define server controls according to XML document and event handler
4.
Define validation control according to XML document
5.
Parse Controls and Define Event Handler
Prepare XML Document
Our sample XML Document will look like:
Listing 1
<root>
<Employee Id="1">
<Address Caption="Address">
<Home Caption="Home 1">
<Street Caption="Street 1" Type="Text">Road# 27, House# 13, Banani</Street>
<Street Caption="Street 2" Type="Text"></Street>
<City Caption="City" Type="Text" Required="yes">Dhaka</City>
<Zip Caption="Zip" Type="Text">1213</Zip>
<Country Caption="Country" Type="CountryDDL">BD</Country>
</Home>
<Home Caption="Home 2">
<Street Caption="Street 1" Type="Text">Sector- 10</Street>
<Street Caption="Street 2" Type="Text">Uttara</Street>
<Street Caption="Street 3" Type="Text">
</Street>
<City Caption="City" Type="Text" Required="yes">Dhaka</City>
<Zip Caption="Zip" Type="Text">1230</Zip>
<Country Caption="Country" Type="CountryDDL">BD</Country>
</Home>
</Address>
</Employee>
<Employee Id="2">
<Address Caption="Address">
<Home Caption="Home 1">
<Street Caption="Street 1" Type="Text">J-13, Road 27</Street>
<Street Caption="Street 2" Type="Text">Banani</Street>
<City Caption="City" Type="Text">Dhaka</City>
<Zip Caption="Zip" Type="Text">1213</Zip>
<Country Caption="Country" Type="CountryDDL">BD</Country>
</Home>
<Home Caption="Home 2">
<Street Caption="Street 1" Type="Text">Michigan Avenue</Street>
<Street Caption="Street 2" Type="Text">Suite 2800</Street>
<Street Caption="Street 3" Type="Text"></Street>
<City Caption="City" Type="Text">Chicago</City>
<Zip Caption="Zip" Type="Text">60601</Zip>
<Country Caption="Country" Type="CountryDDL">USA</Country>
</Home>
</Address>
</Employee>
</root>
This is a very ordinary XML to keep track of employee
addresses. But if we look carefully then we will find that the XML document
contains some extra information in each node, which is not related with
addresses. I am explaining them one by one below.
Caption - will be used to show the caption of the value
Type - will explain the ASP.NET Server control type
Required - will saiy explicitly that the field is a required
field or non required field
We can also keep other information to make the process
easier in XSLT side, if needed.
Prepare XSLT document to transform XML data
To get specific Employee’s information among multiple employees'
data, we have to pass the employee's id to the XSLT document. We can do that by
simply passing the XSLT argument list to the XslCompiledTransform. The Transform
function looks like the following.
Listing 2
//create argument list
XsltArgumentList xslArg = new XsltArgumentList();
xslArg.AddParam("employeeId", "", CurrentEmployeeId);
//load the data
XPathDocument xdoc = new XPathDocument(Server.MapPath("Address.xml"));
//load Xslt
XslCompiledTransform transform = new XslCompiledTransform();
transform.Load(Server.MapPath("DynamicControls.xslt"));
StringWriter sw = new StringWriter();
//transform it
transform.Transform(xdoc, xslArg, sw);
The parameter can be received from XSLT document using
xsl:param which can be then used to get the specific employee's address.
Listing 3
<xsl:param name="employeeId"/>
<xsl:for-each select="Employee">
<xsl:if test="@Id = $employeeId">
<table width="100%" border="0" cellspacing="0"
cellpadding="0" bgcolor="#FFFFFF">
<tbody>
<tr>
<td>
<xsl:apply-templates select="Address">
</xsl:apply-templates>
Now after getting the specific Employee’s address, we have
to define the server controls for each child node (like street, city, country)
of address node that has been explicitly stated in XML document. Also, we have
to declare the required field validator for the required fields and captions
for the caption of the fields. We can also embed the validation logic (such as
regular expression) in XML which can be used here to check valid data.
Listing 4
<xsl:for-each select="child::*">
<xsl:variable name="rowindex" select="position()"></xsl:variable>
<td align="left" valign="top">
<table cellpadding="4">
<tr>
<td colspan="4" height="28" align="left" valign="top">
<strong>
<xsl:value-of select="@Caption"/>
</strong>
</td>
</tr>
<xsl:for-each select="child::*">
<xsl:variable name="varType" select="@Type"></xsl:variable>
<xsl:variable name="isRequired"
select="@Required"></xsl:variable>
<xsl:variable name="varId"
select="translate(concat(concat(concat(@Caption,'_'),$rowindex),position()),' ','_')">
</xsl:variable>
<xsl:variable name="up"
select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>
<xsl:variable name="lo"
select="'abcdefghijklmnopqrstuvwxyz'"/>
<tr>
<td height="28" align="left" valign="top">
<xsl:value-of select="@Caption"/>
</td>
<td colspan="3" align="left" valign="top" height="28">
<xsl:choose>
<xsl:when
test="translate($varType,$up,$lo)='countryddl'">
<asp:DropDownList
id="{concat('ddlCountry',$rowindex)}"
runat="server" DataTextField="Text"
DataValueField="Value">
<asp:ListItem value="{.}">
<xsl:value-of select="."/>
</asp:ListItem>
</asp:DropDownList>
</xsl:when>
<xsl:otherwise>
<asp:TextBox ID="{$varId}" runat="server" Text="{.}"
width="205px" ></asp:TextBox>
<xsl:if test="translate($isRequired,$up,$lo)='yes'">
<asp:RequiredFieldValidator
ErrorMessage=" Required Field" runat="server"
ControlToValidate="{$varId}" />
</xsl:if>
</xsl:otherwise>
</xsl:choose>
</td>
</tr>
</xsl:for-each>
</table>
</td>
</xsl:for-each>
In the above code we have used XML node's position to ensure
unique id for each server controls. Two variables, $up and $lo, have been used
to convert all the letters to lowercase since XLST does not have any built-in
functions to do this.
Parse Controls and Define Event Handler
XSLT will produce a plain XML string from which we have to
parse to ASP.Net server controls. We can accomplish the task by simply calling
Page.ParseControl function. Also, to get a reference of newly created controls,
we have to call the FindControl method. As soon as we get the reference of the
control, we can set the event handler for that control. One thing is very
important to know, the event handler adding process should be after parsing the
control. Before parsing, the control will not be available which can throw an exception.
Also, Parsing should be in Page_Init event so that the controls will be found
in the rest of the ASP.NET life cycle.
Listing 5
protected void Page_Init(object sender, EventArgs e)
{
if (Context.Request.QueryString["id"] != null)
{
ParseControls();
BindInfo();
}
}
private void ParseControls()
{
int EmployeeId = Convert.ToInt32(Context.Request.QueryString["id"]);
CurrentEmployeeId = EmployeeId;
//create argument list
XsltArgumentList xslArg = new XsltArgumentList();
xslArg.AddParam("employeeId", "", CurrentEmployeeId);
//load the data
XPathDocument xdoc = new XPathDocument(Server.MapPath("Address.xml"));
//load Xslt
XslCompiledTransform transform = new XslCompiledTransform();
transform.Load(Server.MapPath("DynamicControls.xslt"));
StringWriter sw = new StringWriter();
//transform it
transform.Transform(xdoc, xslArg, sw);
string result = sw.ToString();
//remove namespace
result = result.Replace("xmlns:asp=\"remove\"", "");
//parse control
Control ctrl = Page.ParseControl(result);
phEmployeeAddress.Controls.Add(ctrl);
}
private void BindInfo()
{
lblMessage.Text = "";
//find dropdown control and update datasource
try
{
DropDownList ddlCountry1 =
phEmployeeAddress.FindControl("ddlCountry1") as DropDownList;
string selectedValue1 = ddlCountry1.Items[0].Value.Trim();
ddlCountry1.DataSource = GetCountryList();
ddlCountry1.DataBind();
DropDownList ddlCountry2 =
phEmployeeAddress.FindControl("ddlCountry2") as DropDownList;
string selectedValue2 = ddlCountry2.Items[0].Value.Trim();
ddlCountry2.DataSource = GetCountryList();
ddlCountry2.DataBind();
//select the selected value
ddlCountry1.SelectedValue = selectedValue1;
ddlCountry2.SelectedValue = selectedValue2;
}
catch
{
//ex
throw;
}
//find control to add event handler
/*===============================================================
Note: Event handler adding process should be after parsing
* the controls since controls will be available after parsing
=================================================================*/
Button btnSaveAddress =
(Button)phEmployeeAddress.FindControl("btnSaveAddress");
btnSaveAddress.Click += new EventHandler(btnSaveAddress_Click);
DropDownList ddlEmployee =
(DropDownList)phEmployeeAddress.FindControl("ddlEmployee");
ddlEmployee.SelectedValue = CurrentEmployeeId.ToString();
ddlEmployee.SelectedIndexChanged+=
new EventHandler(ddlEmployee_SelectedIndexChanged);
}
We have almost covered everything. The most interesting
thing is that XSLT will make our ASPX page very simple and clear looking which
will increase the maintainability. Here is the full ASPX source.
Listing 6
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="edit-address.aspx.cs"
Inherits="edit_address"%>
<span style='background:yellow'> </span>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Dynamic ASP.net Controls Using Xslt</title>
</head>
<body>
<form id="form1" runat="server">
<div style="float: right; background-color: Yellow">
<asp:Label ID="lblMessage" runat="server" Text=""></asp:Label>
</div>
<div>
<asp:PlaceHolder ID="phEmployeeAddress" runat="server"></asp:PlaceHolder>
</div>
</form>
</body>
</html>
All the server controls will be extracted to the place
holder.
Note: To get the best performance, I will recommend that you
store the XML and XSLT in database side and use a caching technique to retrieve
the XSLT template. This way the application will be boosted up in such a way
for the large amount of data (which will be unbelievable!!!).