Sharing Master Pages Across IIS Applications

来源:互联网 发布:返利网源码下载 编辑:程序博客网 时间:2024/06/11 20:01

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

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).

" >
<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>