Sharing Master Pages Across IIS Applications

I'm working on an ASP.NET site that consists of many government departments each having their own IIS application off of the root of the server so that they can have complete control over their own web.config file, etc..  Each department site inherited an XML template framework I wrote for .NET version 1.1 (http://www.xmlforasp.net/codeSection.aspx?csID=102) to provide a consistent look and feel (header and footer) across all departments even though they each have their own IIS application created.  The XML template solution worked great but wasn't as powerful as Master Pages by any means since it didn't support code-behind files and had no designer support (to name just 2 of its short-comings).

In migrating some of the department sites to .NET V2 and Master Pages I ran into the problem of sharing the master page located at the root of the site across all of the child department sites.  Since master pages are derived from user controls they can't be shared across IIS applications (without some virtual directory hacks anyway).  After being pointed to an excellent article Microsoft's Scott Guthrie wrote (http://weblogs.asp.net/scottgu/archive/2005/08/28/423888.aspx) about re-using User Controls and other things across ASP.NET sites (thanks Matias) and playing around with the concept awhile, I believe I have a way to share master pages across multiple IIS applications.  In a nutshell, I stick the root master page in the GAC.

Here are the steps I went through.  I haven't done extension testing (only a basic master page without links to images or anything) but the simple solution seems to work great.

1.  Create an empty Website in VS.NET 2005 (delete anything in it including App_Data and Default.aspx).
2.  Add a master page into the Website.  I created a very simple master page named MasterPageBase.master:

<%@ Master Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "
http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="
http://www.w3.org/1999/xhtml
" >
<head runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        Header
        <br />
        <asp:contentplaceholder id="ContentPlaceHolder1" runat="server">
        </asp:contentplaceholder>
        <br />
        Footer
    </div>
    </form>
</body>
</html>

3.  Select Build | Publish Website from the VS.NET menu.
4.  On the screen that follows select a target site, and check all of the checkboxes except for the top one that allows for dynamic updates.  Note that I reference a strong name key file named keyfile.snk that I created using the sn.exe command-line tool that ships with .NET.  This is required in order to install assemblies into the GAC.  I used the following syntax to create the file:  sn.exe -k keyfile.snk

5.  After the publish operation completes, open the new Website in VS.NET 2005 (named MasterDemo in the example above).  You should see a new assembly (with a somewhat strange name) in the Bin folder.  This assembly is your master page in compiled form.
6.  Install the assembly into the GAC using gacutil.exe or drag-and-drop it into c:\Windows\Assembly using Windows Explorer.  Once you've done this delete the original assembly as well as the newly created XML files associated with it from the Website.
7.  Add a web.config file into your project and add the following within the <system.web> begin and end tags.  You'll need to change the name of the assembly to the name that is generated for your project (the one you added into the GAC) and change the PublicKeyToken to the one you see in the GAC.  Note that the assembly attribute value shouldn't wrap at all.  I didn't give my base master page a version for simplicity here but you can by applying the [assembly: AssemblyVersion("1.0.0.0")] attribute to the master page code-behind class.

<compilation debug="true">
   <assemblies>
     <add assembly="App_Web_masterpagebase.master.cdcab7d2, Version=0.0.0.0,
        Culture=neutral, PublicKeyToken=cceb8435cfc68486" />
   </assemblies>
</compilation>

8.  Add a master page into the Website but don't create a code-behind page for it (you can...but it's not needed in this case).  I named mine MasterPage.master.
9.  Remove all code within the new master page and add the following at the top (it should be the ONLY code in the page).  If you named your original master page (the one created in step 2) differently then you'll need to change your Inherits value.  Use the object browser to see the name of your class that is within the .dll generated in step 4.  It should be within an ASP namespace.

<%@ Master Language="C#" Inherits="ASP.masterpagebase_master" %>

10.  Create a content page that references MasterPage.master (the one you created in the previous step).  The default ContentPlaceHolderID is ContentPlaceHolder1 so use that in the <asp:Content> tag unless you gave the id a different name in step 2. 

These 10 steps probably seem like a lot of work, but now any sub IIS application can leverage the same master page used by other IIS applications by placing the empty MasterPage.master file into the application and updating the web.config file to point to the master page assembly in the GAC.  The downfall of this is that you of course have to recompile the base master page and put it back into the GAC each time you need to make a change.

I haven't had time to experiment with ways to break this (although there's probably a better way to do this or something that will break that I haven't encountered yet) but to this point it seems to work for the situation I needed it for and offers a way to potentially share user controls across IIS applications as well....like you've always been able to do with server controls placed in the GAC.

UPDATE (11/17/2005):

In working with more complex master pages I kept getting a "could not find string resource" error.  I used Reflector to analyze the code generated when the master page is compiled into the assembly and found out that a call to CreateResourceBasedLiteralControl() was being made rather than embedding the actual HTML from the master page into the assembly.  With some help from my good pal Bill Shaw we discovered that if we stripped out some of the whitespace in the HTML it would compile correctly.  For example, we changed:

