VirtualPathProvider In Precompiled Web Sites

After finishing a project and deploying it on a public server, it is normal that you expect some problems or to-dos like some configuration changes in “web.config” file or somewhere else if you preferred implementing your own configuration architecture.

The question is that if you would expect something to break depending on your choice of deployment, e.g. Precompiled deployment or not. My answer was no until 3 days ago but it is a screaming “YES” for now.

What I am talking about is System.Web.Hosting.VirtualPathProvider class located in System.Web assembly. This class insists on not registering itself to the HostingEnvironment (System.Web.Hosting.HostingEnvironment) if you prefer deploying your website using precompilation.

How come I had this problem? Well, EpiServer is just one of our area of expertises in Propeople and it uses VirtualPathProvider architecture for file system. And although everything works perfectly if you don’t precompile your website, EpiServer says “No registered virtual path providers are accessible” if you precompile it.

The page on MSDN has this note:

If a Web site is precompiled for deployment, content provided by a VirtualPathProvider instance is not compiled, and no VirtualPathProvider instances are used by the precompiled site.

I have been looking on Google about what other people say about the problem and guess what, there is simply “nothing” on Google. Actually there are articles and posts about VirtualPathProvider implementations but the posts always include that VirtualPathProvider does not work if you precompile your website. Then I decided to use the best documentation ever, Reflector!

If you look at the RegisterVirtualPathProvider method of HostingEnvironment class you can see the following code.

 

[AspNetHostingPermission(SecurityAction.Demand, Level=AspNetHostingPermissionLevel.High)]
public static void RegisterVirtualPathProvider(VirtualPathProvider virtualPathProvider)
{
    if (_theHostingEnvironment == null)
    {
        throw new InvalidOperationException();
    }
    if (!BuildManager.IsPrecompiledApp)
    {
        RegisterVirtualPathProviderInternal(virtualPathProvider);
    }
}

 

It uses the internal method RegisterVirtualPathProviderInternal to make the necessary job. Nice but did you realize what it checks before calling this method? It just checks if the website is precompiled and if the answer is “true” then it executes nothing.

They might have their reasons which I haven’t heard until now. What I know is that I could not find any documentation, article or post about their reason/s.

So, basicly , the guilty is not VirtualPathProvider class but HostingEnvironment class because it doesn’t execute anything if the website is precompiled.

Lets get to the solution. Most of you might be aware of “AppInitialize” static method. If you create a static class, put a “public static void” type method and name it to “AppInitialize” and finally put this file in App_Code folder, this method will be executed by the .NET Framework as soon as the AppDomain is created for the website. It is the same behavior as Application_Start event of that you can declare in Global.asax file or in your custom HTTP handler. A sample to the usage of AppInitialize method is below.

public static class AppStart
{
    public static void AppInitialize()
    {
	// code to be executed automatically by the framework
    }
}

For more information about AppInitialize, you can read at Wenlong Dong’s blog, it is described in “Using AppInitialize” title.

He writes:

The above global.asax does not work for non-HTTP protocols such as net.tcp and net.pipe that is supported by the Windows Activation Service (WAS) on Windows Vista. There is no protocol-agnostic counterpart for HttpApplication in this case.

Fortunately, ASP.NET provides a simple hook that works in a protocol agnostic way. The hook is based on the following AppInitialize method:

public static void AppInitialize();

This method can be put in any type that is defined in a C# file in the application’s \App_Code directory. When the AppDomain is started, ASP.NET checks whether there is a type that has such as method (exact name and signature) and invokes it. Note that the AppInitialize method can be only defined once and it has to be in a code file instead of a pre-compiled assembly.

Finally, what do we execute here? We rock our reflection knowledge! It follows below.

using System.Web.Hosting;
using System.Reflection;
using System.Collections.Specialized;

public static class AppStart
{
    public static void AppInitialize()
    {
        // we create a new instance of our own VirtualPathProvider.
        MyOwn.VirtualPathProvider providerInstance = new MyOwn.VirtualPathProvider();
        // any settings about your VirtualPathProvider may go here.

        // we get the current instance of HostingEnvironment class. We can't create a new one
        // because it is not allowed to do so. An AppDomain can only have one HostingEnvironment
        // instance.
        HostingEnvironment hostingEnvironmentInstance=(HostingEnvironment)typeof(HostingEnvironment).InvokeMember("_theHostingEnvironment",  BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.GetField, null, null, null);
        if (hostingEnvironmentInstance== null)
            return;

        // we get the MethodInfo for RegisterVirtualPathProviderInternal method which is internal
        // and also static.
        MethodInfo mi = typeof(HostingEnvironment).GetMethod("RegisterVirtualPathProviderInternal", BindingFlags.NonPublic | BindingFlags.Static);
        if (mi == null)
            return;

        // finally we invoke RegisterVirtualPathProviderInternal method with one argument which
        // is the instance of our own VirtualPathProvider.
        mi.Invoke(hostingEnvironmentInstance, new object[] { (VirtualPathProvider)providerInstance });
    }
}

