|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Note: This is an unedited contribution. If this article is inappropriate,
needs attention or copies someone else's work without reference then please
Report This Article
IntroductionOne of the major disadvantages of using XSLTs in your code is the fact that you can't use ASP.NET controls inside the XSLT code. Well... at least until now! Why can't I use ASP.NET controls inside a XSLT?You can't use ASP.NET controls inside a 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. Remenber that ASP.NET pages are dynamically compiled on demand when first required in the context of a Web application. A possible solutionNow 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 that "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.Remenber 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 alredy 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 } DisadvantagesThe 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 ChallengeThe challenge is to make the XSLT/XML transformations, if any, before the ASP.NET page, for the current request, is created. The solutionTo 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 a HttpModule to capture all 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 a 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 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:
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:
You can have a 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 saying to my HttpModule that I have a 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? ConclusionI 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!
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||