Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
Markdown Monster - The Markdown Editor for Windows

Understanding how <% %> expressions render and why Controls.Add() doesn't work


:P
On this page:

I’ve been trying to understand the problem of the inability to use Controls.Add()  when <% %> tags are present in a container for some time and today as I’m working on this page I finally understand why this is a problem while I’m looking at the innards of the generated classes that ASP.NET 2.0 creates dynamically from the ASPX markup…

 

When a container contains static text or plain control definitions the entire control parsing is set up by using AddParseObject() to the control. So if I have a container that contains some literal text and a text box the parse tree method for the container generated by ASP.NET will look like this:

 

private global::System.Web.UI.WebControls.Panel @__BuildControlPanel2() {

    global::System.Web.UI.WebControls.Panel @__ctrl;

   

    @__ctrl = new global::System.Web.UI.WebControls.Panel();

    this.Panel2 = @__ctrl;

    @__ctrl.ApplyStyleSheetSkin(this);

    @__ctrl.ID = "Panel2";

   

    System.Web.UI.IParserAccessor @__parser = ((System.Web.UI.IParserAccessor)(@__ctrl));

   

 

    @__parser.AddParsedSubObject(new System.Web.UI.LiteralControl("\r\n        here\'s some literal text:\r\n        "));

   

    global::System.Web.UI.WebControls.TextBox @__ctrl1;

    @__ctrl1 = this.@__BuildControltxtPanel2();

    @__parser.AddParsedSubObject(@__ctrl1);

    @__parser.AddParsedSubObject(new System.Web.UI.LiteralControl("\r\n        "));

   

    return @__ctrl;

}    

 

All of the control layout and formatting for the Panel is handled in the Build method which creates the instance and adds the controls to the parse tree.

 

If the Panel also contains markup text (for example adding a simple <%= DateTime.Now %> and script block) the Build routine only handles adding the controls, but doesn’t handle any of the layout tasks – it doesn’t create literal controls. Instead it uses SetRenderMethodDelegate() to delay the control layout task until render time.

 

Assume we have a Panel on a page that looks like this:

 

<asp:Panel ID="Panel1" runat="server" Height="251px" Width="517px">

    <%= DateTime.Now %>

    <asp:GridView ID="grdListInPanel" runat="server">

    </asp:GridView>

    <%

        int x = 4;

        Response.Write(x);

    %>

    <br />

    <asp:Button ID="btnGoInPanel" runat="server" OnClick="Button1_Click" Text="Button" />

</asp:Panel>

 

The generated Parse Tree method then looks something like this:

 

private global::System.Web.UI.WebControls.Panel @__BuildControlPanel1() {

    global::System.Web.UI.WebControls.Panel @__ctrl;

   

    @__ctrl = new global::System.Web.UI.WebControls.Panel();

   

    this.Panel1 = @__ctrl;

    @__ctrl.ApplyStyleSheetSkin(this);

    @__ctrl.ID = "Panel1";

    @__ctrl.Width = new System.Web.UI.WebControls.Unit(517, System.Web.UI.WebControls.UnitType.Pixel);

   

 

    global::System.Web.UI.WebControls.GridView @__ctrl1;

    @__ctrl1 = this.@__BuildControlgrdListInPanel();

 

    System.Web.UI.IParserAccessor @__parser = ((System.Web.UI.IParserAccessor)(@__ctrl));

   

    @__parser.AddParsedSubObject(@__ctrl1);

   

    global::System.Web.UI.WebControls.Button @__ctrl2;

    @__ctrl2 = this.@__BuildControlbtnGoInPanel();

    @__parser.AddParsedSubObject(@__ctrl2);

   

    @__ctrl.SetRenderMethodDelegate(new System.Web.UI.RenderMethod(this.@__RenderPanel1));

    return @__ctrl;

}

 

Notice that the Build method does nothing with the <% %> blocks and doesn't inject any literal controls. All of this is deferred into the RenderPanel1 method. A Render method for a control is only created IF there is markup script in the container. Otherwise the rendering is managed entirely through the Controls collection of the container control. The Render method of the Panel that contains the markup code then looks like this:

 

