views:

3125

answers:

12

OK, this might sound a bit confusing and complicated, so bear with me.

We've written a framework that allows us to define friendly URLs. If you surf to any arbitrary URL, IIS tries to display a 404 error (or, in some cases, 403;14 or 405). However, IIS is set up so that anything directed to those specific errors is sent to an .aspx file. This allows us to implement an HttpHandler to handle the request and do stuff, which involves finding the an associated template and then executing whatever's associated with it.

Now, this all works in IIS 5 and 6 and, to an extent, on IIS7 - but for one catch, which happens when you post a form.

See, when you post a form to a non-existent URL, IIS says "ah, but that url doesn't exist" and throws a 405 "method not allowed" error. Since we're telling IIS to redirect those errors to our .aspx page and therefore handling it with our HttpHandler, this normally isn't a problem. But as of IIS7, all POST information has gone missing after being redirected to the 405. And so you can no longer do the most trivial of things involving forms.

To solve this we've tried using a HttpModule, which preserves POST data but appears to not have an initialized Session at the right time (when it's needed). We also tried using a HttpModule for all requests, not just the missing requests that hit 404/403;14/405, but that means stuff like images, css, js etc are being handled by .NET code, which is terribly inefficient.

Which brings me to the actual question: has anyone ever encountered this, and does anyone have any advice or know what to do to get things working again? So far someone has suggested using Microsoft's own URL Rewriting module. Would this help solve our problem?

Thanks.

A: 

Just a guess: the handler specified in IIS7's %windir%\system32\inetsrv\config\applicationhost.config which is handling your request is not allowing the POST verb to get through at all, and it is evaluating that rule before determining whether the URL doesn't exist.

jlew
The HttpHandler is set to verb="*" (in web.config, not applicationHost.config). I don't think that should cause any problems, should it?
Rahul
+2  A: 

Since IIS7 uses .net from the top down there would not be any performance overhead of using an HttpModule, In fact there are several Managed HttpModules that are always used on every request. When the BeginRequest event is fired, the SessionStateModule may not have been added to the Modules collection, so if you try to handle the request during this event no session state info will be available. Setting the HttpContext.Handler property will initialize the session state if the requested handler needs it, so you can just set the handler to your fancy 404 page that implements IRequiresSessionState. The code below should do the trick, though you may need to write a different implementation for the IsMissing() method:

using System.Web;
using System.Web.UI;

class Smart404Module : IHttpModule
{
    public void Dispose() {}

    public void Init(HttpApplication context)
    {
        context.BeginRequest += new System.EventHandler(DoMapping);
    }

    void DoMapping(object sender, System.EventArgs e)
    {
        HttpApplication app = (HttpApplication)sender;

        if (IsMissing(app.Context))
            app.Context.Handler = PageParser.GetCompiledPageInstance(
                "~/404.aspx", app.Request.MapPath("~/404.aspx"), app.Context);
    }

    bool IsMissing(HttpContext context)
    {
        string path = context.Request.MapPath(context.Request.Url.AbsolutePath);

        if (System.IO.File.Exists(path) || (System.IO.Directory.Exists(path)
            && System.IO.File.Exists(System.IO.Path.Combine(path, "default.aspx"))))
            return true;
        return false;
    }
}

Edit: I added an implementation of IsMissing()

Note: On IIS7, The session state module does not run globally by default. There are two options: Enable the session state module for all requests (see my comment above regarding running managed modules for all request types), or you could use reflection to access internal members inside System.Web.dll.

Stefan Rusek
Thanks for the comment! Once I get a chance to try it out, I'll let you know if it worked.
Rahul
Have you tried it yet?
Stefan Rusek
Nope, sorry, I'm in the middle of a deadline :( But I'll try to get around to it in the next few weeks.
Rahul
Stefan, see Martin's answer below
Rahul
My solution does not use the default 404 provider. You specifically said you were using your own 404 page that needed Session state. In which case you would have needed to make your handler implements IRequiresSessionState.
Stefan Rusek
Martin is a colleague of mine so he's working with the same problem, but thanks, we'll take another look.
Rahul
A: 

Thanks for the help Stefan, however, the problem with your proposed solution is that a session state is not always present. This is because the original 404 handler does not implement IRequiresSessionState.

We know of a trick to swap to a dummy IRequiresSessionState-handler in between the PostAuthorizeRequest and PostRequestHandlerExecute phase. However, the problem with it is that it does not work on IIS7:

using System;
using System.IO;
using System.Web;
using System.Web.SessionState;
using System.Configuration;

namespace Something
{  
  public class HttpModule : IHttpModule
  {
    public void Dispose()
    { 
    }

    public void Init(HttpApplication application)
    {
      application.PostAuthorizeRequest += new EventHandler(application_PostAuthorizeRequest);
      application.PostRequestHandlerExecute += new EventHandler(application_PostRequestHandlerExecute);
    }

    void application_PostAuthorizeRequest(object sender, EventArgs e)
    {
      HttpApplication app = (HttpApplication)sender;
      if (!(app.Context.Handler is IReadOnlySessionState || app.Context.Handler is IRequiresSessionState))
      {
        app.Context.Handler = new ForceSessionHttpHandler(app.Context.Handler);
      }
    }

