URL rewrites and the HtmlForm action attribute

by Mads Kristensen 22. September 2007 22:58

A known bug in ASP.NET 1.x and 2.0 is that the action attribute of a form doesn’t respect URL rewrites. Everyone that uses URL rewrites uses one of several mechanisms to take care of the action attribute bug.

Recently it has been quite popular to use control adapters and .browser files to change the rendering of a form and its attributes. I’ve never liked that approach because it treats the symptom instead of fixing the problem in a transparent and clean way.

<strongOpinion>
The use of control adapters and .browser files to alter the output for different clients is just as bad as having separate stylesheet files for IE and Firefox. Instead of writing cross-browser mark-up and CSS you treat the browser differences as an illness and try to treat it. The thing is that you don’t cure it by using control adapters; you just treat the symptom of not being able to (or don’t care to) write cross-browser code.

However, it still has its right when dealing with mobile devices, but not much longer. The browser in the iPhone or Windows Mobile is becoming so good that it soon doesn’t matter anymore.
</strongOpinion>

In the past I’ve used a custom overridden HtmlForm but when I had to do it recently on my job, I thought it was about time to make it better and cleaner. I very much like the use of a custom form control approach because it is very transparent in what it does and where it does it.

The code

#region Using

using System;
using System.ComponentModel;
using System.Web;
using System.Web.UI;
using System.Web.UI.HtmlControls;

#endregion

/// <summary>
/// A custom HtmlForm for use when a postback is made to a website
/// that has URL rewriting enabled.
/// </summary>
[DefaultProperty("Text")]
[ToolboxData("<{0}:RewriteForm runat=server></{0}:RewriteForm>")]
public class RewriteForm : HtmlForm
{
  /// <summary>
  /// Renders the attributes using the RewriteFormHtmlTextWriter.
  /// </summary>
  protected override void RenderAttributes(HtmlTextWriter writer)
  {
    RewriteFormHtmlTextWriter custom = new RewriteFormHtmlTextWriter(writer);
    base.RenderAttributes(custom);
  }

  /// <summary>
  /// Writes the HtmlForm's markup to support URL rewrites.
  /// </summary>
  private class RewriteFormHtmlTextWriter : HtmlTextWriter
  {
    /// <summary>
    /// Initializes a new instance of the <see cref="RewriteFormHtmlTextWriter"/> class.
    /// </summary>
    /// <param name="writer">The writer.</param>
    public RewriteFormHtmlTextWriter(HtmlTextWriter writer)
      : base(writer)
    {
      base.InnerWriter = writer.InnerWriter;
    }

    /// <summary>
    /// Writes all attributes the normal way, but writes the action attribute differently.
    /// </summary>
    /// <param name="name">The markup attribute to write to the output stream.</param>
    /// <param name="value">The value assigned to the attribute.</param>
    /// <param name="fEncode">true to encode the attribute and its assigned value; otherwise, false.</param>
    public override void WriteAttribute(string name, string value, bool fEncode)
    {
      if (name == "action")
      {
        HttpContext context = HttpContext.Current;

        if (context.Items["ActionAlreadyWritten"] == null)
        {
          value = context.Request.RawUrl;
          context.Items["ActionAlreadyWritten"] = true;
        }
      }

      base.WriteAttribute(name, value, fEncode);
    }
  }
}

Download

RewriteForm.zip (320 bytes)

* Only $4.95/month ASP.NET & Windows 2008 + IIS 7 Hosting! FREE SQL Included

Tags:

ASP.NET

Comments

9/23/2007 9:52:09 AM #

Paul Wilson

Just add an application PreRequestHandlerExecute event with the following line:
   HttpContext.Current.RewritePath(HttpContext.Current.Request.RawUrl);

Paul Wilson United States |

9/27/2007 2:39:14 PM #

huobazi

why not use a controladapter ??

huobazi People's Republic of China |

9/27/2007 2:42:38 PM #

huobazi

//------------------------------------------------------------------------------
// <copyright Author="Meibo Wu huobazi.AspxBoy.com">
//     Copyright (c) huobazi.AspxBoy.com  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Reflection;
using System.IO;

namespace AspxBoy.DotNetJobs.Adapters
{
    public class HtmlFormAdapter : System.Web.UI.Adapters.ControlAdapter
    {
  
        protected override void Render( System.Web.UI.HtmlTextWriter writer )
        {
            base.Render( new FormRewriteTextWriter( writer ) );
        }

    }

    internal class FormRewriteTextWriter : HtmlTextWriter
    {
        private static readonly string alreadyRewirteKey = "AspxBoy.DotNetJobsCn.FormActionAlreadyRewrote";
        public FormRewriteTextWriter( TextWriter writer )
            : base( writer )
        {
            if ( writer is HtmlTextWriter )
            {
                this.InnerWriter = ( writer as HtmlTextWriter ).InnerWriter;
            }
            else
            {
                this.InnerWriter = writer;
            }
        }

        public override void WriteAttribute( string name , string value , bool fEncode )
        {
            HttpContext context = HttpContext.Current;
            if ( name == "action" && context.Items[ alreadyRewirteKey ] == null )
            {
                value = context.Request.RawUrl;
                context.Items[ alreadyRewirteKey ] = true;
            }
            base.WriteAttribute( name , value , fEncode );
        }
    }

}

huobazi People's Republic of China |

9/27/2007 11:35:17 PM #

Mads Kristensen

Because control adapters suck. Didn't you read the post?

Mads Kristensen Denmark |

10/1/2007 8:25:55 PM #

Steve

I agree with you that using ControlAdapters for working around browser-specific bugs is a bad idea, but in this scenario you aren't getting around bugs in the client browser, you're using it to get around bugs in the server-side ASP.NET implementation (the rendering of the Form control).

Using control adapters for this has one big advantage over your custom control - you can drop the browser file and your adapter DLL into a current site, and have it work straight away without recompiling your whole solution.  In my opinion this approach is more "transparent" than creating a custom override as you are doing here (as new developers on the project don't need to remember to use your custom control for their forms instead of using what they are used to).

Steve United Kingdom |

10/3/2007 3:44:28 PM #

Jonas

I implemented your form and it is working with my normal url www.mydomain.dk/map.aspx (its a google maps page with ajax style postbacks), so I must have implemented it correct.
The problem is that my callbacks still return 'access denied' when i try to use the page with the rewritten url map.mydomain.dk.

I am using ISAPI/rewrite.

Any clue what im doing wrong?

Jonas Denmark |

10/24/2008 10:06:41 PM #

Leigh

Check this out: john-sheehan.com/.../

Leigh |

Comments are closed

About the slave

Mads Kristensen Mads Kristensen
Web developer at ZYB and founder of BlogEngine.NET. More...

LinkedIn ZYB Facebook Last.fm Twitter View Mads Kristensen's profile on Technorati

The Lounge

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2008