This article applies to ASP.NET 2.0.
This article originated from a customer question on the ASP.NET site. What they are trying to achieve is running multiple sites under a single actual ASP.NET application. This can be useful to avoid the overhead of having a different appdomain per site. So the general idea is to have a single application, and to use sub-directories to represent the site. Let's call them 'pseudo-sites' as they are really just directories from the point of view of ASP.NET.
For example, the app could have this structure:
MyApp PseudoSites Site1 page.aspx uc.ascx Site2 Site2's files...
Such pseudo-sites will necessarily have a number of limitations: e.g. they won't be able to each have their own bin, App_Code, and other top level directories, since these can only exist at the top level of a real ASP.NET application. In spite of these limitations, the structure can be useful for apps that don't needs to have those directories.
The main issue that this article deals with is the fact that path resolution will by default not work correctly when using such a structure. e.g. suppose /MyApp/PseudoSites/Site1/page.aspx has:
Recall that '~' means "the root of the app". Clearly "~/uc.ascx" means to refer to uc.ascx in the same pseudo-site as page.aspx. But ASP.NET will not see it that way, as the real root of the app is just "/MyApp". Instead, this will resolve to "/MyApp/uc.ascx", which is not where the file is.
One obvious solution is to use relative paths instead of app relative paths. e.g. here you could write src="uc.ascx" mce_src="uc.ascx" and it would work fine. This is a fine thing to do in some cases, but in many other cases, you are much better off using app relative paths, as you are then free to move files around without having to worry about the relative locations always staying the same.
So the question is: how can we make app relative paths (as well as absolute path, e.g. "/Site1/page.aspx") work correctly in the pseudo-site environment?
ASP.NET 2.0 introduces the ability to hook deep into the way it deals with files via something called a VirtualPathProvider. Implementing a full VirtualPathProvider is somewhat involved, and is usually done to serve files out of an alternate store, like a database. Doing this is beyond the scope of this article (though I'd like to write more about it if there is interest!), and we will look at only one VirtualPathProvider method: CombineVirtualPaths. This method is called whenever the parser needs to resolve paths, which is exactly what we need to solve our problem!
The code below shows a sample implementation of CombineVirtualPaths. You will need to adapt it to your situation but it demonstrates the principle. To try this code, simply put it somewhere in the App_Code directory (of your real app, not pseudo app!).
Note: AppInitialize is a special method that gets called automatically at startup when it is found somewhere is App_Code. You could alternatively register the VirtualPathProvider from global.asax (in Application_OnStart) or an HttpModule.
That's basically it! With this code, the situation described above will be able to run, since your code is driving the path resolution. Basically, you get to give whatever meaning you want to '~'.
A couple more notes about this:
As some of you found, if your site if precompiled, your VPP is not used. I wish we had made this scenario work, but I guess it fell through due to scheduling. We basically ended up artificially disabling the scenario because we didn't have time to test it properly. Someone posted a workaround using private reflection. It is definitely a hack (and may break in later versions, though that's not likely), and I can't guarantee that it works well in all scenario, but if it can unblock you, give it a try.