    void application_PostRequestHandlerExecute(object sender, EventArgs e)
    {
      HttpApplication application = (HttpApplication)sender;
      HttpContext httpContext = application.Context;
      ForceSessionHttpHandler handler = httpContext.Handler as ForceSessionHttpHandler;

      if (handler != null)
      {
        string executionFile = httpContext.Request.PhysicalPath;
        if (File.Exists(executionFile))
        {
          new System.Web.DefaultHttpHandler().BeginProcessRequest(httpContext, null, null);
        }
        else
        {
          httpContext.Handler = handler.OriginalHandler;
          Something.DrawPage(httpContext);
        }
      }
    }

    public class ForceSessionHttpHandler : IHttpHandler, IRequiresSessionState
    {
      internal readonly IHttpHandler OriginalHandler;

      public ForceSessionHttpHandler(IHttpHandler originalHandler)
      {
        OriginalHandler = originalHandler;
      }

      public void ProcessRequest(HttpContext context)
      {
        throw new InvalidOperationException("ForceSessionHttpHandler will not process requests.");
      }

      public bool IsReusable
      {
        get { return false; }
      }
    }
  }
}
Martin Kool
It is true that the default 404 handler does not implement IRequiresSessionState, but Rahul specifically mentioned that he wanted a custom 404 handler that uses session state, in which case he would need to make the handler implement IRequriesSessionState anyway.
Stefan Rusek
A: 

This question raises an interesting point for me. What are you doing with the posted data if it is not being specifically handled by a page?

I really hope that your aren't just logging all the posted data somewhere for later use - that is one potentially huge attack vector for a malicious user. The very least they can do is fire off thousands of file uploads to your site and clog up the storage medium. That's not even considering sql injection attacks and the like.

Of course, I'm sure that as responsible developers you've already thought of this stuff, but sometimes things fall through the gaps, you know?

ZombieSheep
No, this is for a web development framework we've created internally. When a developer uses the framework, regular posts should continue to work. On IIS7 (Vista), posted data doesn't arrive where it should, hence the problem.
Rahul
A: 

Hi Stefan,

I think there is a misunderstanding.

Rahul is a collegue of mine, and the issue we're facing is that we indeed want to have a 404 mapping an IRequiresSessionState handler, but it's not working in IIS7 even though it works in IIS5 and 6.

When I use your example httpmodule and ours in a IIS5 or 6 environment, it works like a charm. Hook it up on Vista and your session object is null. Even when swapping to an IRequiresSessionState handler in between.

If you have any insights on this please let us know. It's been bugging Rahul and me for quite a while now. Thanks!

Martin Kool
Adding a comment to my response above.
Stefan Rusek
A: 

Yes, I would definitely recommend URL rewriting (using Microsoft's IIS7 one or one of the many alternatives). This is specifically designed for providing friendly URLs, whereas error documents are a last-ditch backstop for failures, which tends to munge the incoming data so it may not be what you expect.

bobince
+2  A: 

Microsoft released a hotfix for this :

http://support.microsoft.com/default.aspx/kb/956578

çağdaş
It looks like this works! I wonder what Microsoft had to fix to get it up and running ;) Thanks!
Rahul
Apparently this download is no longer available for 32-bit processors, only for 64-bit. Weird. Anyone seen the download elsewhere?
Rahul
A: 

I am suffering with the same issue. ASP.NET App II7 with the 404 redirect trick to create my own Custom handler.

All form post data is lost. The hotfix mentioned is for Vista. I am using Windows Server 2008 Standard.

I do intend to go down the custom HttpHandler route, but time doesn't allow right now.

Does anyone know of a hotfix for this OS instead?

Many thanks

Andy
A: 

I am just a beginner and trying to practice some of my classic asp codes. I am having the same problem as Rahul. I am getting 404.0 not found eroor while using the form and post method. However get and querystring works just fine. I am using vista preimum IIS 7. Why post method is not working with IIS 7? Any simple solution for this such as install something or turn on some control or authentication etc? It ought to be problem somewhere in the set up and installation. If any of you have any simple solution or knew what is causing this problem please help.

Thanks Ama

Did you try the hotfix mentioned in the accepted answer of this question?
çağdaş
+2  A: 

The problem in IIS 7 of post variables not being passed through to custom error handlers is fixed in service pack 2 for Vista. Haven't tried it on Windows Server but I'm sure it will be fixed there too.

bartonlee
A: 

We ran into this exact issue. The hotfix does indeed fix the issue. I can confirm that IIS 7.5 does NOT have this issue.

Rocky Madden
A: 

I have a very similar problem...

Have recently migrated a number of websites from an ageing Windows 2003 server running IIS6 to a new Windows 2008 server running IIS7.

The sites all use a CMS written in classic ASP which requires that the 404 error is modified to load a default.asp file which looks at the URL and loads content from a database. This is just to keep all the URLs nice and SEO friendly.

The back office of the CMS works in a similar way (except the 404 error is dealt with by admin/default.asp).

For some reason though, the login page (which obviously involves a form posting to the custom 404 error) does not carry over the POST information.

OK - here's the weird bit. It works fine in Google Chrome, but not in IE7/8 or Firefox.

Obviously this is right royal pain in the harris.

Any ideas why post data is carried through the custom 404 handler for one browser but not any others?

I've got as far as I can and am currently trawling the web for information about this but can't seem to find anything...

edp