Udostępnij za pośrednictwem


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.

 

EmbedMasterPage.zip

Comments

  • Anonymous
    March 27, 2007
    Piyush Shah , a dev on my team, developed a way to embed MasterPages in assemblies using VirtualPathProvider

  • Anonymous
    April 04, 2007
    The VirtualPathProvider is one of the ASP.NET pieces I looked at when 2.0 was new. I suspected then that

  • Anonymous
    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.aspx

  • Anonymous
    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 Applications

  • Anonymous
    September 24, 2007
    I was working with VirtualPathProviders today for an upcoming talk at Tech Ed. VPPs are a technique whereby

  • Anonymous
    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.

  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.
  • 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.ashx

  • Anonymous
    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.Net

  • Anonymous
    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
  • 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". Brian

  • Anonymous
    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 advance

  • Anonymous
    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, Nate

  • Anonymous
    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 removed

  • Anonymous
    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/assembly

  • Anonymous
    November 25, 2008
    You've been kicked (a good thing) - Trackback from DotNetKicks.com

  • Anonymous
    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, Paul

  • Anonymous
    October 16, 2009
    The comment has been removed

  • Anonymous
    October 16, 2009
    The comment has been removed

  • Anonymous
    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, Paul

  • Anonymous
    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 removed

  • Anonymous
    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.