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
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
Anonymous
March 27, 2007
Piyush Shah , a dev on my team, developed a way to embed MasterPages in assemblies using VirtualPathProviderAnonymous
April 04, 2007
The VirtualPathProvider is one of the ASP.NET pieces I looked at when 2.0 was new. I suspected then thatAnonymous
April 19, 2007
"I suspected then that"...? What?Anonymous
April 20, 2007
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.aspxAnonymous
June 30, 2007
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).Anonymous
August 13, 2007
Interesting read...ran across this while developing for a recent POC Sharing Master Pages amongst ApplicationsAnonymous
September 24, 2007
I was working with VirtualPathProviders today for an upcoming talk at Tech Ed. VPPs are a technique wherebyAnonymous
October 16, 2007
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.Anonymous
October 16, 2007
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"Anonymous
October 17, 2007
Can you make sure you do not have any content outside the <asp:content> tag?Anonymous
November 01, 2007
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?Anonymous
January 11, 2008
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.Anonymous
January 12, 2008
Yep. Absolutely, the same concept can be used for Usercontrols as well.
- Embedd UserControls as Resource.
- Read the string as a MemoryStream from the Assembly.
- 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.
Anonymous
January 14, 2008
I couldn't get ParseControl to do the job (HttpParseException), but System.Web.Compilation.BuildManager.CreateInstanceFromVirtualPath did the trick!Anonymous
January 14, 2008
Oops, I mean LoadControl, not CreateInstanceFromVirtualPath. CreateInstanceFromVirutalPath doesn't deal with server-side controls in the User Control Markup that well.Anonymous
February 21, 2008
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!Anonymous
February 21, 2008
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!Anonymous
February 21, 2008
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.Anonymous
February 22, 2008
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 ?Anonymous
February 27, 2008
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 !Anonymous
February 27, 2008
Cool Nice tip. Thanks for keeping me updated.Anonymous
March 05, 2008
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?Anonymous
March 06, 2008
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.Anonymous
March 06, 2008
bharman, The code shown for EmbedMasterPage in this post can be found here - http://blogs.msdn.com/shahpiyush/attachment/1847195.ashxAnonymous
March 06, 2008
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.Anonymous
April 18, 2008
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.Anonymous
June 05, 2008
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.NetAnonymous
June 11, 2008
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
Anonymous
June 11, 2008
Anumole Matthew, For VB.Net related code on Assembly, check this support article - http://support.microsoft.com/kb/319291Anonymous
June 11, 2008
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
Anonymous
June 12, 2008
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". BrianAnonymous
June 12, 2008
Brian, No. Unfortunately that will not be available with this approach.Anonymous
July 11, 2008
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 advanceAnonymous
September 05, 2008
This example seems really incomplete.Anonymous
September 05, 2008
Eric, What is missing for you?Anonymous
September 08, 2008
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).Anonymous
September 15, 2008
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, NateAnonymous
September 17, 2008
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.Anonymous
September 18, 2008
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).Anonymous
October 21, 2008
The comment has been removedAnonymous
November 10, 2008
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.Anonymous
November 14, 2008
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/assemblyAnonymous
November 25, 2008
You've been kicked (a good thing) - Trackback from DotNetKicks.comAnonymous
August 05, 2009
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!Anonymous
October 01, 2009
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!Anonymous
October 01, 2009
Found a solution / quick fix for my problem. For some reason, deleting the "Visual Studio 2005" folder works.Anonymous
October 15, 2009
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, PaulAnonymous
October 16, 2009
The comment has been removedAnonymous
October 16, 2009
The comment has been removedAnonymous
October 16, 2009
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.Anonymous
October 18, 2009
Hi shahpiyush, Did you have a chance to look at my question yet? It would be really cool if you could help me :) Thanks, PaulAnonymous
October 19, 2009
@ 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...Anonymous
October 19, 2009
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.Anonymous
December 05, 2009
The comment has been removedAnonymous
December 05, 2009
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.