09 March 2007

Sharing Master Pages amongst Applications by Embedding it in a Dll

If you need to share Master Pages across applications and you don't want to create a ton of virtual directories, below is a way to do that. You can embed the Master Page as a Resource in your Dll and use VirtualPathProvider mechanism to call the Resource.

VirtualPathProvider is a new functionality provided in .Net Framework 2.0, this functionality allows you to retrieve pages/resources from a virtual file system. This means that you can create web application to serve pages from a Database, Zip file, etc.

You will need to inherit and provide functionality for these 3 classes –

1.       VirtualPathProvider class provides a set of methods to implement the Virtual File System. It has methods which you will need to override like –

a.       FileExists – indicates whether a file exists in the virtual file system

b.      GetFile –This method gets the file from the virtual file system.

c.       DirectoryExists -  indicates whether a directory exists in the virtual file system.

d.      GetDirectory – This method gets a virtual directory from the virtual file system.

2.       VirtualFile class represents a file object in a virtual file or resource space. You will need to override Open() method.

3.       VirtualDirectory  class Represents a directory object in a virtual file. I am not including this functionality in this example, as its not used.

 

First, I will create the Master page (and its code behind) and embed it as a Resource

 Set build action to Embedded Resource

Next I will create the VirtualPathProvider, by inheriting from System.Web.Hosting.VirtualPathProvider. You need to override FileExists and GetFile methods

 

public override bool FileExists(string virtualPath)

{

   if (IsPathVirtual(virtualPath))

   {

      MasterPageVirtualFile file = (MasterPageVirtualFile)GetFile(virtualPath);

      return (file == null) ? false : true;

   }

   else

   {

      return Previous.FileExists(virtualPath);

   }

}

 

public override VirtualFile GetFile(string virtualPath)

{

   if (IsPathVirtual(virtualPath))

   {

      return new MasterPageVirtualFile(virtualPath);

   }

   else

   {

   return Previous.GetFile(virtualPath);

   }

}

Both the methods call IsPathVirtual method to check if the file requested is a Virtual file.

 

private static bool IsPathVirtual(string virtualPath)

{

String checkPath = VirtualPathUtility.ToAppRelative(virtualPath);

return checkPath.StartsWith(VirtualMasterPagePath,   StringComparison.InvariantCultureIgnoreCase);

}

 

Next I will create the VirtualFile and implement Open()

 

public MasterPageVirtualFile(string virtualPath): base(virtualPath)

{

this.virPath = virtualPath;

}

 

public override Stream Open()

{

return ReadResource(virPath);

}

 

private static Stream ReadResource(string embeddedFileName)

{

string resourceFileName = VirtualPathUtility.GetFileName(embeddedFileName);

      Assembly assembly = Assembly.GetExecutingAssembly();

return assembly.GetManifestResourceStream(

Constants.MasterPageKeys.VirtualPathProviderResourceLocation + "." + resourceFileName);

}

Now I will use this VirtualPathProvider in the Web Application.
First you need to Register the VirtualPathProvider with the Application. You can do that in the Application_Start of Global.asax.


void
Application_Start(object sender, EventArgs e)

{

    MasterPageVirtualPathProvider vpp = new MasterPageVirtualPathProvider();

    HostingEnvironment.RegisterVirtualPathProvider(vpp);

}

Next, you will need to register the Master page in the PreInit event of the page.

protected override void OnPreInit(EventArgs e)

{

   MasterPageFile = MasterPageVirtualPathProvider.MasterPageFileLocation;

   base.OnPreInit(e);

}

The advantages are plentiful with this approach –

1.       You can distribute Master pages inside Dll’s.

2.       You can share Master pages amongst different applications.

3.       You can extend this approach for Themes, Pages, etc.

Having said the advantages you need to note that this approach has these shortcomings –

1.       No designer support.

2.       You cannot bind the Master page inside the ASPX in the <%@Page%> directive.

 

 

Comments

# Jon Gallant's Blog said:

Piyush Shah , a dev on my team, developed a way to embed MasterPages in assemblies using VirtualPathProvider

27 March 07 at 12:38 PM
# BusinessRx Reading List said:

The VirtualPathProvider is one of the ASP.NET pieces I looked at when 2.0 was new. I suspected then that

04 April 07 at 11:55 PM
# Kungen said:

"I suspected then that"...? What?

20 April 07 at 7:13 AM
# shahpiyush said:

