Contact   •   Products   •   Search

Rick Strahl's Web Log

Wind, waves, code and everything in between...
ASP.NET • C# • HTML5 • JavaScript • AngularJs

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


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.

Make Donation


Feedback for this Post

 
# re: Understanding how &lt;% %&gt; expressions render and why Controls.Add() doesn't work
by Vadivel Kumar June 25, 2006 @ 4:52am
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
# re: Understanding how &lt;% %&gt; expressions render and why Controls.Add() doesn't work
by Rick Strahl June 25, 2006 @ 12:04pm
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.
# Adding Rendering with SetRenderMethodDelegate() - aeh, sort of - Rick Strahl's Web Log
by Rick Strahl's Web Log November 12, 2006 @ 7:33am
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.
# Understanding how <% %> expressions render and why Controls.Add() doesn't work
by Lucy Blain November 17, 2006 @ 4:41am
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
# Understanding how <% %> expressions render and why Controls.Add() doesn't work
by Lucy Blain November 17, 2006 @ 4:45am
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.
# re: Understanding how <% %> expressions render and why Controls.Add() doesn't work
by Lucy November 17, 2006 @ 8:49am
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.
# re: Understanding how <% %> expressions render and why Controls.Add() doesn't work
by Peter September 07, 2008 @ 7:51pm
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]" :-)
# re: Understanding how <% %> expressions render and why Controls.Add() doesn't work
by Rick Strahl September 07, 2008 @ 8:19pm
Ah now there's using you head. Nice! Glad to see you find the content useful Peter.
# re: Understanding how <% %> expressions render and why Controls.Add() doesn't work
by andy gee January 05, 2009 @ 9:30pm
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...
# re: Understanding how <% %> expressions render and why Controls.Add() doesn't work
by Kit West March 22, 2010 @ 7:05am
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">
# re: Understanding how <% %> expressions render and why Controls.Add() doesn't work
by Mahdi November 15, 2011 @ 9:50pm
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 - 2014