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.
Just want to say thanks for this post, it took me a while to find it but after 2 days of trying to solve this problem, I’m glad I did, worked a treat.
Hi Chris,
Thank you for the feedback that it still helps someone out there. Glad to hear that.
Nice hack.
‘AppInitialize’ doesn’t seems to work in ASP.NET MVC, You have to make a separate file with a static class and call it from Global.asax Application_Start.
I have to thank you for this post. You solved my problem after 6 months
Thanks Man. I read your post immediately after I decompiled MVC source code. BTW, have you heard back from Microsoft about why they put that check (IsPrecompiledApp) there?
@Saeid, never heard back of anything from MS, unfortunately.
Like others have said — it worked like a charm for me.
On .Net 4.6.2 there is a RegisterVirtualPathProviderInternal which is static. That allowed me to reduce your code to:
public static void RegisterProvider()
{
// we create a new instance of our own VirtualPathProvider.
var providerInstance = new EmbeddedViewPathProvider();
typeof(HostingEnvironment).GetMethod(“RegisterVirtualPathProviderInternal”,
BindingFlags.Static | BindingFlags.InvokeMethod | BindingFlags.NonPublic)
.Invoke(null, new object[] {providerInstance});
}
and take one fewer dependency on an implementation detail.
@JohnMelville glad it worked for you.