UGTS Document #78 - Last Modified: 8/29/2015 3:23 PM
Pitfalls of Server Side Code Blocks in ASP.NET

Controls Collection Cannot Be Modified

ASP.NET web forms provide a great deal of flexibility, but not all of the features are fully supported. If you've ever tried to add server side controls dynamically to a page, you may have run across the following runtime HttpException:

The Controls collection cannot be modified because the control contains code blocks (i.e. <% ... %>).

This error will occur whenever you have an ASPX page with Outside Code (<% ... %>) or Equals (<%= ... %>) blocks, and somewhere the page is dynamically adding server side controls to the Page.Controls tree. 'Outside' means that these blocks are not contained within a server side control (runat='server').  The error does not occur for Data Binding blocks (<%# ... %>).  The Exception will specifically be raised at the time you try to add the control to the Controls tree, and the Controls tree has IsReadOnly = true.

Why does this happen, and why doesn't it happen when these tags are under a server side control like <asp:Placeholder> ?  

If you compare the generated code for your ASPX page in Reflector for the case of having dynamic code blocks inside and outside server side tags (runat='server'), you'll see that the code generated for the BuildControlTree and Render methods has a subtle difference:
  • No Outside CodeBlocks – When there are no dynamic code blocks except inside server side controls, then the page will have a BuildControlTree method that places all of the page’s HTML content inside the Controls collection - either as LiteralControl, DataBoundLiteralControl, or some other server side control.  This means that the content of the page can be completely built through the BuildControlTree method, and ASP.NET only needs to walk the Controls tree and have each control render itself in order to render the whole page.
  • Outside CodeBlocks – However, if there are code blocks (other than data binding blocks <%# ... %>) outside of server side controls, then ASP.NET will compile the parts that specifically are server side controls in the BuildControlTree method, and it will call SetRenderMethodDelegate to setup a custom rendering function.  This custom rendering function will take care of rendering the dynamic code interspersed with calls to the Controls’ rendering functions to render the whole page.  The crucial difference here is that in this case, there is page content and rendering code which is OUTSIDE the Controls tree, and the Controls tree does NOT have enough information to render the entire page.

At this point you may be thinking: what’s the big deal about having a custom rendering function for the page, why should that stop me from modifying the Page.Controls collection?

Well, it turns out that ASP.NET has to override the custom rendering method for the page because dynamic code blocks are a whole lot more powerful than simple text substitution and HTML generation.  You can have a dynamic code block which starts a for loop, then have literal HTML and more dynamic code blocks, and then have a dynamic code block to end the loop, which allows you to generate a kind of HTML repeater.  You can do all kinds of program flow control and branching with dynamic code blocks up to the point where the page looks more like a method than a document.  To render that page properly, all of that code needs to go into the custom rendering function, and the rendering function that ASP.NET generates has to hard-code server side control references by index into the Controls tree.  Because of this, ASP.NET takes a heavy handed approach in this case and locks down the Controls tree, denying you the ability to modify it, so that those lookups remain correct (they don't crash or render the wrong controls), and so that you don't add controls and then wonder why they don't render on the page.

At this point you may object: 'It might make sense to override render if there are <% ... %> code blocks, because those might contain program flow statements such as loops and branching, but why is that necessary if the page only has <%= ... %> blocks, which seemingly cannot change program flow?   Why should ASP.NET adopt such a heavy-handed restriction to handle an edge case that people rarely use?'  Rick Strahl asks this question on his website.  I can only guess that there may be pathological cases Microsoft is aware of where program flow can change even for these kinds of tags during rendering, or it may be necessary for backwards compatibility, or maybe Microsoft just goofed up and applied the restriction where it wasn’t necessary.  Whichever the reason, it does make development of dynamic pages painful with ASP.NET.

There are two workarounds for this problem:

  • Data Binding - use data binding expressions <%# ... %> instead of <%= ... %>.  If you look at the code generated for your page in Reflector when <%# ... %> data binding blocks are used, it is only marginally less efficient than code from <%= ... %> blocks, and for the most part it accomplishes the same thing.  However, there may be problems with this approach because it can cause some values on user-edited ASP.NET web form controls to get wiped out and replaced with the original data bound value.  You also have to explicitly remember to call the Page.DataBind method at some point before the page renders, and for basic pages that have no code-behind, this is an extra step you have to do.
  • Server side container tags - so long as the dynamic <%= ... %> code block is contained in a server side tag, the problem won't happen.  So you can give the overall html tag a runat='server' attribute, or wrap the individual dynamic code block in a <div runat='server'> tag, and the problem will go away.  However, you should be aware of the cost of this approach - if a page has any server side controls in it, and the properties of those controls are modified programmatically after initialization or by the user, and ViewState is not explicitly disabled for the page, then those modified attributes will be added to the viewstate.  Usually this is not a problem except for controls such as data grids where the overhead is abusive.

Again it bears repeating that neither workaround is necessary unless the page has server side controls, and some of those controls are added dynamically.  A simple analysis of these two approaches suggests that you should divide up the web pages in your site into two types - those which use server side controls (web form pages), and those which don't (raw HTML pages).  Web form pages should either use data binding expressions with DataBind calls, or they should wrap the dynamic code in server side tags.  Raw HTML pages can use dynamic code blocks without any problems.

Data Binding Blocks Output Nothing

Data Binding blocks don't have the Controls collection problem that Equals blocks have, but they have their own pitfall - unlike Equals blocks, if you do not call Page.DataBind, data binding blocks do nothing.  This webpage discusses the difference between Data Binding and Equals blocks, and here is an article specifically on Data Binding.  The summary of the differences is that both types compile to dynamic code on the page, but that Data Binding blocks are evaluated at the PreRender step, whereas Equals blocks are evaluated later at Render.  Because of this, data binding expressions can be used to dynamically assign property values to server side controls, but Equals block can't.  If you try it, the Equals block text is just passed verbatim as a literal string to the property, which is almost certainly not what you want.

Data Binding blocks don't have the Controls collection modification issue because they don't have the ability that Equals blocks have that gets them into trouble - they can't be used to make changes to program flow like Equals blocks do.  Becuase they do all of their work at PreRender, they don't directly affect how the Render step is to be executed.

Dynamic property values are the primary reason why Data Binding blocks exist - to bind dynamic data to controls.  You can also have Data Binding expressions outside of control properties, but they will be replaced at PreRender time with an empty string unless the page has called the DataBind function at some point before PreRender in the page's lifecycle.  This point bears repeating: Data Binding expressions do nothing unless the Page.DataBind method is called, either by the CodeFile or CodeBehind or an inherited base class.  

But you might say, why can't ASP.NET detect for me that I obviously need to call DataBind and just do it for me, or perhaps throw an error if DataBind had not been called but there was a Data Binding expression on the page?  First, Microsoft chose not to do it for you to force you to make the choice of where to do it, because sometimes DataBind calls do database access, and that can be very slow.  On the initial request, you might not want to do it at all, and instead wait for a postback event.  This is also why no Exception is thrown if DataBind is never called.  It's a legitimate use of a Data Binding block to display empty content on the initial page load so that the page loads more quickly, and only fill in content with data as the user requests it.  Personally I prefer to make sure that all data binding calls are fast, and always call DataBind in a base class from which all web pages inherit.  But that approach may not work for your particular website.

Controls are Null until Init

The ASP.NET page lifecycle shows that the control which you add through the designer and markup on the ASPX page are initialized just prior to the Init event.  This includes creation on those controls.  Prior to that, at the PreInit event, all theses controls are null.  Normally this is not an issue for you unless you are using C#, where you must explicitly add event handlers for all events because there is no Handles keyword.  When you add event handlers, you should wire the page's event in the contructor for the page, and the controls events during Page.Init.  If you try to wire up the event handlers for the controls during PreInit, the page will crash because at that point the controls are still null.

Custom Controls have Null Child Control Members

Custom controls have an overridable method CreateChildControls, but it does not work like you might expect it to.  On the first request to a page, it is called after the Page.Load event, and it is only called before on postbacks to the page, unless you also call the method EnsureChildControls earlier than that.  EnsureChildControls is a protected method of the Control base class - it is meant to be called from within your custom control class, usally from within the control's Init event, unless you have a good reason to call it earlier or later than that.  If you don't do this, you'll probably get null reference exceptions when you try to set the properties of your custom controls during the Init or Load events.

Master Pages and Dynamic Control Creation at PreInit

If you try to add controls dynamically to the Controls collection in a web page that uses a master page, you might get the error:

Content controls have to be top-level controls in a content page or a nested master page that references a master page.

Usually people get this error when they try to add a master page to an existing page, but they do an incomplete job of it, and leave some content in the page which is outside the page directives and asp:Content tags.  In this case, the cause of the error is straightforward - the page uses a master page, and it has content which is sitting outside of the content tags - and this is not allowed.

However, you may also get this error if you're trying to add controls dynamically to the Page.Controls collection at the PreInit event.  The error (if you receive it) will be not be raised immediately when you try to add the control, but just afterwards from the call stack:

 at System.Web.UI.MasterPage.CreateMaster(TemplateControl owner, HttpContext context, VirtualPath masterPageFile, IDictionary contentTemplateCollection)
  at System.Web.UI.Page.get_Master()
  at System.Web.UI.Page.ApplyMasterPage()
  at System.Web.UI.Page.PerformPreInit()

As the call stack shows, the page is setting up the master page relation immediately after PreInit, and at this point the CreateMaster function sees controls in the Controls collection which are sitting outside of asp:Content tags, which is bad - the master page does not know where to put them. 

If you dynamically add the controls at the Init event, there is no problem - because at that point the master / content layout has already been determined, and after that, controls are appended to the end of the page (even past the closing </html> tag).  This is also the recommended way according to the ASP.NET life cycle to handle the issue - create the dynamic control (if necessary) during PreInit, and then add it to the Controls collection at Init.  If you want better control over where the control is placed in the page, then you should add it to a subcontrol instead of Page.Controls, insert it somewhere in the middle of the Controls list instead of appending it to the end, or set Visible = false so that only the control does not show up in the page's HTML at all, but the state is maintained in the __VIEWSTATE value.