I’m working on an article full of tips and tricks for master pages. The article will be online soon, but I wanted to throw out one tip as a preview and get some feedback.
A master page typically includes the single HTML <head> tag for a page. This approach gives every content page a <head>, but presents problems when a content page wants to add additional tags inside head, like <meta> and <link>, for example. The tags might be specific to the content page, and we wouldn’t want to include them in the .master for every page.
As long as the <head> tag has a runat=”server” attribute defined, a content page can programmatically add to <head>. The recommended ASP.NET approach to add a <link> tag, for example, is to use the following code:
For more Header examples, see Sue’s edreams.org post.
Milan thinks the Header API is lacking functionality, and I’d agree. My frustration level rises whenever I have to modify the <head> tag programmatically, so for kicks, I tried putting a ContentPlaceHolder inside of the <head> tag:
Then in a web form I used a Content control to add additional scripts and metadata inside of <head>:
Lo and behold, this works and feels natural. No more Header.Controls.BlecktyBlah!
Except, there is a catch....
The validation engine in the Visual Studio 2005 IDE doesn’t believe a ContentPlaceHolder control belongs inside of <head>, so it raises a validation error. This is just a validation error, so the application still compiles, and still executes, but it’s an irritating validation error nonetheless, and one we can’t get rid of unless we disable all HTML validation. Ugh.
Update: The rest of this post was a late night ramble full of rubbish that didn't produce the intended result.
Move along now, nothing to see here.
To appease the deities of validation, I can resort to a hack. I can move the master's ContentPlaceHolder for <head> related goo inside of the <form> tag. This avoids validation error flags, and feels justifiable because all the webforms look more maintainable. The master page only needs to swap the ContentPlaceHolder into the Header controls collection during the page lifecycle.
In other words, with a master page like this:
All we need is some code behind the master page, or in a master page base class like so:
The .aspx webforms can add to <head> declaratively, using a Content control.
What do you think? Good? Bad? Dirty?
Comments
Just write:
<link href="StyleSheet.css" rel="stylesheet" type="text/css" />
within the head section of your .master page.
The head server control will then automaticlaly update the stylsheet path to always be relative to the master page location regardless of what sub-directory a page based on the master page is. For example, if you put a page1.aspx page within a "NewFolder" subdirectory in your site, the control would automaticlaly adjust the reference to the stylesheet to be "../Stylesheet.css" for you. If you had a page2.aspx in the same directory as Site.Master, then it would be declared as just "StyleSheet.css".
This saves you from having to write any code at all.
Hope this helps,
Scott
For me the recommended ASP.NET approach option(adding optional tags programmatically) looks the best option for today, although it has disadvantages such as you mentioned and also others such as bad rendering for example.
MartinKr: Sorry, I'm not sure what can cause that problem.
Anatoly: thanks for the feedback :) I hope the next version of asp.net can provide a cleaner way to do this (but that too far in the future to even dream about at this point).
Also, as a somewhat tangential aside, this is also part of my client-side script management strategy since ASP.NET insists on placing all of the RegisterClientBLAHBLAH stuff in the BODY and not the HEAD. I've seen posts back and forth why this shouldn't matter, but this is not a compromise I'm willing to make so I manage it all myself.
Thanks.
In any case then, forget the hack. I guess we have to live with the validation error and use a placeholder outside of the form.
This blog post covers both topics more: weblogs.asp.net/.../437228.aspx
Hope this helps,
Scott