So I found this article, http://weblogs.asp.net/soever/archive/2006/11/14/SharePoint-2007_3A00_-using-the-masterpage-from-your-site-in-custom-_5F00_layouts-pages.aspx, which explains how to use your SharePoint site’s master page when you create a custom application page in the _layouts virtual directory. At this point in time I don’t really have any custom application pages yet but I liked the idea of when I theme a site that all pages, including application pages in the _layouts virtual directory, use the same master page. By default all application pages use the application.master master page. After finding this article, http://www.odetocode.com/Articles/450.aspx, and a lot of playing around I believe I have found a solution. I created a HttpModule that on a page’s preinit, it checks to see if the page is in the _layouts virtual directory, and if so, it will assign it the master page url from the current sharepoint site. Here’s my code:
using System; using System.Collections.Generic; using System.Text; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using System.Configuration; using System.Data.SqlClient; using System.Data; using Microsoft.SharePoint; public class ApplicationMasterPage : IHttpModule { public void Init(HttpApplication context) { context.PreRequestHandlerExecute += new EventHandler(context_PreRequestHandlerExecute); } void context_PreRequestHandlerExecute(object sender, EventArgs e) { Page page = HttpContext.Current.CurrentHandler as Page; if (page != null) { page.PreInit += new EventHandler(page_PreInit); } } void page_PreInit(object sender, EventArgs e) { Page page = sender as Page; if (page == null) return; if (page.MasterPageFile == null) return; HttpContext context = HttpContext.Current; string currUrl = context.Request.Url.ToString(); string basepath = currUrl.Substring(0, currUrl.IndexOf(context.Request.Url.Host) + context.Request.Url.Host.Length); //Use RawUrl, otherwise it will always use the root web. currUrl = context.Request.RawUrl; if (currUrl.ToLower().Contains("/_layouts/") && page.MasterPageFile.ToLower().EndsWith("application.master")) { currUrl = currUrl.Substring(0, currUrl.ToLower().IndexOf("/_layouts/")); SPSite site = null; SPWeb web = null; try { site = new SPSite(basepath + currUrl); web = site.OpenWeb(currUrl); if (String.IsNullOrEmpty(web.MasterUrl) == false) { page.MasterPageFile = web.MasterUrl; page.Load += new EventHandler(page_Load); } } finally { if (web != null) { web.Dispose(); } if (site != null) { site.Dispose(); } } } } public void Dispose() { } void page_Load(object sender, EventArgs e) { Page page = sender as Page; if (page == null) return; try { ContentPlaceHolder placeHolderTitleBreadcrumb = page.Master.FindControl("PlaceHolderTitleBreadcrumb") as ContentPlaceHolder; SiteMapPath contentMap = placeHolderTitleBreadcrumb.FindControl("ContentMap") as SiteMapPath; contentMap.CssClass = "ms-hidden"; } catch { } try { ContentPlaceHolder placeHolderSearchArea = page.Master.FindControl("PlaceHolderSearchArea") as ContentPlaceHolder; placeHolderSearchArea.Visible = false; } catch { } } }
Be sure to register the HttpModule in the web.config of your sharepoint site.
This did require me to add extra contentplaceholders into my default.master. Here are the two extra placeholders I had to add:
PlaceHolderPageDescriptionRowAttr
PlaceHolderPageDescriptionRowAttr2
To add these placeholders into my default.master I replaced the following code:
<PlaceHolder id="MSO_ContentDiv" runat="server"> <table id="MSO_ContentTable" width=100% height="100%" border="0" cellspacing="0" cellpadding="0" class="ms-propertysheet"> <tr> <td class='ms-bodyareaframe' valign="top" height="100%"> <A name="mainContent"></A> <asp:ContentPlaceHolder id="PlaceHolderPageDescription" runat="server"/> <asp:ContentPlaceHolder id="PlaceHolderMain" runat="server"> </asp:ContentPlaceHolder> </td> </tr> </table> </PlaceHolder>
with
<PlaceHolder id="MSO_ContentDiv" runat="server"> <table id="MSO_ContentTable" width=100% height="100%" border="0" cellspacing="0" cellpadding="0" class="ms-propertysheet"> <TR valign="top" <asp:ContentPlaceHolder id="PlaceHolderPageDescriptionRowAttr" runat="server"/> > <TD class="ms-descriptiontext" width="100%"> <asp:ContentPlaceHolder id="PlaceHolderPageDescription" runat="server"/> </TD> </TR> <TR <asp:ContentPlaceHolder id="PlaceHolderPageDescriptionRowAttr2" runat="server"/>> <TD ID=onetidMainBodyPadding height="8px"><IMG SRC="/_layouts/images/blank.gif" width=1 height=8 alt=""></TD> </TR> <tr> <td class='ms-bodyareaframe' valign="top" height="100%"> <A name="mainContent"></A> <asp:ContentPlaceHolder id="PlaceHolderMain" runat="server"> </asp:ContentPlaceHolder> </td> </tr> </table> </PlaceHolder>
I also had to change my default breadcrumbs code in the default.master to match the application.master. I replaced the following code:
<asp:contentplaceholder id="PlaceHolderTitleBreadcrumb" runat="server"> <asp:sitemappath id="ContentMap" runat="server" sitemapprovider="SPContentMapProvider" skiplinktext="" nodestyle-cssclass="ms-sitemapdirectional"></asp:sitemappath> </asp:contentplaceholder>
with
<asp:contentplaceholder id="PlaceHolderTitleBreadcrumb" runat="server"> <asp:SiteMapPath SiteMapProvider="SPXmlContentMapProvider" SkipLinkText="" NodeStyle-CssClass="ms-sitemapdirectional" runat="server"/> <asp:SiteMapPath SiteMapProvider="SPContentMapProvider" id="ContentMap" SkipLinkText="" NodeStyle-CssClass="ms-sitemapdirectional" runat="server"/> </asp:contentplaceholder>
————————————————————————————————–
Update (02/23/08): I’ve changed my default.master and my httpmodule around a lot since posting this. If you don’t compile a dll you will need to place the ApplicationMasterPage.cs file in a App_Code directory of your root site and in a App_Code directory at C:Program FilesCommon FilesMicrosoft SharedWeb Server Extensions12TEMPLATEIMAGES. You can download my latest files below:
————————————————————————————————–
All of the code and changes ended up with a site settings page that looks like this.