That was a trackback from K Scott Allen's blog. Here is the link for the complete sentence -

http://odetocode.com/Blogs/scott/archive/2007/04/04/10666.aspx

20 April 07 at 1:16 PM
# Sergio Pereira said:

Thanks for sharing this. I was about to try a similar thing and you saved me a lot of trouble. Just as a tip for anyone implementing all this, you do not need to embed the .master.cs file. You can compile it normally, just remove the CodeFile/Codebehind property from the master directive in the .master file (make sure the Inherits stays there).

30 June 07 at 9:35 PM
# Steve Caravajal's Ramblings said:

Interesting read...ran across this while developing for a recent POC Sharing Master Pages amongst Applications

13 August 07 at 12:49 PM
# Hilton Giesenow's Jumbled Mind said:

I was working with VirtualPathProviders today for an upcoming talk at Tech Ed. VPPs are a technique whereby

24 September 07 at 4:36 PM
# Helaba said:

Hi,

I just tried your approach.

I opened your project(EmbedMasterPage.zip).

I just opened new web site project and added a new reference to VirtualPathProvider.dll. Then I added the OnPreInit Event to Default.aspx.cs.

Then I added new Global.asax and added Application_Start Event to it.

But the new project is not compilable - "VirtualPathProvider.MasterPageVirtualPathProvider does not contain a definition for masterPageFileLocation". Hope you can help me... otherwise I don't know how to implement the MasterPage via a DLL.

16 October 07 at 12:25 PM
# Helaba said:

Additional:

Starting the app with the result:

"Server Error in '/WebSite1' Application"

"Content controls have to be top-level controls in a content Page or a nested master page that references a master page"

16 October 07 at 1:01 PM
# shahpiyush said:

Can you make sure you do not have any content outside the <asp:content> tag?

17 October 07 at 8:56 PM
# ian said:

I've tried setting this up, but the Assmebly stream in ReadResource always returns null for me when I call this from the web application. My guess is that I haven't set the three class constants in the VirtualPathProvider class correctly. Can you explain a bit more about when each of those constants represents and how to modify them for a project?

01 November 07 at 10:04 PM
# Joe Chung said:

Hi, Piyush, it was nice to meet you today!  I was curious how you managed to distribute a master page in a DLL, and wouldn't you know it?  You have a blog post describing exactly how to do it.  Excellent!

I wonder if something similar could be done for Web User Controls as well.

12 January 08 at 6:05 AM
# shahpiyush said:

Yep. Absolutely, the same concept can be used for Usercontrols as well.

1. Embedd UserControls as Resource.

2. Read the string as a MemoryStream from the Assembly.

3. Use Page.ParseControl to add it to your page. http://msdn2.microsoft.com/en-us/library/kz3ffe28.aspx

However if this is something you are sharing amongst applications I would create a Server WebControl as above may not be that performant. With Server Control you can GAC it, have designer support, toolbar support, etc.

12 January 08 at 8:52 PM
# Joe Chung said:

I couldn't get ParseControl to do the job (HttpParseException), but System.Web.Compilation.BuildManager.CreateInstanceFromVirtualPath did the trick!

15 January 08 at 1:32 AM
# Joe Chung said:

Oops, I mean LoadControl, not CreateInstanceFromVirtualPath.  CreateInstanceFromVirutalPath doesn't deal with server-side controls in the User Control Markup that well.

15 January 08 at 1:45 AM
# Jayanthi said:

Hi Piyush,

Thanks so much for the brilliant technique. I've extended the idea, and using HttpModule to register master page, and all works fine.

However, i'm stuck when I create a deployment project to the webapp project that uses the dll. What should the properties look like? The project has the masterpage dll in the bin (Copy to Output).I get a runtime error that it cannot find the virtual directory for the masterpage. Any help or pointers will be greatly appreciated! Thanks!

21 February 08 at 2:41 PM
# Jayanthi said:

After going through the MSDN documentation for the nth time, I finally saw the 'Note:' that this will not work for Precompiled websites.

But I also found a workaround here

http://sunali.com/2008/01/09/virtualpathprovider-in-precompiled-web-sites/

Although, i'm not too happy since I have to muck with an internal method using reflection.

Could you please give an update, if at all this will be fixed? (VPP in precompiled apps)

Appreciate your time and all the great work - thanks!

21 February 08 at 4:04 PM
# shahpiyush said:

