Code Blocks Inside Master Pages Cause Trouble

Posted on May 25, 2006  |  

Posted in Development

50 comments

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.

50 comments

Shane Shepherd
on May 25, 2006

@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!


Nicholas
on May 25, 2006

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...


Shane Shepherd
on May 26, 2006

@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.


Nicholas
on May 28, 2006

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.)


Nicholas
on May 28, 2006

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.


Nicholas
on May 28, 2006

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]


Rick Strahl
on May 30, 2006

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).


Craig Bolon
on May 31, 2006

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" . . . . >


Nicholas
on May 31, 2006

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.


Nicholas
on May 31, 2006

Arg. That should be "You would normally generate the Javascript (that references ClientID) server-side"


adancin
on September 25, 2006

Muchas gracias,,, Excelente!!!


Steele
on November 1, 2006

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


Steele
on November 1, 2006

Actually, the Server-Side Comment Tag does work...

but a code style comment doesn't...


Darren
on December 28, 2006

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 :)


Mike Westermann
on February 7, 2007

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.


AbsCoder
on March 22, 2007

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 :)


Aditya
on May 8, 2007

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?


Lee Dumond
on May 9, 2007

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.


Milan Negovan
on May 10, 2007

Lee: no, there's no particular reason. I think you can call it just about any time.


Scott Mayo
on May 14, 2007

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!


Germ
on May 16, 2007

AbsCoder you are a genius. I was looking all over for code to insert script tag into header.


AbsCoder
on June 8, 2007

Glad I could be of some help, Germ. If only the genius part were true... :)


Theodora
on June 15, 2007

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.


JiPe
on June 22, 2007

Just to say thank you ! You are a genius !!!


abrakadabra
on August 1, 2007

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!


Josh Stodola
on August 30, 2007

Milan to the rescue!! Thanks for sharing, dude!


Dave
on September 15, 2007

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


Milan Negovan
on September 16, 2007

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)?


Gearóid
on September 21, 2007

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!


Dave Durose
on October 4, 2007

Great work! Thank you, thank you, thank you.


Nitin Gupta
on October 24, 2007

The solution was awesome! Works like a charm. Thanks for saving me a lot of time. I knew it had been done before :)


Rosnell
on November 5, 2007

Men, tankyou so much!!!! i was about to hit my self against a wall with this trouble!!!!


Sunny
on January 3, 2008

You Rock dude !! thanks a lot


k
on February 11, 2008

Works great except for src attribute of an image tag. Thoughts?


Milan Negovan
on February 11, 2008

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.


k
on February 12, 2008

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 :-)


Kevin zhou
on March 20, 2008

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


Milan Negovan
on March 20, 2008

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.


Rex Henderson
on April 3, 2008

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!


Olof W
on July 8, 2008

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...)


Akhila
on July 24, 2008

Thanks dude...
Nice work...


Nathanael Jones
on August 9, 2008

I ran into the same issue... It's really frustrating.

Here's how I made links and script tags act like they are supposed to - application wide.


Jay
on August 14, 2008

wow, thanks! this was very helpful!!


SydSloth
on August 23, 2008

How would you go about resolving the URL when it is passed to a function within the header?

< head runat="server" >
....
< script type= "text/javascript" >
function rollover(name, filename)
{
document.images[name].src = '< %= ResolveUrl(filename) % >';
}

< head >


SydSloth
on August 23, 2008

Nevermind, I see how that won't work,

I am running into a similar problem with the relative paths while referencing an image in a level 1 sub directory. The only way I get Rollover menu buttons with active anchors to work on a master page is by using a code behind file to resolve the image url in the page load event.

string relpath = ResolveUrl("~/Images/Button_Homev02_Rollover.png");
activeHome.Attributes.Add("onmouseover", btnHome.UniqueID.Replace("$","_") + ".src='"+ relpath + "'");

Would there be a better way to do this?


Milan Negovan
on August 27, 2008

This is a tough one. In your previous comment the filename parameter is determined on the client side. What I do sometimes is write out to the page a "global" variable with the path to my application root. This way I can always build an accurate absolute URL in my JavaScript.

Makes sense?


protonboy
on September 12, 2008

Thanks a lot Milan.

I have a User Control in an ascx, that could get embedded in a page in any directory, using a MasterPage from yet another directory. I was trying to attach a stylesheet to the page header from the control code-behind and ran into this problem

Your solution has saved me a great deal of premature grey hair and has had the unexpected bonus of clearing up some buggy hacks I had in my MasterPage header as well.

Top notch!!

BTW for anyone using this technique to get a client-side refernce to ASP.Net server controls, why not use "this" in the function name, as in, e.g. "onmouseover=doSomething(this)" in the server control? Saves having to write out document.getElementByID in the function as well.

e.g.
< input type="button" id="btn" runat="server" value="server" onclick="foo(this);" />< br />< script type="text/javascript">
function foo(element){
element.value='client';
}


Milan Negovan
on September 12, 2008

Good point! Using 'this' is often overlooked. I use it, for example, with my accessible hyperlinks.


stfinch
on October 23, 2008

Thank you very much for this. Saved me from banging my head against the wall!


Adeel Fakhar
on April 20, 2009

Once i was also working in master page and want to call the javascript function that contains Code Blocks. I was feeling headache to solve then i found this solution and every thing gone fine. You just have to put your javascript function in the form tag of master page