OdeToCode IC Logo

The ContentPlaceHolder – Not Just For Content

Tuesday, April 11, 2006

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:

HtmlLink link = new HtmlLink();
link.Href =
"~/wildStyle.css";
link.Attributes.Add(
"type", "text/css");
link.Attributes.Add(
"rel", "stylesheet");
this.Header.Controls.Add(link);

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:

<%@ Master Language="C#" %>
...
<head runat="server">
    ...
    <asp:ContentPlaceHolder runat="server" id="headerPlaceHolder" />
</
head>
...

Then in a web form I used a Content control to add additional scripts and metadata inside of <head>:

<%@ Page Language="C#"
        
MasterPageFile="~/Master1.master"
        
Title="Foo" %>

<asp:Content ID="Content1"
            
ContentPlaceHolderID="headerPlaceHolder"
            
runat="Server">
             
  <meta name="keywords" content="declassified information" />
  <style type="text/css" media="all">
      @import "wildstyle.css";
  
</style>
  <script type="text/javascript" src="foo.js"></script>
  
</asp:Content>

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:

<%@ Master Language="C#" %>

...
<head runat
="server">
    <title>Untitled Page</title>
    
</head
>
...
    <form id="form1" runat
="server">
     <asp:ContentPlaceHolder runat="server" id="headerPlaceHolder"
/>
     ...
    </form
>
...

All we need is some code behind the master page, or in a master page base class like so:

protected void Page_Init(object sender, EventArgs e)
{
  Controls.Remove(headerPlaceHolder);
  Page.Header.Controls.Add(headerPlaceHolder);
}

The .aspx webforms can add to <head> declaratively, using a Content control.

What do you think? Good? Bad? Dirty?


(c) OdeToCode LLC 2004 - 2021