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.