Skip navigation.

Edit-and-Continue Should Be an Individual SettingAll recent postsList Control tagPrefixes In Web.config

Code Blocks Inside Master Pages Cause Trouble

Here’s a little shocking detail about master pages I figured out yesterday. If you happen to have <% ... %> code blocks in the page <head>, sooner or later you may run into the following exception:

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

A search in user groups revealed that the HtmlHead control does not welcome code blocks. Any time some code tries to dynamically add another control (a stylesheet link, for example) to the <head>, it blows up with the message above.

If you haven’t followed my previous posts about master pages and URL rebasing, you might want to quickly skim through them to better understand what the issue here is.

URL rebasing does not happen for links inside <style> and <script> tags:

<style type="text/css">
 @import '../css/screen.css';
</style>
<script type='text/javascript'  src='../library/nav.js'></script>

To accomodate for this, I use code blocks to resolve paths at runtime:

<style type="text/css">
  @import '<%= ResolveUrl("~/css/screen.css") %>';
  @import '<%= BaseURL %>/css/screen.css';
</style>
<script type='text/javascript'
    src='<%= ResolveUrl ("~/library/nav.js") %>'></script>

As it turns out, <% ... %> code blocks don’t sit right with the page header. It’s unnerving to put in so much time building a beefy master page only to realize you can’t do any of it. The suggestion I’ve seen was to add everything dynamically. Nuts! It defies the purpose of master pages.

Data Binding To the Rescue

But then I remembered that every control, which derives from Control, has a DataBind() method. HtmlHead ultimately derives from Control, so…

First, I changed code blocks to data binding expressions (<%# ... %>):

<style type="text/css">
  @import '<%# ResolveUrl("~/css/screen.css") %>';
  @import '<%# BaseURL %>/css/screen.css';
</style>
<script type='text/javascript'
    src='<%# ResolveUrl ("~/library/nav.js") %>'></script>

Next, I added the following to the master code-behind:

protected override void OnLoad (EventArgs e)
{
  base.OnLoad (e);
  Page.Header.DataBind ();
}

The last line forces the header to resolve data binding expressions, and everything gets back to normal!

Expressions Work, Though

To add insult to injury, ASP.NET expressions work without any tweaking. The following markup does not trip anything:

<script type='text/javascript'>
var popUpBlockerAlert=
   '<asp:Localize
     runat="server"
     Text="<%$ Resources: WebResources,PopUpBlockerAlert %>" />';
</script>

Basically, it pulls out a resource string appropriate for the current culture. The parser has no problem with this construct. Weird.

Comments

Comment permalink 1 Shane Shepherd |
@Milan - Good detective work! I've been using the dynamic method until now...it is a pain. DataBinding is much easier, thanks for the explanation!
Comment permalink 2 Nicholas |
Why are you using code blocks anyway? I haven't found a single situation that warrants their use in the transition from ASP to ASP.Net...
Comment permalink 3 Shane Shepherd |
@Nicholas - I am using them to make sure the path to a style sheet or jscript file in my master page resolves correctly no matter where in the directory structure the page that inherits it is located.
Comment permalink 4 Nicholas |
You should be using code like this to append the code to your header (in your Page_Load):

Dim cssLink As New HtmlLink()
cssLink.Href = "~/styles.css"
cssLink.Attributes.Add("rel", "stylesheet")
cssLink.Attributes.Add("type", "text/css")
Page.Header.Controls.Add(cssLink)

That should work fine, and it will use the normal ASP.Net relative pathing (due to the ~ indicating application root.)
Comment permalink 5 Nicholas |
Also, any legit script includes placed into a HEAD tag with runat="server" should automagically get corrected with the relative pathing. For example:

< head runat="server" >

< /head>

If you request a page on level deeper than the application's root, the href should automatically be corrected to "../styles/styles.css". This does not work for "@import" directives, though, only direct hrefs.
Comment permalink 6 Nicholas |
Well my example had tags in it.. just replace [ and ].. you get the idea.

[head runat="server"]
[link href="styles/styles.css" type="text/css" rel="stylesheet"/]
[/head]
Comment permalink 7 Rick Strahl |
Nicholas, CodeBlocks should be minimized but they are useful and can reduce code. Using codebehind code to inject code is not nearly as clean in many situations, and remember too that is about as efficient as you can get in terms of performance (since they translate straight into Response.Write() expressions - unlike controls which have lots of overhead).

It's also an issue for generic tools. I have a number of extender controls that get thwarted by this very problem because these controls can't generically add to the page if the user decides to use tags.

And although I try to avoid CodeBlocks as well I find I need them frequently for script code where i NEED to do this sort of thing:

var Ctl = document.getElementById('< %= txtName.ClientID % >');

I don't see any decent workaround for this particular situation (except using databinding expresions).
Comment permalink 8 Craig Bolon |
Can data binding also link function names for a custom validator in a content page with JavaScript functions in master page?

Such as this in the master page < HEAD > --

< script type="text/javascript" >
function Validate(source, arguments)
. . . .


And this in one of the content pages --

< asp:CustomValidator ClientValidationFunction="Validate" . . . . >
Comment permalink 9 Nicholas |
Rick,

You would normally get generating the code (that references ClientID) server-side, then spitting it out in a literal control or using the "RegisterStartupScript" method. I think ASP.Net has a newer version of that method, anyhow, to make it easier to handle that sort of thing.

I usually make generic functions that have a "psClientId" parameter, and in calls to the function (assembled server-side), it's quite easy to pass in the control's ClientID.

I'm not trying to be difficult here :) It's just that code blocks are a carry over from ASP, and who knows when Microsoft will kick 'em out of ASP.Net. Also, you mention that it's faster to do a codeblock than to assemble an object-- I wonder if that's really true. ASP.Net is going to compile/JIT the commands anyway (codeblock or not), and probably run them both just as fast in a "real world" test.
Comment permalink 10 Nicholas |
Arg. That should be "You would normally generate the Javascript (that references ClientID) server-side"
Comment permalink 11 adancin |
Muchas gracias,,, Excelente!!!
Comment permalink 12 Steele |
Another very good reason for CodeBlocks is to insert Comments that you do not want rendered to the browser. I do this a lot and that is now broken since you can't make a comment with expressions
Comment permalink 13 Steele |
Actually, the Server-Side Comment Tag does work...

but a code style comment doesn't...
Comment permalink 14 Darren |
Rick,

Great article, should come in handy. BTW, I must disagree with Nicholas' objections posted here. Code blocks are not merely "carry-overs" from classic ASP, which are to be discontinued in ASP.NET. The downside to code blocks were mainly because their use made code ugly and harder to read (especially in the hands of poor coders) not that the practice made code less efficient. On the contrary, code blocks are a very useful tool for the developer, providing he has a skilled enough hand to wield them. Perhaps ole Nick hasn't run accross any scenerio where code blocks would be useful because of his clearly lacking design skills. Or maybe he just gets his kicks posting his little quips on these types of forums. I know I just got mine :)
Comment permalink 15 Mike Westermann |
Nicholas, your assertion doesn't hold for script tags in the header. I haven't found a codebehind way to add script tags in the header with rebased src attributes yet. It works fine for link and meta, but not for script.

