Thursday, July 14, 2011

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

The Controls Collection Cannot Be Modified Because the Control Contains Code Blocks
If you're using Master Pages in ASP.NET and trying to resolve <script> or <style> references in the page head, you may have run into this this show-stopper. If so, here's why it's happening, and here's an easy way to fix it.
If you've worked with ASP.NET Master Pages, you've no doubt taken advantage of automatic URL re-basing within the HtmlHead control. This bit of ASP.NET magic helps assure that a content page has access to the correct resources:

<head id="head1" runat="server">
<title>My Page</title>
<link href="css/common.css" rel="stylesheet" type="text/css" />

</head>
When you add the runat="server" attribute to the <head> tag, the runtime treats it as an HtmlHead control. The HtmlHead control has the ability to parse child <link> controls contained withing it, so that when a content page is rendered, the URL in the href attribute points to the correct file. Pretty sweet.

The Problem

Now, let's say you also want to place a reference to an external JavaScript file in the <head>, so you do this:
<head id="head1" runat="server">
<title>My Page</title>

<link href="css/common.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="javascript/leesUtils.js"></script>

</head>
Naturally, you might assume that URL re-basing will help you out here, but you'd be mistaken. Unfortunately, the runtime doesn't automatically re-base URLs in <script> or <style> tags, so any page not in the same directory as your master page won't find your script or CSS files. Bummer.
Soooo... you figure if HtmlHead won't resolve your URL automatically, you'll wrestle that sucker manually by using the Control.ResolveUrl method. ResolveUrl can take a URL that is relative to the root of the application and correct it for the current request path. You'd then use a code block to substitute the correct URL at runtime:

<head id="head1" runat="server">
<title>My Page</title>
<link href="css/common.css" rel="stylesheet" type="text/css" />

<script type="text/javascript" src="<%= ResolveUrl("~/javascript/leesUtils.js") %>"></script>

</head>
But then, when you run the page, you get the following System.Web.HttpException :
The Controls collection cannot be modified because the control contains code blocks (i.e. <% ... %>).
Ouch. As we can see, page headers don't play nice with code blocks. That's no fun. What to do?

The Solution


I've seen various solutions for this published before. Some will tell you to simply move the code block to the form or to a ContentPlaceHolder, which requires you to move the tag out of the header (and which will fail if you populate the placeholder in a content page). Other solutions involve dynamically creating elements and adding them to the HtmlHead.Controls collection at runtime. However, I've come up with something I find a lot easier and more straightforward -- while leaving the <script> tag in the header where it belongs.
First, start the code block with <%#  instead of <%=  :

<head id="head1" runat="server">
<title>My Page</title>
<link href="css/common.css" rel="stylesheet" type="text/css" />

<script type="text/javascript" src="<%# ResolveUrl("~/javascript/leesUtils.js") %>"></script>

</head>
This changes the code block from a Response.Write code block to a databinding expression. Since <%# ... %> databinding expressions aren't code blocks, the CLR won't complain. Then in the code for the master page, you'd add the following:
protected void Page_Load(object sender, EventArgs e)

{
Page.Header.DataBind();    
}
The DataBind method evaluates all the databinding expression(s) in your header at load time, and you're good to go. No muss, no fuss, no ring around the collar.

2 comments: