Jason Conway

just trying to help

Meta Tags in ASP.NET using MasterPage and ContentPlaceHolders

Having META tags like Description and Keywords on the .aspx pages of our sites is very important.  It is also important that the content of the tags be easily added and/or updated.  Most online help will have you instantiate an HtmlMeta class or write tags by implementing some variance of either:

  • Option 1 - create a base Page class with methods or properties and create an external persistence store for META content
  • Option 2 - create a base Page class with virtual methods or properties and override those on the deriving page
  • Option 3 - create an Interface or abstract class with methods or properties that the deriving page will employ
  • Option 4 - use a third-party component or write a control
  • Option 5 - on every page, write or use existing implementation to create META tags

While my solution also uses the HtmlMeta class, it differs in architecture and uses the following guidelines:

  • create META tags on pages in a new or existing Site
  • no base Page class, abstract class, or Interface that all existing or new pages inherit
  • no database or external store for persisting Tag content
  • no controls or third-party components
  • just want META content and not the actual tag creation on every page
  • must provide unique content per page
  • must provide simple, maintainable content changes without recompile
  • must provide the ability to change content with basic skills and without a special interface

In other words, I did not want to create a base Page, Interface, control, or third-party component that every single one of the pages in my site derives from or uses.  In addition having to ensure that any newly created pages inherited the base Page or implement some Interface is not as easily maintainable.  I can also use this solution on existing sites without having to worry about any Page inheritance issues.

What better place to keep information about an .aspx file than right in the .aspx file?  Most markup resides in each .aspx file using ContentPlaceHolders anyway and when you create a new page, these ContentPlaceHolders will be included automatically!  For this post, I am only going to demonstrate adding META Description and Keywords tags, but this process can be applied to as many tags as you need.  One thing that is great about this approach, is that you will not need a database or external file (like xml) to store META content...making the content very easily updated with a simple text editor (like Notepad) and no additional programming or database skills!

DOWNLOAD - Meta Spike Files

This is how you do it (the source files are available for download above):

  1. drop 2 ContentPlaceHolders in a MasterPage's head tag, 1 for Description and 1 for Keywords making sure to set  Visible="false" 
  2. wire up Load events on the ContentPlaceHolders and LoadComplete on the MasterPage's Page property
  3. implement handled events
  4. create or update existing page with new Description and Keywords ContentPlaceHolders add content to place holders
  5. view page in browser and inspect rendered source

Step 1 - default.master

<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Default.master.cs" Inherits="MetaSpike.Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title></title>
       <asp:ContentPlaceHolder id="MetaDescriptionHolder" runat="server" Visible="false"></asp:ContentPlaceHolder>
        <asp:ContentPlaceHolder ID="MetaKeywordsHolder" runat="server" Visible="false"></asp:ContentPlaceHolder>
    </head>
    <body>
        <form id="DefaultMasterForm" runat="server">
            <div>
                <asp:ContentPlaceHolder ID="MainContentHolder" runat="server"></asp:ContentPlaceHolder>
            </div>
        </form>
    </body>
</html>

Step 2 - default.master.cs

using System;
using System.Web.UI;

namespace MetaSpike {
    public partial class Default : MasterPage {

        protected override void OnInit(EventArgs e) {
            base.OnInit(e);

            this.MetaDescriptionHolder.Load += new EventHandler(MetaDescriptionHolder_Load);
            this.MetaKeywordsHolder.Load += new EventHandler(MetaKeywordsHolder_Load);

            this.Page.LoadComplete += new EventHandler(Page_LoadComplete);
        }

        private void MetaDescriptionHolder_Load(object sender, EventArgs e) {
            throw new NotImplementedException();
        }

        private void MetaKeywordsHolder_Load(object sender, EventArgs e) {
            throw new NotImplementedException();
        }

        private void Page_LoadComplete(object sender, EventArgs e) {
            throw new NotImplementedException();
        }
    }
}

Step 3 - default.master.cs (continued)

using System;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;

namespace MetaSpike {
    public partial class Default : MasterPage {
        private HtmlMeta descriptionMeta;
        private HtmlMeta keywordsMeta;

        protected override void OnInit(EventArgs e) {
            base.OnInit(e);

            this.MetaDescriptionHolder.Load += new EventHandler(MetaDescriptionHolder_Load);
            this.MetaKeywordsHolder.Load += new EventHandler(MetaKeywordsHolder_Load);

            this.Page.LoadComplete += new EventHandler(Page_LoadComplete);
        }

        private void MetaDescriptionHolder_Load(object sender, EventArgs e) {
            string content = this.ParseHolderContent(this.MetaDescriptionHolder);

            if (string.IsNullOrEmpty(content)) return;

            this.descriptionMeta = new HtmlMeta();
            this.descriptionMeta.Name = "description";
            this.descriptionMeta.Content = content;
        }

        private void MetaKeywordsHolder_Load(object sender, EventArgs e) {
            string content = this.ParseHolderContent(this.MetaKeywordsHolder);

            if (string.IsNullOrEmpty(content)) return;

            this.keywordsMeta = new HtmlMeta();
            this.keywordsMeta.Name = "keywords";
            this.keywordsMeta.Content = content;
        }