<td align="left" valign="top" height="45">
    <www:HtmlOutput ID="egovHeader" runat="Server"
        XmlSource="/XML/SiteLinks.xml"
     XsltSource="/XSLT/Header.xslt"
     LanguageCookieName="EgovCookie/Language" />              
</td>

To the following (all one line):

<td align="left" valign="top" height="45"><www:HtmlOutput ID="egovHeader" runat="Server" XmlSource="/XML/SiteLinks.xml" XsltSource="/XSLT/Header.xslt" LanguageCookieName="EgovCookie/Language" /></td>

So, if you try this technique and get a string resource error you'll likely have to play around with your HTML in the base master page until no CreateResourceBasedLiteralControl() calls are made in the generated code (use Reflector to take a look:  http://www.aisto.com/roeder/dotnet).

comments powered by Disqus

14 Comments

  • Nice work Dan. Thanks for sharing.

  • Thanks Lynn...I've played around with those but they didn't seem to convert the master page into the proper type of class within the assembly. I'm still looking into why the deployment project extension generates a different assembly than the Publish Web option.



    Dan

  • I follow your instructions but in final step I got Content page with no ContentPlaceHolder on it colored in gray and I can't drop any control from tool box on it.

    Did I miss something?

  • Dan,

    I have to say that this workaround is amazing. I have been searching for a way to implement global master pages for months and I am hoping that this will actually be the method that will work. I am able to get it working by using the "Create website" option, however we are using Web Application Projects for our apps, and I am trying to implement the same GAC-driven approach using the assembly that is generated when a WAP is built. Have you had any luck in accomplishing that? I've gotten as far as adding it to the GAC, but I'm unsure how to change the inheritance/references. If you have time to discuss this offline, I would be forever grateful.

    Thank you,
    Kyle Parker
    Ball State University

  • You can use an add-on from Microsoft (Web Deployment Projects) to name your DLL properly.

  • Dan, I'm getting a warning in my master page when I tell it to inherit the ASP.masterpagebase_master. The warning I'm getting is this:

    property 'Profile' shadows an overloadable member declared in the base class 'masterpagebase_master'. If you want to overload the base method, this method must be declared 'Overloads'.

    Master page markup is 1 line:



    -------------------------

    I think it's in how I'm building the initial master page. I went through the steps a few times and keep getting the same result.

    any thoughts?

  • Tough to say without seeing the app, but in your Master Page are you calling a property Profile or something along those lines? I haven't seen that warning before when doing this so I'm guessing it's something with the custom code in your master page (although I could certainly be wrong).

  • Thanks for the article Dan, I am trying to use the technique to allow us to share one master page across a set of intranet applications. I was wondering if you could go into some more detail about how to find the CreateResourceBasedLiteralControl references? I downloaded the reflector tool, but don't seem to find any references to that, and I am getting the error loading the string resources.

    Thanks,
    Curt

  • It can be hard to find, but you'll need to look through everything in the assembly (different methods, etc.) that you generate and make sure that there are no calls to that CreateResourceBasedLiteralControl() method. If you're getting that error then make sure you remove as much whitespace as you can from the master. That seems to fix it in most situations.

  • I'm not sure what character 0x166 represents but I'm wondering if you may have a hidden type unicode character somewhere in your page that is causing the problem since that seems to show up in both CreateResourceBasedLiteralControl() calls. It's tough to say though. I'd probably start removing different characters to see if you can locate the general area where this is occurring and what is causing it. Not a fun task I'll admit though unfortunately.

  • I don't think it's got anything to do with the 0x166. That just represents the length of the string passed to the CRBLC call. Based on the information in the Reflector code, the markup in question is provided below:



    This is exactly all the markup between the cphTop ConentPlaceHolder and the cphLeft ContentPlaceHolder, leaving me to believe that the problem generating the first call is in there somewhere. I just can't find it as I don't see any extraneous characters.

  • Correct, 0X166 is first used as the size and then the offset. So, have you checked that the visible size matches with what it says the size is? I don't see anything wrong just glancing at it, so there has to be more going on behind the scenes I would guess. At this point I'd start deleting sections of the code to see if you can't narrow down the issue (experimentation basically). I really don't have any other suggestions other than doing that since visually it seems to be normal code.

  • Hi I'm having problems when i try to run the website it informs that

    Parser Error Message: Could not load file or assembly 'App_Web_masterpagebase.master.cdcab7d2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=04fe06ff2beb1ab6' or one of its dependencies. The system cannot find the file specified.
    and the problem is in web.config line 17
    Line 15:
    Line 16:
    Line 17:
    Line 18:
    Line 19:
    Can you help me?

  • I'd make sure that the assembly is installed in the GAC properly and that there aren't any typos in assembly attribute of web.config. If it is in the GAC properly then it's really hard to say without being able to inspect the application more.

Comments have been disabled for this content.