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.
Hi there,
for your HttpModule, what did you have to add to the web.config file?
Also I take it that your HttpModule gets called for each page that is served, it\’s just that you chose to ignore it if it\’s not a page?
I need to do some basic usage logging of when people download documents, I\’ve created the module but it falls down when trying to register the dll in the web.config file.
Thanks!
Check out the httpModules section of the web.config file for some examples of how to register your httpmodule.
Yes, it gets called for each page that is servered, and I ignore it if it\’s not a page, or it doesn\’t have a masterpage, or if it doesn\’t exist in the _layouts virtual directory.
If you are getting the following error when trying to perform certain functions like checkin, workflows, and deleting site columns:
[CODE]
Unable to validate data. at System.Web.Configuration.MachineKeySection.GetDecodedData(Byte[] buf, Byte[] modifier, Int32 start, Int32 length, Int32& dataLength)
at System.Web.UI.ObjectStateFormatter.Deserialize(String inputString)
[/CODE]
This is caused by having the search box on these pages. I\’ve started using a code behind page for my master page so I can hide one of the breadcrumbs and so now I can hide the search box when on a /_layouts/ page. My code has been updated above, check it out and let me know if it fixes your problem. Thanks.
I would like to do the following in wss v3.
Create my own site definition and then create my own master page which only has siteactions control and some Hello Text and a simple asp.net content place holder control and nothing else (I have read about minimal master page that applies to sts but I would like to build my own master and really would like to throw out out of the box sts templates). I would like to put this in _catalogs/master pages/ . Then I would like to have a layout page called Helloworld.aspx (which has simple html place holder control) that uses Helloworld.master. Then I would like to build a feature that lets me create Helloworld page from site actions menu. First, is this possible or I am always stuck with the minimal master page and sts site definition? Why is one site definition related to one master page? It would have been nice if I could select the master page I would like to use when creating a new page as opposed to creating a feature (May be that’s what I have to do to get this feature??).
What I am really trying to do is understand inner workings of wss v3 better and it seems most of the books in the market talk about sts. It would be nice if there are articles that show how to build a simple helloworld.master and helloworld.aspx and a simple site definition (bare minimum). Do you know of any resources that might help me?
Since you are talking about page layouts, I assume you have MOSS and not just WSS. What you are saying is possible i believe. From my experience, creating my own site definition wasn\’t an easy task. Everything else seems pretty doable.
As far as relating a site definition to a master page, typically you can use one of the master page shortcut urls to always use the default.master of the current site. Or you can type in the static url to a specific master page.
As far as resources go, check out the MOSS 2007 SDK. It comes with a tool that will convert a site to a site definition. In my experience it doesn\’t work that great but can get you going in the right direction.
I\’ve posted some new HttpModule code that doesn\’t require the master page to have a code behind page. I also have this code setup on my personal sharepoint site: [URL=http://sharepoint.thelineberrys.com]http://sharepoint.thelineberrys.com[/URL]. So go there and click view all site content for an example.
The default.master file has a typo in it that causing rendering issues on browsers other than IE. The following is incorrect:
[CODE]
[/CODE]
This is actually an ASP.NET PlaceHolder control. It needs to be changed to:
[CODE]
…
[/CODE]
Changing this fixes some issues with borders being drawn incorrectly and not connecting to the bottom of the page.
For some reason it wasn\’t replacing the application.master for me, so i decided to debug within visual studio attaching it to the w3wp procces, when it gets to: HttpContext.Current.CurrentHandler it is null, and thus doesn\’t ever replace it.
Do you have any idea what would cause HttpContext.Current.CurrentHandler to be null? 😕 😕
If anyone has input please respond! thanks much!
Great & helpfull article. Thank you.
Maybe you should add something like this below the construction of the variable [b]basepath[/b]
// check for port number
if (context.Request.Url.Port != 80)
{
string strPortExtension = \”:\” + context.Request.Url.Port.ToString();
basepath += strPortExtension;
}
Otherwise you got the typical SharePoint Error Message if you\’re using ports other than 80.
Greetings,
Jm
Jm, good call. One should also check for https. Thanks.
Good article but when i modified your master page, i have a problem a page with \”unknow error\”..
So i don\’t modified my master page with my template…
The breadcrumb example in your current master page is displayed 2 times on the settings pages have you found a solution to this. Example go to a document library and go to the settings page and the bread crumb is displayed 2 times.
CR, in my latest code which you can download, I have it setup so that the httpmodule should change the css class of the breadcrumb that shouldn\’t be displayed to ms-hidden which should in effect, hide it. This works in our environment. Please make sure that you are using the latest code from the download links.
Hi,
I am trying to use a custom master page with the custom login page I have developed. The login page is deployed in the layouts directory. I am using forms authentication.
I have followed instruction in your article. But I get 401 error when I try to browse the login page i created.
Any help is really appreciated.
Hello! Nice article! Thanks!
If somebody wants to create his own custom master page you can follow the steps described in the article – SharePoint: How to create a custom master page – to create a ghostable copy of a built-in master page and get a full access to the Controls-collection due to subclassing.