private void @__RenderPanel1(System.Web.UI.HtmlTextWriter @__w, System.Web.UI.Control parameterContainer) {

    @__w.Write("\r\n            ");   

    @__w.Write( DateTime.Now );

     

    @__w.Write("\r\n            ");

    parameterContainer.Controls[0].RenderControl(@__w);

    @__w.Write("\r\n            ");

   

        int x = 4;

        Response.Write(x);

   

    @__w.Write("\r\n            <br />\r\n            ");

    parameterContainer.Controls[1].RenderControl(@__w);

    @__w.Write("\r\n        ");

}

 

Notice that the method writes out literal code directly into an HTML TextWriter and simply embeds <%= DateTime.Now %> as a simple writer.Write() call – very efficient. The code snippet that was embedded in the panel (the int x=4 and Response.Write()) is directly embedded into the class method as pure code that executes at rendering.

 

ASP.NET generates this class at compile time, and notice that the Control indexes are hard coded. It’s clear that if you tried to inject a control into the Controls collection here wouldn’t have any effect at all – the control would never render since the control collection is not being accessed by using the Enumerator.

 

There are a couple of questions that spring to mind here. SetRenderMethodDelegate() allows this class to hook the container’s Rendering pipeline, but why isn’t the container rendering the controls that were added to the collection? If I understand the way that Render() and SetRenderMethodDelegate() works shouldn’t the original Render() of the Panel still fire? At least that’s what I’m seeing…

This should be fixable by the ASP.NET engine

The other thing that comes to my mind is that this problem of not allowing <% %> and Control.Add() should be relatively easy to overcome.  Instead of creating this custom RenderMethod() for the code, it seems to me that <% %> expressions could be created as separate methods of the generated class. You could then have a DynamicExpression control that gets added to the control tree and sets up delegates that call these custom methods.

 

I can see some problems with this for complex code snippets. Some things you can do with snippets at least couldn’t be handled by calling out to a delegate. For example something like this:

 

<% if (!this.Item.IsInStock)

   {

       

%>

Out of Stock (re-stock) <%= this.Item.DeliveryDate %>)

<% } %>

 

Ok, not a great example, since that sort of thing is much better encapsulated in a control and driven from CodeBehind, but ASP.NET does allow this sort of syntax. However, for those that use code or expressions in a page I think usage like the above is rare.

 

But it seems that at least <%= %> expressions COULD be handled by an external callout mechanism. Code snippets are much less likely to be in a page, but <%= %> expressions are often unavoidable, especially in script code where you might frequently need  <%= this.txtName.ClientID %> to properly identify a control in the page. There are no easy workarounds for this particular problem short of sticking a literal control in the page for each and assign each one of them – Not.


The Voices of Reason


 

Vadivel Kumar
June 25, 2006

# re: Understanding how &amp;lt;% %&amp;gt; expressions render and why Controls.Add() doesn't work

What I think is the reason that ASP.NET rendering engine doesn't render the inline expressions is because of that the way custom controls are to be developed should be independent of any layouts, that is the reson we talk lot of keepin the layout and others processes separately. Because layouts might often change according to the needs.

So, when it comes to custom controls you can even bind the data to an embedded webcontrol in CreateChildControl() method.

IMHO, I dont see that putting the <%=%> expressions in the ASPX page is not a proper or proffessional way of doing the things.

As you told <%=%> expressions are "unavoidable" - yes, it is true. But, while developing an custom or user control it is pretty much avoidable if the layout is separated from the custom control implementation.

I often use Control.FindControl() and load the control thru overriden CreateChildControls() method, so that the layout (.ascx) can be entirely modified according to the designer's need.

What do you feel?

-
Vadi

Rick Strahl
June 25, 2006

# re: Understanding how &amp;lt;% %&amp;gt; expressions render and why Controls.Add() doesn't work

Hi Vadivel,

I agree. If you create custom controls or even user controls you should avoid script markup tags. Heck even in normal page code you should avoid it. But the reality is that in some cases you can't easily do this. Also remember that <%= %> is vastly more efficient than even a literal control so there is code out there that capitalizes on that.

