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;
}
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.
Other Posts you might also like
- Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application
- Resolving Paths To Server Relative Paths in .NET Code
- Map Physical Paths with an HttpContext.MapPath() Extension Method in ASP.NET
- Getting the Client IP Address in ASP.NET Core
- Back to Basics: Rewriting a URL in ASP.NET Core
The Voices of Reason
# re: Understanding how &lt;% %&gt; expressions render and why Controls.Add() doesn't work
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.
# Understanding how <% %> expressions render and why Controls.Add() doesn't work
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
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
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
# re: Understanding how <% %> expressions render and why Controls.Add() doesn't work
# re: Understanding how <% %> expressions render and why Controls.Add() doesn't work
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
<asp:Content ID="SeparateContentSinceCodeBlocksCauseTelerikAjaxControlsToGetHttpException" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
# re: Understanding how <% %> expressions render and why Controls.Add() doesn't work
http://weblogs.asp.net/infinitiesloop/archive/2006/08/09/The-CodeExpressionBuilder.aspx
# re: Understanding how &lt;% %&gt; expressions render and why Controls.Add() doesn't work
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