Kirk Evans Blog

.NET From a Markup Perspective

Dynamically Adding Web Controls and adding event delegates

Dynamically Adding Web Controls and adding event delegates

  • Comments 6

For the complete thread that led to this post, see the entire thread in the XML forum on ASP.NET.

I have played around with the Page.ParseControls method to add dynamically created controls to the page processing model before, but only really playing around.  I never tried to fire a server-side event for a dynamically created control. I really haven't found a great use for this technique, but I guess others have as I see a lot of posts on it in newsgroups.  

I thought that adding server events should be simple enough: just add the OnServerClick attribute to the HTML, and you should be golden:

System.Web.UI.Control button = Page.ParseControl("<input id=\"IDAHFUX\" name=\"IDAHFUX\" value=\"Click me!\" style=\"Z-INDEX:102;POSITION: absolute;TOP: 40px;LEFT: 10px\" type=\"button\" runat=\"server\"> OnServerClick=\"Button_Click\"");
Page.FindControl("WebForm1").Controls.Add(button);

What I found was that the OnServerClick method is not used to generate the postback handling, instead it is rendered to the client. The client has no idea what "onserverclick" is, so nothing happens and the server-side Button_Click event never fires. So, you have to set the event delegate yourself. That should be simple enough, so I decided to use XSLT to generate the controls.

The XML document I used is very simple:

<root>
    <foo/>
    <foo/>
    <foo/>
</root>

The XSLT to generate the controls is also pretty simple.  The only 2 weird things might be the use of attribute value templates (AVT's), noted by the "{ }" and the generate-id function.  I could have used a bunch of xsl:attribute tags to create the attributes, but AVT's are quite a bit shorter. The generate-id function is used to create a unique ID for each control (a requirement of ASP.NET), and 2 successive calls to generate-id for the same node context will return the same value.  That means the value in the ID and NAME attributes will be identical for each distinct node in the result tree.  I also used the node's position to manipulate both the TOP and Z-INDEX attributes of the control.

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="
http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="html" version="1.0"  />

 <xsl:template match="/root/foo">
  <input id="{generate-id()}" name="{generate-id()}" value="Click me!" style="Z-INDEX:{101 + position()};POSITION: absolute;TOP: {position() * 40}px;LEFT: 10px" type="button" OnServerClick="Button_Click" runat="server"/>
 </xsl:template>
</xsl:stylesheet>


There are a couple gotchas with the code.  The first is using a foreach to enumerate over the parsedControl.Controls collection.  If you replace the for loop with a foreach loop, you will receive an error that the collection has changed.  This is because we are setting an eventhandler for items contained in the collection, causing the error.  That explains the for loop instead of a foreach enumerator.   The second non-obvious thing in the code is that you might try to optimize the page processing by checking IsPostBack.  However, the controls will not be automatically added into the Page's Controls collection so the loop has to be performed each time the page is loaded.

<%@ Page language="c#"%>
<%@Import namespace="System.Xml"%>
<%@Import namespace="System.Xml.XPath"%>
<%@Import namespace="System.Xml.Xsl"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<HTML>
    <HEAD>
        <title>WebForm1</title>
    </HEAD>


<script language=C# runat=server>
private void Page_Load(object sender, System.EventArgs e)

    XPathDocument doc = new XPathDocument(Server.MapPath("xmlfile1.xml"));
    XslTransform trans = new XslTransform();
    trans.Load(Server.MapPath("xsltfile1.xslt"));

    System.IO.StringWriter writer = new System.IO.StringWriter();
    trans.Transform(doc,null,writer);   
    writer.Flush();
    System.Text.StringBuilder sb = writer.GetStringBuilder();

    System.Web.UI.Control parsedControl = Page.ParseControl(sb.ToString());
    HtmlForm form = (HtmlForm)Page.FindControl("WebForm1");
    for (int i=0;i< parsedControl.Controls.Count;i++)
    {
        HtmlInputButton button = parsedControl.Controls[i] as HtmlInputButton;
        if(null != button)
        {        
            button.ServerClick += new System.EventHandler(this.Button_Click);
            form.Controls.Add(button);      
        }
    }
}

public void Button_Click(object sender, System.EventArgs e)
{
    this.Label1.Text = "Button clicked at " + System.DateTime.Now.ToLongTimeString();

</script>


    <body MS_POSITIONING="GridLayout">
        <form id="WebForm1" method="post" runat="server">   
            <asp:Label id="Label1" style="Z-INDEX: 101; LEFT: 444px; POSITION: absolute; TOP: 77px" runat="server" Width="497px">Label</asp:Label>
        </form>
    </body>
</HTML>


By the way - I could have used code-behind for the Page_Load and Button_Click events, there is nothing special about this syntax.  I chose this format for brevity. 

<Kirk/>

  • Is there solution for dynamic including User Control instead Web controls?
  • ParseControl can parse user control, but you have to include relevant @Register directive with the markup to be parsed.
  • This was very nice to read. I was interested in this because I just started a new position where the app has been designed like ibuyspy, using user controls dynamically added to screens. We have a default.aspx page, where all pages are created in, dynamically on the fly based on menu selections. I'm realy not sure how good an idea this is. One issue we have is that the ispostback is not able to be used inside the user controls as it appears that it is always a post back. I was thinking that we could handle an event, that the main page would fire, on ispost back, an if each user control handled that event, we could get that type of processing to work. Thanks for you ideas and posting. It was very helpful to see this processing.
  • Hi, How to generate the User Control dynamically in ASP.NET.
  • http://www.dnzone.com/showDetail.asp?TypeId=2&NewsId=151&LinkFile=page3.htm
  • Thank you, I've been looking all over for the solution to adding event handlers for XSL/XML dynamic pages and you had the answer (more or less). Thanks a bunch!

    The only difference is I'm calling my methods from OnInit() instead of Page_load. Additionally, I'm skipped the ParseControl section and went directly after the specific controls I need to add events for - using FindControl("cntlName") (i.e. Submit, ClearForm, etc..)
Page 1 of 1 (6 items)
Leave a Comment
  • Please add 4 and 8 and type the answer here:
  • Post
Translate This Page
Search
Archive
Archives