Also, your solution provides for 5 lines of codebehind for each meta or link declaration where one line for each will suffice in the master page file instead.

Also, the following line in your solution has an issue in my opinion as well:
cssLink.Href = "~/styles.css"
You would not want to hard-code a css path into compiled code, so you'd end up replacing this with some sort of derived constant anyway. This creates another reference to manage.

Much, much easier to use the databinding method proposed in this article if you ask me.
Comment permalink 16 AbsCoder |
Mike W, you can add scripts to the header within the code behind like so...

Dim jsLink As New HtmlGenericControl()
jsLink.TagName = "script"
jsLink.Attributes.Add("src", ResolveClientUrl("~/scripts/foo.js"))
jsLink.Attributes.Add("type", "text/javascript")
Page.Header.Controls.Add(jsLink)

Just FYI :)
Comment permalink 17 Aditya |
I have run into same kind of problem.
My scenario is that my master page and content page is in the root folder(web server) and the .css is in css folder.
In my master page head tag ( which is runat="server") , i have few meta tags, links tag for css, scripts tags for js.

All this have path as say href="css/ssheet.css" or src="js/jscript.js"
I also have some server-side comment in place between tags.

The most wierd part is, i get this error for few pages and do not get for other pages, which are part of same master page!
Also, this error was not occuring earlier, but suddenly it has cropped up to me :(

Is this situation some what similar to what discussed above?
Can any body help me on this, please?
Comment permalink 18 Lee Dumond |
Just curious about the reason for overriding Onload...

Is there any reason you can't call the Databind() method in the master page's Page_Load? Seems to work fine for me.
Comment permalink 19 Milan Negovan |
Lee: no, there's no particular reason. I think you can call it just about any time.
Comment permalink 20 Scott Mayo |
Great work. This has come in very handy! Beats trying to reimplement the HtmlHead control (Microsoft sealed it). I think the easiest solution for Microsoft would be to add a runat="client" attribute value so that the tilde could be converted in this circumstance. That way codeblocks wouldn't be broken and we wouldn't need a hack!
Comment permalink 21 Germ |
AbsCoder you are a genius. I was looking all over for code to insert script tag into header.
Comment permalink 22 AbsCoder |
Glad I could be of some help, Germ. If only the genius part were true... :)
Comment permalink 23 Theodora |
Thanks so much for posting your code! I was struggling with this problem for a while, but adding Page.Header.DataBind() took care of everything.
Comment permalink 24 JiPe |
Just to say thank you ! You are a genius !!!
Comment permalink 25 abrakadabra |
Wow! Great!
I just added into my head yesterday, and while everything seemed fine I notice today that a page I had been working on last week suddenly threw the terrible error: "The Controls collection cannot be modified because the control contains code blocks (i.e. ). "

