Mukarram Mukhtar

Share Master Page (p 2)

Shared Master Page through Virtual Path Provider Classes:

The theory of this approach is little different from the other two approaches; you create a class library project and add a Master page in it, make the master page an embedded resource of the assembly. Add some additional classes that will extend from VirtualPathProvider and VirtualFile classes of .NET. Now create a web application which will be using and sharing master page with other similar applications, let’s call it client application. Add a reference to master page project in your client application. Set client project’s content web pages’ master page file to the one in the dll, use VirtualPathProvider for this purpose. With a little care you can set the correct paths in your client application as well as in your master page application so that client web pages can refer to the master page and rest of the application works fine.

Theory is quite kind of complicated but practically works better than the other approaches. Not that this approach doesn’t have any issues, but again, we’ll try to deal with some of them and try to find workarounds.

Creation of Master Page Application:

Let’s do it step-by-step:

  • Start Microsoft Visual Studio 2005 or later.
  • Click Create New Project
  • In New Project dialog box, select Visual C# in Project types panel on left.
  • Select Class Library in the Templates panel on right.
  • Give a decent physical path and name to your project and select OK.

SMVPCreateProject

  • Right click the project and add a new folder called images.
  • Add an image in images folder, I added MotherTeresa.png
  • Right click project and add a master page and its .cs; I named the two files as MainMaster.master and MainMaster.master.cs.
  • Right click the project and add two class files; I named them as clsVirtualFile.cs and clsVirtualPathProvider.cs.

After completing the above steps your solution explorer should look something like this:

SMVPSolutionExplorer

Next step is to add some simple code in Master page; this can be done by copying the following code in the master page:

<%@ Master Language="C#" AutoEventWireup="true" CodeFile="MainMaster.master.cs" Inherits="MainMaster" %>
<!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 id="Head1" runat="server">
    <title>Untitled Page</title>   
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <h1>Main Master Header</h1>
        <asp:Image ID=" imgHeader" ImageUrl="~/images/MotherTeresa.PNG" runat="server" Width="300px" />
        <h3><asp:Label ID="lblDate" runat="server"></asp:Label></h3>
        <asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">

        </asp:ContentPlaceHolder>
        <h3>Main Master Footer</h3>
    </div>
    </form>
</body>
</html>

For simplicity let’s not add any server side script at this point and let’s see it working in its simplest form. So my MainMaster.master.cs looks like as follows:

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Collections.Generic;

public partial class MainMaster : System.Web.UI.MasterPage
{
    protected void Page_Load(object sender, EventArgs e)
    {

    }
}

After adding the above simple code the next step is to write some code in clsVirtualFile.cs and clsVirtualPathProvider.cs. But before I do that we need to set Build Action property of Master page and the image to be Embedded Resource. Now let’s write the code for clsVirtualFile.cs (line numbers are given for explanation only and are not the part of real code):

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using System.Web;
01 using System.Web.Hosting;

namespace prjShareMasterVirtualPath
{
02    public class clsVirtualFile : VirtualFile
    {
03        static Assembly assembly;

        private string virPath;

04        public clsVirtualFile(string virtualPath)
            : base(virtualPath)
        {
05            this.virPath = virtualPath;
06            assembly = Assembly.GetExecutingAssembly();
        }

        public override Stream Open()
        {
07            return ReadResource(virPath);
        }

        private static Stream ReadResource(string embeddedFileName)
        {
08            string resourceFileName = VirtualPathUtility.GetFileName(embeddedFileName);
09            if (resourceFileName.ToLower().EndsWith(".png") ||
		resourceFileName.ToLower().EndsWith(".jpg") ||
		resourceFileName.ToLower().EndsWith(".gif"))
10                return assembly.GetManifestResourceStream(clsVirtualPathProvider.VirtualPathImageResourceLocation + 
										"." + resourceFileName);
            else
11                return assembly.GetManifestResourceStream(clsVirtualPathProvider.VirtualPathProviderResourceLocation + 
										"." + resourceFileName);

        }
    }
}

Now let’s write the code for clsVirtualPathProvider .cs (line numbers are given for explanation only and are not the part of real code):

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using System.Web;
using System.Web.Hosting;

namespace prjShareMasterVirtualPath
{
01     public class clsVirtualPathProvider : System.Web.Hosting.VirtualPathProvider
    {
02        public const string MasterPageFileLocation = "~/MasterPageFolder/MainMaster.master";
03        public const string VirtualPathProviderResourceLocation = "prjShareMasterVirtualPath";
04        public const string VirtualPathImageResourceLocation = "prjShareMasterVirtualPath.images";
05        public const string VirtualMasterPagePath = "~/MasterPageFolder/";
06        public const string VirtualImagesPath = "~/images/";

        public clsVirtualPathProvider()
            : base()
        {
        }

07        public override bool FileExists(string virtualPath)
        {
            if (IsPathVirtual(virtualPath))
            {
08                clsVirtualFile file = (clsVirtualFile)GetFile(virtualPath);
                return (file == null) ? false : true;
            }
            else
            {
09                return Previous.FileExists(virtualPath);
            }
        }

10        public override VirtualFile GetFile(string virtualPath)
        {
            if (IsPathVirtual(virtualPath))
            {
11                return new clsVirtualFile(virtualPath);
            }
            else
            {
                return Previous.GetFile(virtualPath);
            }
        }
12
        public override System.Web.Caching.CacheDependency GetCacheDependency(string virtualPath, 
	IEnumerable virtualPathDependencies, DateTime utcStart)
        {
            return null;
        }

13        public static bool IsPathVirtual(string virtualPath)
        {
14            String checkPath = VirtualPathUtility.ToAppRelative(virtualPath);
15            return (checkPath.StartsWith(VirtualMasterPagePath, StringComparison.InvariantCultureIgnoreCase) ||
			checkPath.StartsWith(VirtualImagesPath, StringComparison.InvariantCultureIgnoreCase));
        }
    }
}

