Troubleshooting custom VirtualPathProvider - asp.net

I have just created a customized VirtualPathProvider for my ASP.NET web application. It basically maps all virtual files in "~/Storage" and subdirectories to a directory other than the solution's directory.
Code essentials
private bool IsPathVirtual(string virtualPath)
{
String checkPath = VirtualPathUtility.ToAppRelative(virtualPath);
return checkPath.StartsWith(VirtualRootPath, StringComparison.InvariantCultureIgnoreCase);
}
public override bool DirectoryExists(string virtualDir)
{
return IsPathVirtual(virtualDir) ? ((FileSystemVirtualDirectory)GetDirectory(virtualDir)).Exists() : Previous.DirectoryExists(virtualDir);
}
public override bool FileExists(string virtualPath)
{
return IsPathVirtual(virtualPath) ? ((FileSystemVirtualFile)GetFile(virtualPath)).Exists() : Previous.FileExists(virtualPath);
}
In my case VirtualRootPath = "~/Storage", but that it configurable.
The problem
In IIS Express, when I debug via Visual Studio, the two public methods, required to resolve a virtual path, are not always called.
Calling http://localhost:7749/Storage triggers breakpoints on both methods. A 404 error is returned and desired. This is a correct behaviour to me
Calling http://localhost:7749/Storage/ExistingFile.txt doesn't trigger debug, and a different 404 error is returned. This is not correct
The difference between the two 404 errors is that when I call for the directory, it's ASP.NET responding (Server error in application '/') but when I call for the file inside that directory, it's IIS 8.0 responding (HTTP Error 404.0 - Not Found).
The question
Why, even if I correctly registered the VirtualPathProvider in my HostingEnvironment, doesn't IIS 8.0 let ASP.NET pipeline handle the HTTP request so it could be resolved correctly?
The workaround
After reading VirtualPathProvider doesn't (quite) work in production on IIS 7.5 I realized it could be a Web.config problem. Judging from the other question, it looks like that IIS handles certain file extensions independently, no matter if ASP.NET maps those to a virtual resource, a controller, or anything else. So, since I was trying to read an XML file (and perhaps not a JPEG), IIS didn't bother ASP.NET.
The workaround has been putting this line in Web.config's system.webServer.handlers section:
<add name="AspNetStaticFileHandler-XML" path="*.xml" verb="*" type="System.Web.StaticFileHandler" />
But this workaround works only for XML files. How to make a permanent fix that works for all files under the storage directory?

You can specify the storage directory as part of the 'path' field in the config, as follows:
<add name="AspNetStaticFileHandler-Storage" path="Storage/*" verb="*" type="System.Web.StaticFileHandler" />

Related

How to check whether Application_BeginRequest is called for static resources like images on real IIS webserver?

We have an ASP.NET 4.0 website, and we use the Application_BeginRequest event in Global.asax to do some smart redirects. When debugging the solution under the local ASP.NET Development Server provided by Visual Studio (no IIS), Application_BeginRequest is called for both apsx pages and the static resources like css files, jpg/gif images, etc our pages contain.
That's a known issue, but what about the real IIS hosting of our hosting provider (Windows 2008/IIS 7.0)? How can we check whether this happens for the static resources? And how to prohibit this?
All requests will flow through Application_BeginRequest unless you tell the webserver to behave differently by setting runAllManagedModulesForAllRequests to false
<system.webServer>
<modules runAllManagedModulesForAllRequests="false" />
</system.webServer>
If you don't have access to web.config then you can set up a quick test : publish two distincts images : redirect.jpg and noredirect.jpg and set a redirection in Application_BeginRequest and see if it occurs or not
var url = ((System.Web.HttpApplication)sender).Request.Url;
if (url.EndsWith("noredirect.jpg"))
{
Response.Redirect(url.replace("noredirect.jpg","redirect.jpg"));
}
Then try to access "noredirect.jpg", if "redirect.jpg" shows instead then the redirect is in action ( = default setting)
You can try;
if (Request.Path.ToLowerInvariant().IndexOf(".aspx") > -1)
{
// static files
}

Handling Expires Headers in ASP.Net MVC

