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.
| Print article | This entry was posted by Coskun SUNALI on 9 Jan 2008 - Wed at 15:25, and is filed under General. Follow any responses to this post through RSS 2.0. You can leave a response or trackback from your own site. |
about 2 years ago
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
about 2 years ago
Thank you! It really helped alot.
about 2 years ago
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.
about 2 years ago
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
about 2 years ago
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.
about 2 years ago
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
about 2 years ago
Good workaround, but run this workaround on shared hosting (where I don’t have granted with ReflectionPermission) :( …
about 2 years ago
I am getting an error myOwn could not be found. Can you please help.
about 2 years ago
Did you ever hear back from Microsoft regarding their reasoning?
about 2 years ago
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
about 2 years ago
Answered you question at:
http://beta.stackoverflow.com/questions/12397/net-virtualpathproviders-and-pre-compilation#15451
about 2 years ago
It sure would be interesting to hear what Scott (or another ASP.NET team member) would have to say about this… ;)
about 1 year ago
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?
about 1 year ago
I meant HTTP Module, not Handler
about 1 year ago
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
about 1 year ago
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!
about 1 year ago
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.
about 1 year ago
Great Work.
Thank you very much, it’s helped alot
about 1 year ago
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! ;-)
about 1 year ago
Why not use a Shared/static constructor?
Shared Sub New()
HostingEnvironment.RegisterVirtualPathProvider(New MyPathProvider())
End Sub
or
public static MyPathProvider( )
{
HostingEnvironment.RegisterVirtualPathProvider(New MyPathProvider())
}
about 1 year ago
Hello Asang,
Have you tried that already?
about 1 year ago
Genius, saved me many hours of frustration. Thanks for publishhing this.
about 1 year ago
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?
about 1 year ago
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 :)
about 12 months ago
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.
about 8 months ago
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.
about 7 months ago
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.
about 5 months ago
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?
about 5 months ago
Thank you for your post it really helped. Did anyone at MS every come back to you with reasons for the decision?
about 5 months ago
@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.
about 4 months ago
Hats off Man, worked like a charm…