But the point I'm making is not that a control needs to use <% %> tags, but a control might need to manipulate another control on the page in a container that DOES have <% %> tags in them. In that case Controls.Add() doesn't work and that's when it becomes a problem. If you build controls that extend other controls on the page especially this is an issue and I've been doing that a lot lately...

I would like to see the problem minimized as much as possible so that you have a consistent model for getting controls added to a container.

Rick Strahl's Web Log
November 12, 2006

# Adding Rendering with SetRenderMethodDelegate() - aeh, sort of - Rick Strahl's Web Log

I ran into SetRenderMethodDelegate today and was thinking it'd be very useful for injecting HTML into a control. Unfortunately it turns out that the delegate is not fired on all controls as there's a dependency on base.Render() calls which apparently are not made by all controls even the stock controls.

Lucy Blain
November 17, 2006

# Understanding how <% %> expressions render and why Controls.Add() doesn't work

Hiya.

Yet again Google has bought me to your site.. and very good one too.. I was wondering if you'd be able to advise me.. I've read all the above and not quite sure how to attack this.. I've only been doing this sort of programming for a few months.. I am using Visual Studio .NET 2003, with .aspx , .vb, ASP 1.

I have an .aspx page.. it was all working fine until I put the code below into the Initialise_Page() in my .vb code behind.

With Form1
.Controls.Add(lblContinue)
.......
End With



I have several controls I wish to add like this.. in my .aspx page I have many <% %> tags. I tried the <%# %> method, but this gives a new error.

BC30201: Expression expected.

This occurs for tags I have used to write Session variables from my vb into value/text property of other html elements.

However I also use this method to e.g. write in the 'checked' property to INPUT option controls.

I get the impression from your blog that I need to write server side code to do these actions instead.. but I have so many! Do you know a fix for the 'new' error I got when I tried your <%# %> solution?

Many thanks,

Lucy

Lucy Blain
November 17, 2006

# Understanding how <% %> expressions render and why Controls.Add() doesn't work

Sorry I should have included this!!

the error:
BC30201: Expression expected.

was generated by this line of code....
<input type="hidden" name="hdnCountyType" value="<%#= Session("CountyType")%>" >


Thanks,

Lucy.

Lucy
November 17, 2006

# re: Understanding how &lt;% %&gt; expressions render and why Controls.Add() doesn't work

Hi, Again!!

Quite embarrassing.. colleague sat with me to have a look at this.. he'd never come across it.. but he helped me get your <%# % working. I'd left the '=' in
all fixed now!

Thanks very much.. this will definately come in handy!

Lucy.

Peter
September 07, 2008

# re: Understanding how &lt;% %&gt; expressions render and why Controls.Add() doesn't work

I hope website doesn't ever go offline because my source code has a bunch of links to various articles hosted here. It's usually something like "Because of limitation X and Y, for more info... [link to this site]" :-)

Rick Strahl
September 07, 2008

# re: Understanding how &lt;% %&gt; expressions render and why Controls.Add() doesn't work

Ah now there's using you head. Nice! Glad to see you find the content useful Peter.

andy gee
January 05, 2009

# re: Understanding how &lt;% %&gt; expressions render and why Controls.Add() doesn't work

re: It's usually something like "Because of limitation X and Y, for more info... [link to this site]" :-)

I've even seen professional level documentation refer to this blog/articles - Telerik do so, can't recall other...

Kit West
March 22, 2010

# re: Understanding how &lt;% %&gt; expressions render and why Controls.Add() doesn't work

The thing that finally worked was isolating the bits with the code blocks into a separate Content region. I couldn’t figure out how to place a comment close by to explain why, so I gave it a reallyLongName:

<asp:Content ID="SeparateContentSinceCodeBlocksCauseTelerikAjaxControlsToGetHttpException"
  ContentPlaceHolderID="ContentPlaceHolder1" runat="server">

Mahdi
November 15, 2011

# re: Understanding how &lt;% %&gt; expressions render and why Controls.Add() doesn't work

I know that this post belongs to 5 years age, but for the reference, take a look at this nice solution (also 5 years old!):
http://weblogs.asp.net/infinitiesloop/archive/2006/08/09/The-CodeExpressionBuilder.aspx

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024