Tuesday, May 4, 2010

Custom XML web part with ASP.NET controls in the XSLT

Sometimes requirement from customer is very peculiar. For instance, once customer wanted a web part whose layout (positioning of controls within the web part, styling, design specs etc. ) can be changed by some authorized user without changing the web part code. Even he wants that the authorized user can add/remove controls from/to web part without code change. Now this is where the XML/XSLT comes into picture.

In this case we can either use the OOTB Data Form web part or we can create our own custom web part. I will take you through the custom web part implementation approach (just to demonstrate how exactly the XML/XSL transformation takes place).

Here for our custom web part, we'll put the ASP.NET controls in the XSLT (code will be shown below) and they will read their properties (ID, Text, CSS Class name) and default values from a XML file. These XML/XSL files can be stored in either a list/library or their values can be stored within a list item of a list/library. For our custom web part, we were storing the XSL file within the Style Library and the XML file as a list item of a list.

  1. Master XML File

The Master XML file will look like:


<?xml version="1.0" ?>
<CONTROLS xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<CONTROL Name="WebPartControl1">
<PROPERTIES>
<PROPERTY Name="Hidden" ID="Hidden" type="Boolean" controltype="CheckBox">
<VALUE>False</VALUE>
</PROPERTY>
<PROPERTY Name="List URL" ID="NavigationListName" type="string" controltype="TextBox">
<VALUE>lists/CustomList</VALUE>
</PROPERTY>
<PROPERTY Name="XSLT File Path" ID="XSLTFilePath" type="string" controltype="TextBox">
<VALUE>Style Library/customXSL/ListMenusTransformXSLT.xsl</VALUE>
</PROPERTY>
<PROPERTY Name="CSS Class Name" ID="CSS" type="string" controltype="TextBox">
<VALUE>leftNav</VALUE>
</PROPERTY>
<PROPERTY Name="Orientation" ID="Orientation" type="string" controltype="DropDownList">
<OPTIONS>
<OPTION>Horizental</OPTION>
<OPTION>Vertical</OPTION>
</OPTIONS>
<VALUE>Vertical</VALUE>
</PROPERTY>
</PROPERTIES>
</CONTROL>
</CONTROLS>

 

The XML above has defined the controls required in the web part "WebPartControl1". Each <PROPERTY></PROPERTY> node represents a control. It contains Check Boxes, Textboxes & Dropdown lists etc. we can add any type of control. The value of these controls will be stored in the <VALUE></VALUE> node.

2. Master XSLT File

The corresponding XSLT file will look like:


<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:asp="remove">

<!-- Parmeters passed through Code -->
<xsl:param name="controlID"></xsl:param>
<xsl:param name="mainCSSClass"></xsl:param>

<xsl:template match="CONTROLS">
<div class="{$mainCSSClass}">
<xsl:if test="string-length($controlID)!=0">

<asp:Panel ID="PROPERTIES" runat="server">
<table>
<!-- ControlID the the value of Control Node's Name attribute value -->
<xsl:for-each select="CONTROLS/CONTROL[@Name=$controlID]">
<div ID="propertiesDiv" class="propertiesDiv">
<table>
<!-- Iterate through all the property nodes -->
<xsl:for-each select="PROPERTIES/PROPERTY">
<tr>
<td>
<xsl:value-of select="@Name"/>
</td>
<td>
<!-- Read the value of the Property in a variable -->
<xsl:variable name="selectedValue">
<xsl:value-of select="VALUE"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="@controltype='DropDownList'">
<!-- Render Drop down list -->
<asp:DropDownList ID="{@ID}" runat="server">
<xsl:for-each select="OPTIONS/OPTION">
<xsl:choose>
<xsl:when test="$selectedValue=current()">
<asp:ListItem selected="selected">
<xsl:value-of select="current()"/>
</asp:ListItem>
</xsl:when>
<xsl:otherwise>
<asp:ListItem >
<xsl:value-of select="current()"/>
</asp:ListItem>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</asp:DropDownList>
</xsl:when>
<xsl:when test="@controltype='CheckBox'">
<!-- Render Check box control -->
<asp:CheckBox ID="{@ID}" runat="server">
<xsl:attribute name="Checked">
<xsl:value-of select="$selectedValue"/>
</xsl:attribute>
</asp:CheckBox>
</xsl:when>
<xsl:otherwise>
<!-- Render Text box control -->
<asp:TextBox ID="{@ID}" runat="server" >
<xsl:attribute name="Text">
<xsl:value-of select="$selectedValue"/>
</xsl:attribute>
</asp:TextBox>
</xsl:otherwise>
</xsl:choose>
</td>
</tr>
</xsl:for-each>
</table>
</div>
</xsl:for-each>
</table>
</asp:Panel>
<div ID="buttonDiv" class="buttonDiv">
<table>
<tr>
<td>
<asp:Button ID="btnSave" runat="server" Text="Save" />
</td>
<td>
<asp:Button ID="btnCancel" runat="server" Text="Cancel" />
</td>
</tr>
</table>
</div>
</xsl:if>
</div>
</xsl:template>
</xsl:stylesheet>

 