After writing all this code, compile the project to see if there is any compile time error. If everything is okay then now it’s time for creating master page client application.

Creation of Master Page Client Application:

  • Start Microsoft Visual Studio 2005 or later.
  • Click Create New Project
  • In New Project dialog box, select Visual C# in Project types panel on left.
  • Select ASP.NET Web Application in the Templates panel on right.
  • Give a decent physical path and name to your project and select OK.

SMVPCCreateProject

  • IDE will create a web project for you with a Default.aspx web page in it.
  • Right click the project in solution explorer and add Global.asax in it
  • Right click References folder in solution explorer and add reference to the dll of project prjShareMasterVirtualPath, that we just created in above section
  • Just compile and run the application one time, it will add web.config in the project

After you’ve done the above steps client application’s solution explorer should look like this:

SMVPCSolutionExplorer

Now let’s start coding, and first comes Global.asax; add following two namespaces in your global.asax:

using System.Web.Hosting;
using prjShareMasterVirtualPath;

Then make Application_Start method look like this:

        protected void Application_Start(object sender, EventArgs e)
        {
            clsVirtualPathProvider vpp = new clsVirtualPathProvider();
            HostingEnvironment.RegisterVirtualPathProvider(vpp);
        }

The last line is the most important one in the whole project. This line tells JIT (Just In Time debugger) where to find objects that are referenced through virtual paths. This means, whenever an object e.g. image, master page etc is referenced via virtual path like myImage.ImageUrl = ~/images/MotherTeresa.png, JIT will first try to find it in the current application’s images folder. If it is unable to find it then it will try to resolve it through clsVirtualPathProvider class’s object that we have registered in Hosting Environment in global.asax. When call goes to clsVirtualPathProvider it finds the embedded resource in prjShareMasterVirtualPath’s dll.

Next, let’s take a look at Default.aspx; first change its HTML as follows:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="prjShareMasterVirtualPathClient._Default" %>

<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
    <div>
        Virtual Paths work fine when running via IDE but don't work when published or deployed to IIS.   
    </div>
</asp:Content>

That’s the code of a typical content web page except one difference; in page directive we didn’t set MasterPageFile attribute. The reason is, we don’t have the master page file on the physical location in the project. The master page file is in the dll as an embedded resource which can be referenced in server side script as follows:

using System;
using System.Collections;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;

using prjShareMasterVirtualPath;

namespace prjShareMasterVirtualPathClient
{
    public partial class _Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {

        }

        protected override void OnPreInit(EventArgs e)
        {
            Page.MasterPageFile = clsVirtualPathProvider.MasterPageFileLocation;
            base.OnPreInit(e);
        }
    }
}

This method is quite simple, the overridden OnPreInit method first sets current page’s master page file and then calls parent’s OnPreInit method for rest of the functionality.

After the above coding if we run the application, the web page would look like this:

SMVPCRunning

This approach was my favorite one, reason: it requires least amount of work on client applications’ side in case there is any change in master page application. For example, if you make any change in HTML of the master page or any server side script changes or even if any image changes used in master page; you need to do only one change in the client application i.e. deploy the new dll of the master page application. This theory worked perfectly fine, until I deployed the client application in IIS and it killed my whole excitement. For some weird reason images in the master page application stopped working; which means, now any change in images in master page application, you would have to copy paste the new images in client application as well; thus bringing this approach at the same level of other two. You can try this by creating a virtual directory and running client project using IIS (for details, see part 1 of this article, end of the first section Creation of Master Page Application).

Following is the synopsis of what we did and what we achieved in this tutorial:

Pros and Cons:

  • We can share a master page by embedding master page and its images into the dll of a class library project.
  • Client project’s content web pages can share master page by adding reference to the project at the cost of losing Design view mode
  • If the master page has some images, css files and javascript files then these files have to be copied in all the client web applications
  • If the master page has some server side script then master page project’s dll has to be added in references of all the client web applications
  • Every time we make any change in HTML of the master page, client projects need a new dll in their bin folder
  • Every time there is a change in images, css or javascript files, these files have to copied in all the client web applications
  • Every time there is a change in server side script of the master page, new dll has to be copied in all the client web applications.

Well, that’s it; that’s the end of our second method of sharing a master page.

5 Comments »

  1. how can i add masterpage to class libray…??

    am not getting option for adding masterpage.

    Comment by ambica — June 1, 2010 @ 10:13 am

    • Are you sure you are using .NET 3.5 SP 1? If you still can’t find an option of adding master page in the project then add any file but add it with extension .master, then add another file with extension .master.cs, and finally copy-pate the above code in respective files.

      Comment by Mukarram Mukhtar — June 1, 2010 @ 1:43 pm

  2. Nice post! It got my project going again, thank you.

    One thing I wanted to add in regard to your pros and cons list: you can also embed images, css files, and javascripts in the dll in the same way you do the master page. You need to modify the isVirtualPath and ReadResource operations to account for the new files, but it’s working nicely for me so far.

    Comment by steve — May 23, 2012 @ 7:39 pm

    • Thanks for the feedback and the additional info regarding embeding images, css and javascripts etc. I didn’t know that, and will definitely try. Hopefully it’ll work for me as well! 🙂

      Comment by Mukarram Mukhtar — May 23, 2012 @ 10:14 pm

  3. Everything is very open with a very clear explanation of the issues. It was truly informative. Your website is very useful. Thanks for sharing!

    Comment by John Moder — December 18, 2012 @ 6:48 am


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Create a free website or blog at WordPress.com.

%d bloggers like this: