Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

How to use ASP.NET controls inside XSLTs

0.00/5 (No votes)
15 Feb 2007 1  
Shows you how to generate ASP.NET controls from XSLT.

Introduction

One of the major disadvantages of using XSLTs in your code is the fact that you can't use ASP.NET controls inside XSLT code. Well... at least until now!

Why can't I use ASP.NET controls inside XSLT?

You can't use ASP.NET controls inside XSLT because when the XSLT/XML transformation is made, the page that is dynamically created and contains all the ASP.NET controls is already created. Remember that ASP.NET pages are dynamically compiled on demand when first required in the context of a Web application.

A possible solution

Now, suppose you've some ASP.NET controls inside your XSLT that you want to use.

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt"
    xmlns:asp="remove"
    exclude-result-prefixes="msxsl vt">
    
    <xsl:output method="html" version="4.0"/>

    <xsl:template match="/">
        <div>
            <asp:TextBox runat="server" 
              ID="NomeCompleto"><xsl:value-of select="c"/></asp:TextBox>
        </div>
    </xsl:template>
</xsl:stylesheet>

Note that I've declared the asp namespace so the XSLT processor knows the "new" namespace.

You have to execute the XSLT/XML transformation, store the HTML result in a string, and remove the string xmlns:asp="remove" from it.

The next thing you need to do is execute the ParseControl method, passing the string with the HTML of the transformation as a parameter. This method returns a reference to a control. In fact, this control has all the ASP.NET controls that were inside your XSLT.

Next, you have to add that collection of controls to your page's control collection, or if you want, to a placeholder control collection.

Remember that all this code must be executed on the Init event of the page, because on the Init event, the viewstate isn't already restored and server-side events aren't already fired. To get a reference to one of the newly created controls, you have to call the FindControl method... for each control that you want to use.

private void Page_Init(object sender, System.EventArgs e)
{
    string xml = // string com o xml
    string html = XMLFunctions.GetXSLXMLFile(
       Server.MapPath("../XSL/A.xslt"), xml);  // Do the transform...

    html = html.Replace("xmlns:asp=\"remove\"", String.Empty);//Retirar o namespace
    Control ctrl = ParseControl(html);//Get the control collection
    OutPutPlace.Controls.Add(ctrl);// OutPutPlace is a placeholder

    // Register some events
    Button okBtn = this.Page.FindControl("OK") as Button;
    // Find a Button that was inside the XSLT

    okBtn.Click += new System.EventHandler(this.OK_Click);// Registar o evento
}

Disadvantages

The fact that you've to call the ParseControl method to get the control collection, and the calls to FindControl to get a reference to a control.

The challenge

The challenge is to make the XSLT/XML transformations, if any, before the ASP.NET page for the current request is created.

The solution

To meet the challenge, we have to run some code inside the HTTP Pipeline before the PageHandlerFactory runs. The page handler factory is responsible for either finding the assembly that contains the page class or dynamically creating an ad hoc assembly.

I use an HttpModule to capture all the requests to my ASP.NET pages. If the requested page has a special element that I've defined ("AspXsl:Out"), then the module must make an XSLT/XML transformation.

Obviously, the result of the transformation can't be injected in the current page because then I would lose the information that I need to make the transformation itself, such as the XSL file and the XML file or method to invoke to get the XML.

So, I create a new page with the same name as the page that was requested and save it in a temporary directory that I've called DinTemp. In this new page, I inject the HTML of the XSLT/XML transformations.

When all transformations are done, the HTTP Pipeline work is redirected to the newly created page.

To improve performance, I save the path of the newly created file in the cache with the following FileDependecies:

  • The requested page itself
  • All the XSLT files that are needed to create the final ASP.NET page

This way, any changes made to one of the listed files will cause my module to recreate the temporary ASP.NET file with the result of all XSLT/XML transformations.

Time to see some code.

private void app_BeginRequest(object sender, EventArgs e)
{
    HttpApplication app = (HttpApplication)sender;
    HttpRequest request = app.Request;
    HttpContext context = app.Context;

    if(request.Url.AbsoluteUri.IndexOf("DinTemp")==-1)
    {
        string siteName = GetSiteName(request.Url);
        string pathPageName = GetRequestedPagePath(request.Url, siteName);
    
        if(context.Cache[pathPageName]==null)
        {
            // If the cache entry is invalid, then we must rewrite the new ASP.NET page
            #region Do work
            string pageName = GetRequestedPageName(request.Url);
            string dinPagePath = 
              context.Server.MapPath(siteName+@"DinTemp\")+pathPageName;
            // Get the final html from the requested ASP.NET page.
            // The ProcessHtml will save a new entry in the cache.
            string html = ProcessHtml(siteName, pageName, 
                                      context.Server.MapPath( pageName ), context);
            // Write the new ASP.NET file
            using (StreamWriter sw = new StreamWriter(dinPagePath)) 
                sw.Write(html);
            #endregion
        }
        RedirectToGeneratedPage(request, context, @"\DinTemp\"+pathPageName);
    }
}

Great! How can I use this module in my ASP.NET pages?

The new element that this module uses is AspXsl:Out, and has the following attributes:

  • Xsl: The path to the XSL file
  • XmlFile: The path to the XML file
  • XmlMethod: The method of the page to call to get the XML

You can have an ASP.NET page called C.aspx with this code:

<%@ Page Trace="true" language="c#" 
    Codebehind="C.aspx.cs" AutoEventWireup="false" Inherits="AspXsl.C" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<HTML>
    <HEAD>
        <title>C</title>
        <meta name="GENERATOR" Content="Microsoft Visual Studio .NET 7.1">
        <meta name="CODE_LANGUAGE" Content="C#">
        <meta name="vs_defaultClientScript" content="JavaScript">
        <meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5">
    </HEAD>
    <body>
        <form id="Form1" method="post" runat="server">
            <div>
                <AspXsl:Out id="target" Xsl="Xsl/C.xslt" XmlMethod="GetXml" />
            </div>
            <div>
                <asp:Button Runat="server" ID="ok" Text="Post it!" />
            </div>
            <div>
                <asp:Label Runat="server" ID="Msg" />
            </div>
        </form>
    </body>
</HTML>

Notice that AspXsl:Out element again? I'm telling my HttpModule that I have an XSLT file in "Xsl/C.xslt" and I want to transform the XML that the GetXml method returns.

Let's look at the code-behind:

namespace AspXsl
{
    /// <summary>
    /// Summary description for C.
    /// </summary>
    public class C : System.Web.UI.Page
    {
        protected System.Web.UI.WebControls.Button ok;
        protected System.Web.UI.WebControls.Label Msg;
        
        protected TextBox NomeCompleto;// Inside the XSLT

        private void Page_Load(object sender, System.EventArgs e)
        {
        }

        public static string GetXml(HttpRequest request){return "<c>Xml is fun</c>";}

        private void ok_Click(object sender, System.EventArgs e)
        {
            Msg.Text = "Olá "+NomeCompleto.Text;
        }
    }
}

See? The code looks like the code that you have in your other ASP.NET pages! How great is that?

Conclusion

I hope that with this HttpModule, you can get the best of the two worlds: XSLT and ASP.NET. You have the code for the HttpModule and a demo web app as well to try! As always, tell what you think!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here