As you can see, here we are iterating through all the <PROPERTY></PROPERTY> nodes of the web part control whose ID is passed as a parameter into the XSLT. If the "ControlType" attribute of the control (in the XML, its PROPERTY) is Dropdown list, then we are adding a ASP.NET drop down control. The list items of this dropdown list are stored inside <OPTIONS><OPTION></OPTION></OPTIONS> node. Also the selected value is stored within the <VALUE></VALUE> node. We've assigned all the required properties of the ASP.NET control in XSLT. Similarly you can set the properties of Check box and Text box ASP.NET controls also. Also you can add other ASP.NET controls in the same way.

3. Web part Code

In the web part you need to read the XML data from the file or a list item. Then use the XSlTransform class to transform XML data into HTML using the XSL file. I will not go deep into this; however I'll write few lines of code for your reference which returns the HTML string data from the transformation of XML using the XSLT.


/// <summary>
/// Render xml/xsl into html using path of xsl and content of xml
/// </summary>
/// <param name="XMLString"> string formate of XML</param>
/// <returns></returns>
public virtual string RenderIntoHTML(string XMLString)
{
string XHTML = "";
string sXslUrl = "";
try
{
if (XMLString == null) { throw new System.ArgumentException("XSL/XML file is missing"); }
if (xslPath == null) { throw new System.ArgumentException("XSL file is missing"); }
sXslUrl = GetXslLocation + xslPath;

using (StringWriter swRenderedContents = new StringWriter())
{
if (xmlDocument == null) { xmlDocument = new XmlDocument(); }//creating objects
if (xslTransform == null) { xslTransform = new XslCompiledTransform(); }//creating objects
if (xslArguList == null) { xslArguList = new XsltArgumentList(); }//creating objects

xmlDocument.LoadXml(XMLString);

byte[] byteArray = System.Text.Encoding.UTF8.GetBytes(xslPath.Replace("\\\"", "\""));

MemoryStream streamd = new MemoryStream(byteArray);

XmlTextReader ob = new XmlTextReader(streamd);

xslTransform.Load(ob);
//xslTransform.Load(sXslUrl);



xslTransform.Transform(xmlDocument, xslArguList, swRenderedContents);

XHTML = swRenderedContents.ToString().Replace("<?xml version=\"1.0\" encoding=\"utf-16\"?>", "").Replace("xmlns:asp=\"remove\"", "").Replace("<br>", "<br/>").Replace("xmlns:SharePointWebControls=\"Microsoft.SharePoint.WebControls\"", "").Replace("xmlns:PublishingNavigation=\"Microsoft.SharePoint.Publishing.Navigation\"", "");
streamd.Close();
ob.Close();
}
}
catch (FileNotFoundException ex)
{

throw ex;
}
catch (Exception ex)
{

throw ex;
}
finally
{
xslTransform = null;
xslArguList = null;
xmlDocument = null;
}
return XHTML;

}

Now the most important section is parsing the HTML string into the ASP.NET controls. Therefore in the CreateChildControls method the web part, just after calling the above method we will write following code.

Control ctrl = null;

string strRenderHtml = string.Empty;

strRenderHtml = RenderIntoHTML(strXML);

ctrl = Page.ParseControl(strRenderHtml);

this.Controls.Add(ctrl);

We are using the ParseControl() method of the page to parse the HTML string into ASP.NET controls.

That's it. Done! Your ASP.Net controls defined in the XSLT are rendered in your web part without any custom rendering logic written in the web part code. Thus, if tomorrow customer wants to add new dropdown list/text box etc. in your web part, you just need to add corresponding nodes in the XML file. Also if you need to change the display of your web part (controls positioning change etc.), you just need to make these changes in the XSLT file without the web part code change.

No comments:

Post a Comment