Home Contact RSS

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.

Thanks Dear Martin Kulov for his feedbacks about this post.

Martin Kulov said,

January 11, 2008 @ 01:33

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

Patrik said,

January 11, 2008 @ 22:13

Thank you! It really helped alot.

Patrik said,

January 11, 2008 @ 22:22

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.

Coskun SUNALI said,

January 18, 2008 @ 03:08

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

Jayanthi said,

February 27, 2008 @ 18:54

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.

Coskun SUNALI said,

March 6, 2008 @ 13:50

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

dotGicu said,

March 20, 2008 @ 00:41

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

Dave said,

July 7, 2008 @ 14:17

Did you ever hear back from Microsoft regarding their reasoning?

Coskun SUNALI said,

July 10, 2008 @ 19:42

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

Ted Nyberg said,

September 3, 2008 @ 01:56

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

Nate said,

September 15, 2008 @ 18:38

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?

Nate said,

September 15, 2008 @ 20:53

I meant HTTP Module, not Handler

Coskun SUNALI said,

September 16, 2008 @ 09:35

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

Ted Nyberg said,

September 16, 2008 @ 10:39

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!

Wayne said,

September 23, 2008 @ 02:37

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.

Farhan Gohar said,

October 22, 2008 @ 12:49

Great Work.

Thank you very much, it’s helped alot

Shaun Plumb said,

November 26, 2008 @ 20:24

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! ;-)

Asang Dani said,

December 10, 2008 @ 05:15

Why not use a Shared/static constructor?

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

or

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

Coskun SUNALI said,

December 10, 2008 @ 17:17

Hello Asang,

Have you tried that already?

Julian said,

March 27, 2009 @ 14:54

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

RSS feed for comments on this post · TrackBack URI

Leave a Comment