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!
This is how you do it (the source files are available for download above):
- drop 2 ContentPlaceHolders in a MasterPage's head tag, 1 for Description and 1 for Keywords making sure to set Visible="false"
- wire up Load events on the ContentPlaceHolders and LoadComplete on the MasterPage's Page property
- implement handled events
- create or update existing page with new Description and Keywords ContentPlaceHolders add content to place holders
- 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