Thanks for sharing the link. As it said the problem is in the HostingEnvironment class. Unfortunately I do not know why this is there and will it be fixed.

21 February 08 at 9:41 PM
# Jayanthi said:

I tried the workaround using reflection to call the internal method of the HostingEnvironment class - Now i'm able to register the master page, however i get another error now

<MasterPageDirectory/Master page> has not been pre-compiled, and cannot be requested.

I'm developing a framework for several webapps in my company,and not having the webapps precompiled is not an option.

Any suggestions ?

22 February 08 at 12:03 PM
# Jayanthi said:

Ok - I finally have it all working.

For precompilation, apart from the workaround mentioned above, if we use a web application project as opposed to website, you have to remove the master page reference from the aspx files (I had them point to a dummy master page, for designer support, which we don't need in VS2008). However if you have a website solution, you don't need any intervention.

I have a Httpmodule instead of Global.asax to register the VPP, and a base page that adds controls dynamically to the Master page, all wrapped in a dll.

The website page will just inherit from this base page.

Thanks !

27 February 08 at 10:55 AM
# shahpiyush said:

Cool Nice tip. Thanks for keeping me updated.

27 February 08 at 3:55 PM
# bharman said:

In reading the thread above, there is a reference to a EmbedMasterPage.zip file that I assume contains the code example described.  Where is that available?

05 March 08 at 4:47 PM
# bharman said:

Similar to ian's problem mentioned earlier, I too, continue to get a null reference when I modify the constants in the MasterPageVirtualPageProvider.

My constants are:

public const string MasterPageFileLocation = "~/MasterPage.master";

       public const string VirtualPathProviderResourceLocation = "VirtualPathProvider.Resources";

       public const string VirtualMasterPagePath = "~/";

I don't have the master page stored in any folders...stored in the root of the project.

Any ideas what I'm doing wrong?

Thanks for your help.

06 March 08 at 5:13 PM
# shahpiyush said:

bharman,

The code shown for EmbedMasterPage in this post can be found here - http://blogs.msdn.com/shahpiyush/attachment/1847195.ashx

06 March 08 at 5:24 PM
# shahpiyush said:

Regarding the contstants. Here is the description -

VirtualMasterPagePath - This is the path which should be handled by the VirtualPathProvider

VirtualPathProviderResourceLocation - In the example the Master page is stored in the resource folder so that is what this contant is there for.

MasterPageFileLocation - This is the location of the Master page which you can call from your client application.

I would advise you to have the Virtual Path as a folder rathern than at the root. Let me know if that works.

06 March 08 at 5:27 PM
# Anubhuti said:

Hi Piyush,

Thanks for this blog. Actually I am trying to create Global master page since a week by publishing it as DLL and then putting it in GAC but this was giving me a very Irritating Error:

An error occurred while try to load the string resources (FindResource failed with error -2147023083).

Which went away when the HTML in Global Master Page was very Small.

I am going to try this.

18 April 08 at 4:00 PM
# Anumole Mathew said:

Hi,

I am using a VB solution for the Master page and in that I am getting the return of this function ReadResource as nothing that is :

assembly.GetManifestResourceStream(MasterPageVirtualPathProvider.VirtualPathProviderResourceLocation + "." + resourceFileName).

How can make it available in VB.Net

06 June 08 at 5:47 AM
# Raj said:

How does this work, if I need to have:

- Any ASP.NET standard server controls or custom server controls in my master mage.

- user controls in my master mage.

- nested master pages

11 June 08 at 8:17 PM
# shahpiyush said:

Anumole Matthew,

For VB.Net related code on Assembly, check this support article -

http://support.microsoft.com/kb/319291

11 June 08 at 11:49 PM
# shahpiyush said:

Raj,

- Any ASP.NET standard server controls or custom server controls in my master mage.

- user controls in my master mage.

- nested master pages

It should work like any other master page. All I am doing is giving ASP.Net master page from DLL instead of File system.

HTH

11 June 08 at 11:50 PM
# Brian said:

Is the designer support fixed in VS2008? I see that Jayanthi mentioned "I had them point to a dummy master page, for designer support, which we don't need in VS2008".

Brian

12 June 08 at 10:10 AM
# shahpiyush said:

Brian,

No. Unfortunately that will not be available with this approach.

12 June 08 at 12:19 PM
# Santhosh said:

I have several projects that is supposed to share same set of master pages. Also I have some code behind functionality that is common across applications. I need share these along with the master pages.

In the above discussed methods I see that there is no way to get designer support. I would appreciate if someone give me some pointers on how to share master pages without loosing the designer support.

Please help!

Thanks in advance

11 July 08 at 11:13 AM
# Eric said:

This example seems really incomplete.

05 September 08 at 4:29 PM
# shahpiyush said:

Eric,

What is missing for you?

05 September 08 at 4:34 PM
# Eric said:

What is this:  public const string VirtualMasterPagePath = "~/MasterPageDir/";

Can VirtualMasterPagePath be set to anything?

I keep getting stack overflow errors in Page_PreInit of my page that consumes the master page.  I have a feeling that it is the constants declared in 'MasterPageVirtualPathProvider.cs' not being set properly (by me).

08 September 08 at 2:26 PM
# Nate said:

Hi,

I tried this method of storing MasterPages in DLLs and it worked great, it does basically exactly what I want it to do, but with one restriction...  It only works if I remove the caching functionality from the MasterPageVirtualFile.Open function (its in the source you provide, but not in this post...)

The issue is that whenever it retrieves a file from the cache (it works fine the first time I open the application, but does not work subsequently), I get an error telling me the stream is not open.

Could you shed any light on this?

Thanks,

Nate

15 September 08 at 1:34 PM
# Matt Poland said:

FYI: In converting this to VB, I had to remove ".Resources" from the VirtualPathProviderResourceLocation.  I also had to use Sergio Pereira's suggestion of compiling the master page class.

17 September 08 at 7:05 PM
# Matt Poland said:

For some further feedback (and the sake of those just diving in), the virtual path provider and virtual file provider classes could be named EmbeddedResourceVirtualFile and EmbeddedResourceVirtualPathProvider if you handle your constants differently.  Basically if you had 10 embedded master pages, those two classes could serve all of them.  The sample makes it seem that you should create different virtual provider classes for each master page whereas those two classes could serve anything embedded (images, master pages, scripts, styles, etc).

18 September 08 at 9:49 AM
# Jyotishka Bora said:

Has anyone got this error while trying to use the embedded masterpage??

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.ArgumentNullException: Value cannot be null.

Parameter name: value

Source Error:

Line 33:                 if (HttpContext.Current.Cache[virPath] == null)

Line 34:                 {

Line 35:                     HttpContext.Current.Cache.Insert(virPath, ReadResource(virPath));

Line 36:                 }

Line 37:                 return (Stream)HttpContext.Current.Cache[virPath];

21 October 08 at 3:03 PM
# Steph said:

Hello,

I have got the same issue than you and finally found the root cause by step by step debugging:

You need to set BOTH the Master Page and its code behind as being Embedded resources. The error is poping up because the .cs code behind file is not in your resource.

Steph.

10 November 08 at 12:30 PM
# Alan said:

For anyone having problems loading the manifest resource stream, make sure you are correctly referencing the resouce.  An easy way to do that is to just pull up

assembly__1.GetManifestResourceNames

It will show you the name of all of the resources in the executing assembly.  In my case the ".Resources" was unnecessary, and my assembly name was different.  My call ended up looking like this:

assembly__1.GetManifestResourceStream("EmbeddedMasterPage.MasterPage.Master")

EmbeddedMasterPage being the name of my project/assembly

14 November 08 at 12:21 PM
# DotNetKicks.com said:

You've been kicked (a good thing) - Trackback from DotNetKicks.com

25 November 08 at 10:56 AM
# Claudio said:

Hi! Some people complains about performance issues using VirtualPathProvider. Mostly related to app pool restarting frequently. This is related to no (or bad) implementation of GetFileHash and GetCacheDependency. I don't see anything related to those methods here :-(

Any suggestions about how to implement them and avoid performance issues? Thanks!

05 August 09 at 12:21 PM
# Gary said:

Is anyone having trouble publishing their website?

Building it in Visual Studio seems to work out but publishing results to an "Index was outside the bounds of the array." and errors "Unrecognized tag prefix or device filter 'asp'.".

My original website was publishing before integrating the virtual path provider.

I appreciate any tips or suggestions... thanks!

01 October 09 at 1:34 PM
# Gary said:

Found a solution / quick fix for my problem.

For some reason, deleting the "Visual Studio 2005" folder works.

01 October 09 at 1:57 PM
# Paulbi said:

Hi,

One question I have!

Rather interestingly I place the below directive into the 'VirtualPathProviderTest.aspx' and it worked (although I had to ignore the compile time error)...

MasterPageFile="~/MasterPageDir/VirtualPathProvider.dll/MasterPage.master"

...is there a way to get rid of the error?

For me this is a preferred method to reference the master page I want to use.

Cheers,

Paul

15 October 09 at 8:58 AM
# Mack said:

In my MasterPage I added an image. I tried setting its Build Action to all possible values, but when I run the application, the image doesn't show up. Any help will be really appreciated. Following is the line I use to add image in MasterPage:

<asp:Image ID="imgBirthday" ImageUrl="~/Resources/images/happy-birthday.jpg" runat="server" />

16 October 09 at 12:49 PM
# Mack said:

In my MasterPage I added a JavaScrip file and an image. I tried setting image's Build Action to Embeded Resource, but when I run the application, the image doesn't show up. Any help will be really appreciated. Following is the line I use to add image in MasterPage:

<asp:Image ID="imgBirthday" ImageUrl="~/Resources/images/happy-birthday.jpg" runat="server" />

Please also give me a hint on how to add javascript file in Master page.

16 October 09 at 12:58 PM
# shahpiyush said:

Mack,

The image has to be in the virtual path. If you see the example project the virtual path is -

public const string VirtualMasterPagePath = "~/MasterPageDir/";

Try this -

<asp:Image ID="imgBirthday" ImageUrl="~/MasterPageDir/images/happy-birthday.jpg" runat="server" />

Embedding javascript should be the same way as images, you need to make sure the virtual path is correct.

HTH.

16 October 09 at 4:20 PM
# Paulbi said:

Hi shahpiyush,

Did you have a chance to look at my question yet?

It would be really cool if you could help me :)