That is all, it works.

The solution is simple though it is just a work-around and it makes you bound to the .NET Framework, what I mean is that if someone decides to change name of a private field they use inside HostingEnvironment, the code that I wrote will break.

In addition, I just contacted with some people from Microsoft and I will try to directly contact the ASP.NET Product Group as soon as possible and will share the response with you if it is not forbidden to do so with MVP non-disclosure-agreement.

For more information regarding custom VirtualPathProvider implementation, you may want to refer to the following pages.

You may also like...

59 Responses

  1. Martin Kulov says:

    Nice workaround, Josh!
    I just love good workarounds and helpfull tricks like this.

    Thanks for your passion at work!

    Martin Kulov
    http://www.kulov.net

  2. Patrik says:

    Thank you! It really helped alot.

  3. Patrik says:

    I should add that I’ve spent Hours trying to figure out a solution and searching on Google until I found your post on http://www.asp.net.

    Thanks again.

  4. Coskun SUNALI says:

    Hi Patrik,

    I am glad that it worked for you.

    P.S.: Please keep on mind that this hack/work-around is not supported either by myself or by Microsoft.

    Regards,
    Coskun

  5. Jayanthi says:

    Works great – I have to wrap master pages in a dll for multiple webapps to use, and i’am using VPP as a solution. Everything worked seamlessly until I added a web deployment project for a precompiled deployment of the web application. Thanks to your workaround, have it all working now.
    I hope Microsoft provides support for precompiled sites to use VPP architecture sooner.

  6. Coskun SUNALI says:

    Hi Jayanthi,

    Thanks for sharing your comments. I still could not find out why Microsoft does not support it. I will also post about it once I find out their reason(s).

    Regards,
    Coskun

  7. dotGicu says:

    Good workaround, but run this workaround on shared hosting (where I don’t have granted with ReflectionPermission) :( …

  8. Ali says:

    I am getting an error myOwn could not be found. Can you please help.

  9. Dave says:

    Did you ever hear back from Microsoft regarding their reasoning?

  10. Coskun SUNALI says:

    Hi dotGicu,

    This is because you don’t have reflection permission at all, not because you are on a shared hosting. As you may see, the code uses reflection and it simply needs the permission to be executed.

    Hi Dave,

    Unfortunately, not yet. I am hopeful that Scott will respond nowadays. I will keep you updated as soon as I have some information.

    Regards,
    Coskun

  11. Ted Nyberg says:

    It sure would be interesting to hear what Scott (or another ASP.NET team member) would have to say about this… ;)

  12. Nate says:

    Coskun,
    This sounds like a perfect workaround to the pre-compilation issue, but I can’t seem to make it work.
    Does it make any difference that I am doing this through an HTTP Handler rather then AppInitialize?

  13. Nate says:

    I meant HTTP Module, not Handler

  14. Coskun SUNALI says:

    Hi Nate,

    Ted Nyberg (owner of the comment above yours) managed to get it work within a precompiled “Web Application” project. Because I use Web Site structure instead of Web Application, AppInitialize is the only chance for me. It does not work otherwise.

    On the other hand, because Web Applications do not support AppInitialize class, may be it is better if you contact Ted Nyberg directly or he may want to write about his implementation as a comment here.

    Regards,
    Coskun

  15. Ted Nyberg says:

    Hi guys!

    In my web application project I registered the VPPs in the Application_Start() method in Global.asax. It is roughly the equivalent of AppInitialize().

    I haven’t tried registering VPPs in an HTTP module.

    Nate, you should try performing the VPP registration in Application_Start! Good luck!

  16. Wayne says:

    Thanks so much. Like Jayanthi, I have a master page embedded in a DLL for an internal web application framework developed for our company. It was working great until we ran into the precompiled problem. This worked like a charm.

  17. Farhan Gohar says:

    Great Work.

    Thank you very much, it’s helped alot

  18. Shaun Plumb says:

    Many thanks for this!

    I’d just spent a day developing a neat resource-based solution to some problems with our website, only to discover that on compilation it didn’t work!

    I worked at MS for over 3 years and I thought I was used to the kind of dumb decisions they make. Obviously, I have more to learn! ;-)

  19. Asang Dani says:

    Why not use a Shared/static constructor?

    Shared Sub New()
    HostingEnvironment.RegisterVirtualPathProvider(New MyPathProvider())
    End Sub

    or

    public static MyPathProvider( )
    {
    HostingEnvironment.RegisterVirtualPathProvider(New MyPathProvider())
    }

  20. Coskun SUNALI says:

    Hello Asang,

    Have you tried that already?

  21. Julian says:

    Genius, saved me many hours of frustration. Thanks for publishhing this.

  22. John Borders says:

    Hi All,

    I have to host it on my Local PC with Execute Permissions is set to Scripts and Executable only and it does not work, it seems the VPP is not registered. Whereas when it’s set to Scripts only, it works fine. Can you please help me to figure it out?

  23. You’re a GENIUS man…!!
    I have just spent an entire evening due to some £@%%&$$¤ developer in Redmond, and thanx to you I didn’t have to spend WEEKS…!

    Thank you :)

  24. Eamonn Kelly says:

    Thanks for the solution. Like others commenting, I only discovered the pre-compilation issue after I had written the code and was very relieved to find your article.

  25. Brian says:

    For once a blog posting that actually helped me solve a problem. That you so much. I replaced the call to HostingEnvironment.RegisterVirtualPathProvider directly in Application_Start with the reflection calls to the internal version of the method and it worked.

  26. Eric Sims says:

    Has anybody been able to get this to work with a non-updateable web deploy?
    (by using the property pages and de-selecting ‘allow this precompiled site to be updateable’)

    Meaning, just using the placeholder .aspx files and no .ascx files?

    I’m getting an error that it can’t find my user controls (.ascx) because they compiled into a .dll and they don’t exist on the file system.

  27. Gianluca Seno says:

    Hi,
    I’d tryed this workaround on IIS 6 but it never works if I put this code in global.asax nor in appinitialize.

    My project has a root folder with a regular web application, and the a “virtual” folder, a folder that not exists physically, but I’ve develop VirtualPathProvider to see that if I call this folder has to render pages in a assembly, where is another website with aspx,ascx,js, css and more.

    On my IIS 6 I’d set up wildcards for asp.net.

    I’d solved the problem using HttpModules and in the init function I call your solution and works fine to run the first page…

    But there’s another problem…If in my website have a reference to a assebly created I receive everytime an error like this

    Retrieving the COM class factory for component with CLSID {10020200-E260-11CF-AE68-00AA004A34D5} failed due to the following error: 80040154.

    seems that I can’t use assebly different from the system…

    Have you some ideas about this?

  28. Reid Bremner says:

    Thank you for your post it really helped. Did anyone at MS every come back to you with reasons for the decision?

  29. Coskun SUNALI says:

    @Wayne, @Farhan, @Shaun, @Julian, @Thomas, @Eamonn,

    Glad that it worked for you and saved you some hours.

    @Brian,

    Glad that it worked for you as well and thank you for posting that it worked using Application_Start.

    @Reid,

    Unfortunately, I am not aware of any unofficial/official reason for the decision.

    @Guanluca,

    I have the work-around running on several IIS 6 hosted websites on different servers. We only have a wildcard mapping and the initialization code is located within the AppInitialize class.

  30. krish says:

    Hats off Man, worked like a charm…

  31. Fentex says:

    I had just completed a new replacement theming system using dyamically assigned masterpages via a custom virtualpathprovider fetching embedded masterpages from a core dll for an enterprises application framework and was well pleased with myself.

    Until it was deployed to a test server using the formal deployment scripts in place that placed the preocmpiled version in place and it all died in the water, refusing to use my custom virtualfile infrastructure.

    I spent some time trying to nut out how the server configuration differed from the developement environment but thankfully you provided the answer and stopped me tearing my hair out.

    However, I discover, your solution doesn’t help me. after attempting to employ it the master pages were found but now erred when compilation was required.

    Because masterpages embedded as a resource are essentially assigned as a type declaration when set as a value for “MasterPageFile” and compiled on first demand (I’m well annoyed one can’t instantiate then assign an instance of a masterpage to a page)- but a precompiled application does no more compilation and complains during the attmept on assigning MasterPageFile dynamically. My plans seemed sunk.

    Until further research (http://msdn.microsoft.com/en-us/library/aa992039.aspx) revealed the option to precompile applications with an ‘Updateable’ option which leaves the still precompiled app ready to perform any neccessary extra compilations.

    I write this tonight at home – tomorrow hopefully I find out if it works. I suspect using the ‘Updateable’ option may sidestep the problem you resolve here. I’ll get back with some more info on that – whether or not both are required.

  32. Fentex says:


    Coskun SUNALI:

    Because I use Web Site structure instead of Web Application, AppInitialize is the only chance for me. It does not work otherwise.

    In the framework I’m working with the core.dll which contains the masterpages also contains httpmodules that handle authorization, logging, error handling and one for theming.

    The init event in the theming httpmodule is used to register the virtulpathprovider. This works just fine in web applications.

  33. Gokhan Mamaci says:

    Worked for me too. Was looking for such a workaround since a long time. I hope this is fixed also to avoid any future complications.
    Thank you Coskun!

  34. Billy says:

    Genius!!

  35. Tom says:

    This solution has worked for me also, with entering the code into the Application_Start() event in the Global.asax file in a web application project.

    Since this article is now nearly 4 years old, is there any sign of changes from MS so that the site can utilise a VPP whilst being precompiled. If a site is not precompiled, then there is a performace hit due to each page taking a while to load when it receives a request for the first time.

    Tom

  36. Coskun SUNALI says:

    Hi Tom,

    I am still not aware of any official solution on this issue. However, I am glad that the post helps someone out there even after 4 years.

    Coskun

  37. Youssef says:

    I was using this method with CMS5R2.
    but after upgrading to CMS6R2 my VirtualPathProvider crashes … tried to remove the registration worked fine in the local environment but on the server it doesn’t retrieve any of my files.
    Can you please advice what may be the problem?

  38. Coskun SUNALI says:

    Hi Youssef,

    I have, myself, upgraded some 5R2 projects to 6R2 as well. I do not think we have had any problems related to the VPP. One chance is that the provider configuration might have been removed by the upgrade script for some reason. Other than that I do not think I have any idea.

    Coskun

  39. Youssef says:

    Hi Coskun,

    Thanks for your replay.
    It seems that i was using an early version of EPiCode VirtualPathProvider.
    It is working fine now after getting the current version and updating my Provider code.

  40. Coskun SUNALI says:

    Hi Youssef,

    I am happy that it works now for you too.

  41. Very Happy says:

    Hey, thank you for this post! I have been going crazy trying to get this to work. It worked like a champ for me!

  42. chuck says:

    Thanks for the code and the explanation. Definitely fixed my problem. Was very curious on why you did returns if the values were null
    Wouldn’t it better to have the application crash?

    if (hostingEnvironmentInstance== null)
    return;

    if (mi == null)
    return;

  43. chuck says:

    I also had to set EnableUpdateable to TRUE in the PublishProfile for MVC5 VS2012

  44. Coskun SUNALI says:

    Hi chuck,

    Our application should still survive even if it could not register the provider. So, I had to check for nulls and return if necessary. Anyone may safely remove them if their design requires to do so.

    And regarding the EnableUpdatable, everyone commenting here seems to find another trick to do or a checkbox to click here and there :) it seems to be it works for 95% of the people out there. Thank you for the information though. Appreciated by me and I am sure it will be appreciated by someone reading these as well.

  1. Wed, 28 Jul 2010

    […] VirtualPathProvider In Precompiled Web Sites @ sunali.com […]

  2. Fri, 11 Feb 2011

    […] The first hack is about registering the Custom Virtual Path Provider with the Hosting Environment using private reflection detailed in Coskun SONALI’s article here. […]

  3. Sun, 3 Jul 2011

    […] been looking at the workaround which has been posted here: http://sunali.com/2008/01/09/virtualpathprovider-in-precompiled-web-sites/, but so far I haven’t been able to get that to work, either! (Well – it works fine in […]

  4. Fri, 8 Nov 2013

    […] We have tried this way of loading our providers but we still get the same error: http://sunali.com/2008/01/09/virtualpathprovider-in-precompiled-web-sites/ […]

  5. Sat, 9 Nov 2013

    […] been looking at the workaround which has been posted here: http://sunali.com/2008/01/09/virtualpathprovider-in-precompiled-web-sites/, but so far I haven’t been able to get that to work, either! (Well – it works fine in […]

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.