I need advice or suggestions on how to add Expires Headers to my CSS, Image and JavaScript files in ASP.Net MVC.
A key issue is that the software is not in a single location. It is distributed to clients who handle the hosting so I would rather have a solution that doesn't require manual configration in IIS unless it's unavoidable!
I googled around and the majority of answers seem to be focused on content that is returned via a controller. Can't do that for JavaScript files though..
Which IIS Version are you using?
If by 'manual IIS configuration' you mean having to open the IIS manager console, IIS 7.5 (and I think 7 as well) allows you to add expires headers to static content using only the web.config:
<system.webServer>
<staticContent>
<clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="00:30:00" />
</staticContent>
</system.webServer>
You can do something like this by writing a custom handler for your javascript files. In your Web.Config file of your MVC project look for the httpHandlers section. Add something like the following line:
<add verb="GET" path="/YourScriptsFolder/*.js" type="Your.Project.Namespace.And.Custom.Handler, Your.Assembly.Name" validate="false" />
This will force all requests for js files in that folder through your custom handler which will look something like this:
class CustomHandler : IHttpHandler
{
#region IHttpHandler Members
public bool IsReusable
{
get { return true; }
}
public void ProcessRequest(HttpContext context)
{
// Set any headers you like here.
context.Response.Expires = 0;
context.Response.Cache.SetExpires(DateTime.Parse("3:00:00PM"));
context.Response.CacheControl="no-cache";
// Determine the script file being requested.
string path = context.Request.ServerVariables["PATH_INFO"];
// Prevent the user from requesting other types of files through this handler.
if(System.Text.RegularExpressions.RegEx.Match(path, #"/YourScriptsFolder/[^/\\\.]*\.js"))
context.Response.Write(System.IO.File.ReadAllText(path));
}
#endregion
}
I haven't tested this code so you might run into some issues but this is the basic idea. There are a plethora of examples on ASP.Net custom handlers throughout the web. Here's a good example:
http://www.developer.com/net/asp/article.php/3565541/Use-Custom-HTTP-Handlers-in-Your-ASPNET-Applications.htm
Another, less complicated option is to add a randomized or versioned query string to the end of the file path.
<link rel="stylesheet" type="text/css" href="somecssfile.css?version=1.0.0.3" />
When you change the version, the browser will get a new copy of the file.

Neither HttpHandler nor HttpApplication is getting called for /

I have an IHttpHandler registered like this:
<httpHandlers>
<add verb="*" path="*" type="MindTouch.Dream.Http.HttpHandler, mindtouch.core"/>
</httpHandlers>
Which catches /foo, /foo/bar, etc. just fine, but on / the Visual Studio built-in server does not hit hit either the HttpApplication or my handler.
That's the way to do it. Your web server/site will have a setting which specifies the default document to serve for a directory. If not present or not set, the web server will attempt to serve either the directory listing which should be turned off for security, a security error if the listing is not available, or nothing.
So in your case prior to the default document existing, "/" was not actually making an application request.
I fixed it and I think I recall this being an ancient ASP.NET issue:
I created a file called Default.htm, which ASP.NET will try to resolve the / path to and since there is now a real path to resolve to, the HttpApplication gets called, incidentally with a path of /default.htm.
Is there a less hacky solution to this? Gladly would accept a different answer than my own :)

Custom VirtualPathProvider not being used in IIS6

I added the following lines to Application_Start method in global.asax:
var provider = new TestVirtualPathProvider();
HostingEnvironment.RegisterVirtualPathProvider(provider);
Yet the 'TestVirtualPathProvider' is never used when deploying this application in IIS6 (it does in the ASP.NET Development Server).
Edit: the default path provider has always done its job correctly and served (non-embedded) views correctly. The problem is simply that I want to use my own path provider to provide embedded views. So, initially, I already had the following wildcard mapping configured:
Any possible reasons why this does not work in IIS6?
Are there any other factors (handlers for example) wich might influence the used VirtualPathProvider?
UPDATE: the fact that you want to handle extension-less URL's is an important point that's not mentioned in the question. Please see this page for help in setting up MVC with IIS 6: http://haacked.com/archive/2008/11/26/asp.net-mvc-on-iis-6-walkthrough.aspx. This should cover your scenario as well.
Most likely the same issue that I answered in this thread: http://forums.asp.net/t/995633.aspx
Basically, add this in your web.config:
<httpHandlers>
<add path="*" verb="GET,HEAD,POST" type="System.Web.StaticFileHandler" validate="true" />
</httpHandlers>
That other thread has some details that explain why this is necessary.
For the combination Custom VPP + IIS6 + Precompiled site, we need to add the VPP from AppInitailize();
public static class AppStart
{
public static void AppInitialize()
{
// code to be executed automatically by the framework
}
}
See also:
http://sunali.com/2008/01/09/virtualpathprovider-in-precompiled-web-sites/
I believe that you need to use an ISAPI filter in IIS6 to intercept URLs without extensions. Problem is that ISAPI will need to be done in c/c++.
IIS6 is configured to allow only certain extensions to be processed by the ASP.net pipeline.
To findout how you can redirct requests check out the post by DocV.

Why my HttpHandler is ignored?

In an ASP.NET application, I need to do some changes on every CSS file sent.
So I created an HttpHandler (inside the app itself), added:
<add verb="*" path="*.css" type="MyWebsite.CssTestHandler,MyWebsite"/>
to Web.config in system.web/httpHandlers and modified the handler like this:
public void ProcessRequest(HttpContext context)
{
context.Response.Clear();
context.Response.Write("Hello World");
context.Response.End();
}
But CSS files are still just like they were before, so the handler is just ignored.
What I'm doing wrong?
You need to setup a wildcard map in IIS, see the following link:
http://www.microsoft.com/technet/prodtechnol/WindowsServer2003/Library/IIS/5c5ae5e0-f4f9-44b0-a743-f4c3a5ff68ec.mspx?mfr=true
This will cause the request for the CSS file to be served by ASP.NET rather than just IIS.
If the application serves very high traffic, consider setting this mapping for .css files only, or even better change the CSS data in the page rather than changing the file.
Check this page for instructions on all 3 cases of IIS version (6, 7 Classic pipeline and 7 Integrated pipeline):
http://learn.iis.net/page.aspx/508/wildcard-script-mapping-and-iis-7-integrated-pipeline/
According to it, in case of Integrated pipeline, you need to add the following config parameter:
runAllManagedModulesForAllRequests="True"
The App ignores your CSS files because IIS ignores CSS files.
It's not mapped to an executable in IIS.
alt text http://www.fastpics.net/sharepics/imih41904722.jpg
Try adding the .css extension and map it to the .NET dll.

Resources