Thanks,

Paul

19 October 09 at 5:53 AM
# Mack said:

@ Piyush: The trick worked, but didn't actually work too. The image shows up one time but if the page is refreshed by button click or anything, image disappears.

Then I even added a constant member in class MasterPageVirtualPathProvider as follows:

public const string VirtualBirthdayImageLocation = "~/MasterPageFolder/images/happy-birthday.jpg";

and my page's OnPreInit looks like this:

       MasterPageFile = MasterPageVirtualPathProvider.MasterPageFileLocation;

       ((Image)Page.Master.FindControl("imgBirthday")).ImageUrl = MasterPageVirtualPathProvider.VirtualBirthdayImageLocation;      

       base.OnPreInit(e);

Even that didn't help; image shows up one time, but on page refresh disappears. Thanks so much for your help...

19 October 09 at 8:29 AM
# Mack said:

And also, I'm still unable to add Javascript file and/or css stylesheets in my master page; i tried all combinations of virtual and physical paths.

20 October 09 at 6:38 AM
# Ningister said:

All of these can work out, but NOT from the sample.  Here are changes or important things.

1. In method public override Stream Open()

replace content with just "return ReadResource(virPath);", the damn cache cannot work out for things like image, js and css (Microsoft bug!).

2. In method private static Stream ReadResource(string embeddedFileName)

