Thursday, March 31, 2011

UrlRewriter+HttpModule+Session problem

I need to write a custom "UrlRewriter" using a HttpModule, in the moment of "rewriting" I need access to the Session and has followed the advice from another SO thread:

http://stackoverflow.com/questions/276355/can-i-access-session-state-from-an-httpmodule

Everything works, except the RewritePath/Redirect part. I don't get any exceptions, but the browser takes forever to load. Is this really the best way to build a urlrewriter like this?

using System;
using System.Web;
using System.Web.SessionState;
using System.Diagnostics;

namespace MyCompany.Campaigns
{

    public class CampaignRewriteModule : IHttpModule
    {
        public void Init(HttpApplication application)
        {
            application.PostAcquireRequestState += new EventHandler(Application_PostAcquireRequestState);
            application.PostMapRequestHandler += new EventHandler(Application_PostMapRequestHandler);
        }

        void Application_PostMapRequestHandler(object source, EventArgs e)
        {
            HttpApplication app = (HttpApplication)source;

            if (app.Context.Handler is IReadOnlySessionState || app.Context.Handler is IRequiresSessionState)
            {
                return;
            }

            app.Context.Handler = new MyHttpHandler(app.Context.Handler);
        }

        void Application_PostAcquireRequestState(object source, EventArgs e)
        {
            HttpApplication app = (HttpApplication)source;

            MyHttpHandler resourceHttpHandler = HttpContext.Current.Handler as MyHttpHandler;

            if (resourceHttpHandler != null)
            {
                HttpContext.Current.Handler = resourceHttpHandler.OriginalHandler;
            }

            Debug.Assert(app.Session != null);


            string path = HttpUtils.Path();

            if (!CampaignCodeMethods.IsValidCampaignCode(path)) return;

            string domain = HttpUtils.Domain();

            CampaignCode code = CampaignManager.RegisterCode(path, domain.Equals(Config.Instance.Domain.ToLower()) ? null : domain);

            if (code != null)
            {
               //app.Context.RewritePath(code.CampaignCodePath.Path, false);
               app.Context.Response.Redirect(code.CampaignCodePath.Path, true);
            }


        }

        public void Dispose() { }

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

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

            public void ProcessRequest(HttpContext context)
            {
                throw new InvalidOperationException("MyHttpHandler cannot process requests.");
            }

            public bool IsReusable
            {
                get { return false; }
            }
        }

    }

}
From stackoverflow
  • I'm thinking the problem may be inside this block:

    if (code != null)
    {
        //app.Context.RewritePath(code.CampaignCodePath.Path, false);
        app.Context.Response.Redirect(code.CampaignCodePath.Path, true);
    }
    

    Try putting a breakpoint in the if statement and see if continually gets hit.

  • Is your URL rewriter handling requests that aren't for an actual page? If they are, then I don't think you can access Session... the last URL rewriter that I had written was there to handle 404 errors, and I remember digging around and finding (somewhere, can't remember where) that you don't get access to Session if that request is not for an actual .aspx page.

  • I think there should be a call to 'return' after you reset it to the original handler, else you will continually rewrite the path.

    Thinking about it, that's probably why the page is loading forever! :)

  • I wrote a simple URL rewriter module that did something similar. The url rewriting is done in BeginRequest by comparing the requested url to a list of known urls. If we find a mach we use HttpContext.RewritePath to change the requested url.

    This appears to work well with no serious side effects.

    I notice that you use Response.Redirect instead of Context.RewritePath. Using Redirect will cause the users browser to request a new page with the new url. Is this really what you want? The user will then see the new url in his browser. If this really is what you want you could use an alternative approach where you use a custom 404 page not found error handler to redirect the user to the appropriate page.

    If you set up IIS to redirect all 404 errors to a new page, say Custom404.aspx, that you have set up. In this page you can check the requested url to see if the url should be rewritten. If it should you can simply set the Response.Status to "301 Moved Permanently" and write a header with the name "Location" and the new url as the value. If the url should not be rewritten you can just output the standard 404 page not found error.

    This last approach works well, but as with your Response.Redirect approach the user will see the new url in his browser. Using Context.RewritePath allows you to serve a different page than the one requested.

  • I think I know what it is. Your module is executed on ALL requests and assigns a handler that throws an error unless there is a valid campaign code (where a rewrite/redirect occurs).

    But because this is not just for your "handler campaign code" url it is causing an error to be thrown, which is causing you to be redirected to your error page, which is being caught by the module, which is assigning the handler, which is throwing an error, which is redirecting... I think you get where I'm going ;)

    Otherwise I'd try a few things:

    • Setup Fiddler and check for an infinite redirect loop
    • Put a breakpoint on app.Context.Response.Redirect - make sure your not in an infinite loop
    • Put a breakpoint on MyHttpHandler.ProcessRequest - make sure it's not being called and the exception swallowed

0 comments:

Post a Comment