        private void Page_LoadComplete(object sender, EventArgs e) {
            Page page = sender as Page;
            if (page == null) return;

            if (this.descriptionMeta != null) page.Header.Controls.Add(this.descriptionMeta);
            if (this.keywordsMeta != null) page.Header.Controls.Add(this.keywordsMeta);
        }

        private string ParseHolderContent(ContentPlaceHolder holder) {
            if (holder == null || holder.Controls.Count == 0) return string.Empty;

            LiteralControl control = holder.Controls[0] as LiteralControl;
            if (control == null || string.IsNullOrEmpty(control.Text)) return string.Empty;

            return control.Text.Trim();
        }
    }
}

Step 4 - home.aspx

<%@ Page Language="C#" MasterPageFile="~/Default.Master" AutoEventWireup="true" 
    CodeBehind="Home.aspx.cs" Inherits="MetaSpike.Home" Title="Meta Spike Home" %>
    
<asp:Content ID="MetaDescription" ContentPlaceHolderID="MetaDescriptionHolder" runat="server">description goes here</asp:Content>
<asp:Content ID="MetaKeywords" ContentPlaceHolderID="MetaKeywordsHolder" runat="server">keywords, .net, asp.net, meta</asp:Content>

<asp:Content ID="MainContent" ContentPlaceHolderID="MainContentHolder" runat="server">
    this page now has a very simple mechanism for adding and updating meta tags! enjoy!
</asp:Content>

Step 5 - view source rendered home.aspx

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
    <head>
        <title>Meta Spike Home</title>
        <meta name="description" content="description goes here" />
        <meta name="keywords" content="keywords, .net, asp.net, meta" />
    </head>
    <body>
        <form name="aspnetForm" method="post" action="Home.aspx" id="aspnetForm">
            <div>
                <input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE"
 		   value="/wEPDwUJNDMyNDU0NjAzZGQfTG4D56NhuIUPL9dxPlf1j85RXw==" />
            </div>
            <div>              
                this page now has a very simple mechanism for adding and updating meta tags! enjoy!
            </div>
        </form>
    </body>
</html>

Some things to note:

  • ContentPlaceHolders on the MasterPage have Visible set to false
  • the use of the Page's LoadComplete event instead of Load
  • only straight text in the Content tags on Home.aspx, ASP.NET will put that text into LiteralControls
  • no text in the Content tags on Home.aspx will result in no META tags being output (good thing)

This will prevent you from having to write more code than you need and leaves the actual content on the page itself (where it belongs).  In addition, you won't have a database or special component to worry about or pay for.

I hope you find this approach very simple, maintainable, and easily employed.

Jason Conway

kick it on DotNetKicks.com

Comments

Andy said:

Jason,

I am sorry I don't understand, but what advantage does this have over using meta tags directly in a masterpage (if you need common metatags) or  separate metatags per page?

Master Level (commom meta tags)

<head runat="server">

   <title>Untitled Page</title>

   <meta name="description" content="description goes here" />

   <meta name="keywords" content="keywords, .net, asp.net, meta" />

   <asp:ContentPlaceHolder ID="head" runat="server">

   </asp:ContentPlaceHolder>

</head>

Page Level (meta tags per page)

<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">

   <meta name="more stuff" content="text" />

   <meta name="still more stuff" content="text" />

   </head>

</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">

</asp:Content>

Thanks,

Andy

# March 24, 2008 12:40 PM

Jason Conway said:

Great question!  The answer is: it depends.  =)

If you have the same meta content that needs to reside on every page, then it would be best to add it to the MasterPage directly.

In my case, I have content editors that will be updating pages throughout the site's existance and needed to make it as simple as possible for them to add/update content without having to worry about markup (trust me, simple html tags can get messed up very easily).

In addition, I didn't want to have to add the html tags to every page, although that is a completely acceptable approach.  When a new page is created from this MasterPage, the two ContentPlaceHolders provide a very explicit means to the end.

There is nothing keeping you from using all of the options as needed, my solution is just another way of implementing the same thing.  ;)

Jason

# March 24, 2008 3:10 PM

Hannes Preishuber said:

take care that title attribute of content pages page declaration overwrites <title> element.

So in your sample <title> can be removed from masterpage and should not be set in placeholder of contentpage.  

# March 24, 2008 3:31 PM

Jason Conway said:

Yes, the <title></title> tag on the MasterPage is not used if the Title property is set on the content page.  If the Title property is not set on the content page, then the MasterPage's <title> tag wins.

Try it out.  =)

Jason

# March 24, 2008 4:43 PM

mark kuula said:

Does it matter where the meta tags of 'keywords' and 'description' are found ?  I implemented the solution listed above and it is placing the tags below the css.

ie  <title>

   </title>

   <meta name="y_key" content="xxxxxxxxxx" />

   <style type="text/css">

   </style>

   <link  

     href="App_Themes/xxxxx.css" type="text/css"  

     rel="stylesheet" />

   <meta name="description" content="Featuring    

             our description" />

   <meta name="keywords" content="keyword,

         keyword" />

# April 27, 2008 11:45 AM

ray said:

how bout the page refresh option of meta? sorry noob here

# May 18, 2008 10:05 PM