One search on the internet found Rick's blog, and then I noticed your comment which eventually led to the 'avoid inside header'.

Thanks again - tusen takk!
Comment permalink 26 Josh Stodola |
Milan to the rescue!! Thanks for sharing, dude!
Comment permalink 27 Dave |
I'm dynamically inserting a web control into a page. How can I add a control block to get back a handle to the web control into my JavaScript? Specifically the web control is not in my master file. I'm defining it in my aspx file and then adding it at runtime. I can't put a code block into my my master file as it gives an errror that the web control is not found. I can't seem to find a good syntax way to add a code block into my aspx file.

Thanks,
Dave
Comment permalink 28 Milan Negovan |
Dave, a couple of things are missing from your description. Do you think you can email me a sample (even if it doesn't function)?
Comment permalink 29 Gearóid |
Thank so you much!! Ran into this when I was trying to put the TabContainer control from the Ajax Control Toolkit into a site. Was all working fine til then. Saved me going completely insane - thanks!
Comment permalink 30 Dave Durose |
Great work! Thank you, thank you, thank you.
Comment permalink 31 Nitin Gupta |
The solution was awesome! Works like a charm. Thanks for saving me a lot of time. I knew it had been done before :)
Comment permalink 32 Rosnell |
Men, tankyou so much!!!! i was about to hit my self against a wall with this trouble!!!!
Comment permalink 33 Sunny |
You Rock dude !! thanks a lot
Comment permalink 34 k |
Works great except for src attribute of an image tag. Thoughts?
Comment permalink 35 Milan Negovan |
Nope, no thoughts on that. You don't put images in the page head which is what gets bound. My guess is this is why your image is not picked up.
Comment permalink 36 k |
Thanks Milan. It hit me this morning while I was getting ready. I had changed all of the tags over instead of just the ones in the head. Got too excited :-)
Comment permalink 37 Kevin zhou |
You can simply add the style tag:

Dim ltlStyleTage As LiteralControl = New LiteralControl
ltlStyleTage.ID = "ltlNoScrollControler"
ltlStyleTage.Text = " html,body{overflow:hidden;} "
Page.Header.Controls.Add(ltlStyleTage)

But your header shoud have "Runat='server'" attribute
Comment permalink 38 Milan Negovan |
Kevin, the concern is not how it all looks. It's that that automatic rebasing of URLs gets in the way. You can't solve it with CSS.
Comment permalink 39 Rex Henderson |
This has been one of the most informative posts I have EVER read. I've not only found an obscure answer to something that I have run into time and again, but the other tidbits posted herein are pure gold nuggets. Man. Thanks to All! I am saving a copy of this thing, but I hope it stays around forever!
Comment permalink 40 Olof W |
Well seems like not only I got some great help from this article.
Thanks mate, it works! Why didn't MS think of just doing this? (Probably some really good reason, but who cares...)
Comment permalink 41 Akhila |
Thanks dude...
Nice work...

Emails and Notifications

Would you like to be notified when somebody responds to this post?  Would you like to have these comments emailed to you?

TrackBacks

Sorry, TrackBacks are not allowed.

Submit your comment

Please enter only text since all HTML tags except hyperlinks will be stripped. Hyperlinks will become live links. Any comments with flaming or offensive language will be deleted. Be courteous to other posters. Thank you.

Your name (required):
Your email (optional):
Your site's URL (optional):
Enter this number
Type in the number above:
Comment (required):