change to following content to support reading from folders under resources, otherwise, you can put only files under resources directly.

"

string resourceFileName = VirtualPathUtility.ToAppRelative(embeddedFileName);

resourceFileName = resourceFileName.Substring(MasterPageVirtualPathProvider.VirtualMasterPagePath.Length);

resourceFileName = resourceFileName.Replace("/", ".");

Assembly assembly = Assembly.GetExecutingAssembly();

return assembly.GetManifestResourceStream(MasterPageVirtualPathProvider.VirtualPathProviderResourceLocation + "." + resourceFileName);

Now it is ready to solve master page, and anything you refer to the folder name of MasterPageDir.  However, here is a HUGE different you have to remember here.  When you using any asp.net server control/masterpage you have to use prefix of "~/MasterPageDir" and if you refer to any HTML tag, like js, css or plain img you have to refer to "MasterPageDir" without the "~/" part!

The original post presents a good idea, but never servers purpose as a complete sample from Microsoft.  We need remembering that microsoft always presents great ideas with tons of bugs ....

05 December 09 at 9:27 AM
# crmscribe said:

Great Post, was exactly looking for this.

@Ningister,

What have you done innovative that you are being curt? He/she does not even need to give sample, but its good as I get to see firsthand. Give credit where its due.

05 December 09 at 3:06 PM
New Comments to this post are disabled
Page view tracker