Friday, July 10, 2009

URL Rewriting and ASP.NET Login Controls

So, this is the initial post to the Blog I set up almost a year ago (How's that for procrastinating). I figure rather than a bunch of boring, all about me, blah blah blah, I would write about an issue I ran into today, that might be of use to someone else. So here goes...

I've recently been working on a .NET WinForms site that employs quite a bit of URL Rewriting. I'm using UrlRewriter.NET to handle the rewriting. I ran up against the issue, mentioned elsewhere, with URL rewriting and the ASP.NET Login Controls. Specifically, left in their default state, both the Login control and the LoginStatus control will redirect to the rewritten URL (the one I don't want my users to see) rather than my pretty original URL.

The solution I came up with to fix the issue across all instances of the Login controls in the site, which I hadn't run across on the 'tubes, was to create a simple custom control, employ Request.RawUrl to get the original URL, and use tag mapping to replace the problematic controls.

In order to implement this little fix I needed to add the following two classes to my App_Code directory.

New Login Control

public class UrlRewriteLogin : Login
{
   public override string DestinationPageUrl
   {
       get
       {
           if (String.IsNullOrEmpty(base.DestinationPageUrl))
               return this.Page.Request.RawUrl;

           return base.DestinationPageUrl;
       }
       set
       {
           base.DestinationPageUrl = value;
       }
   }
}

The mods to the Login control are fairly straightforward. Using .NET Reflector I can see that the issues arise in the Login Control's private GetRedirectUrl method. When DestinationPageUrl is empty, the method returns Page.Request.Path which shows the underlying URL. If we return the Request.RawUrl from an overridden DestinationPageUrl when the base DestinationPageUrl is blank, we never hit the problematic code, get the desired behavior, and still allow for a custom DestinationPageUrl

New LoginStatus Control

public class UrlRewiteLoginStatus : LoginStatus
{
   public override LogoutAction LogoutAction
   {
       get
       {
           if (base.LogoutAction == LogoutAction.Refresh)
               return LogoutAction.Redirect;

           return base.LogoutAction;
       }
       set
       {
           base.LogoutAction = value;
       }
   }

   public override string LogoutPageUrl
   {
       get
       {
           if (base.LogoutAction == LogoutAction.Refresh)
               return this.Page.Request.RawUrl;

           return base.LogoutPageUrl;
       }
       set
       {
           base.LogoutPageUrl = value;
       }
   }
}

These mods are a bit more tricky. Again using .NET Reflector I see that the issues arise in the LoginStatus Control's private LogoutClicked method, but only when LogoutAction is Refresh (the default). The problem is the same as above, if the LogoutAction is Refresh, the control uses Page.Request.Path. My solution here was twofold: 1.) Never return a LogoutAction of Refresh, and 2.) When base control's LogoutAction is Refresh, return the Request.RawUrl from LogoutPageUrl. This skips the problematic code, but still allows for normal LogoutPageUrl behavior.

Note:The override of LogoutAction might be a bit confusing to a developer unaware of the hack, as even if you manually set LogoutAction to Refresh, it never returns Refresh from the getter.

Then, all I needed to do is add the following tagMappings to our web.config. Now, every instance of <asp:Login> and <asp:LoginStatus /> are the new custom controls. w00t!

<pages>
  <tagMapping>
     <add tagType="System.Web.UI.WebControls.Login"
         mappedTagType="UrlRewriteLogin"/>
     <add tagType="System.Web.UI.WebControls.LoginStatus"
         mappedTagType="UrlRewiteLoginStatus"/>
  </tagMapping>
</pages>

Hope this is useful for someone else.