Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A Smart Form Control for ASP.NET URL Rewriting

0.00/5 (No votes)
20 Jul 2007 1  
A smart form control that fixes many of the problems that are caused by extensionless URL rewriting in ASP.NET.

Introduction

One of the websites I work on uses URL rewriting, i.e., when you type in an address www.somesite.com/Products/WidgetA/, you are really browsing to an ASP.NET page somewhere else in the website, say: www.somesite.com/ProductDisplay.aspx?page=Products/WidgetA/.

For a full explanation of URL rewriting along with a very usable rewriting engine, go to 15seconds.com and read Rewrite.NET -- A URL Rewriting Engine for .NET, by Robert Chartier.

Background

One problem with this, and believe me, there are many problems with URL rewriting, is that it breaks the form tag. The action of the form tag will be the real address of the .aspx page and not the nice friendly address you wanted. To workaround this problem, MSDN suggests that you create an "Action-less form", meaning, you create a custom form tag that simply doesn't write the action attribute on the form tag.

namespace ActionlessForm {
  public class Form : System.Web.UI.HtmlControls.HtmlForm
  {
     protected override void RenderAttributes(HtmlTextWriter writer)
     {
        writer.WriteAttribute("name", this.Name);
        base.Attributes.Remove("name");

        writer.WriteAttribute("method", this.Method);
        base.Attributes.Remove("method");

        this.Attributes.Render(writer);

        base.Attributes.Remove("action");

        if (base.ID != null)
           writer.WriteAttribute("id", base.ClientID);
     }
  }
}
//From URL Rewriting in ASP.NET by Scott Mitchell

The big problem with the above code is that it completely breaks the form tag for any server control that hooks into the postback cycle, validators for instance. If you look at the source code for the RenderAttributes method the HtmlForm class in the .NET Framework using the excellent Lutz Reflector, you will see that there is a whole lot going on in the .NET Framework's HtmlForm class that Scott's version does not duplicate. Besides that, what if the HtmlForm tag was changed in a future version of the .NET Framework?

If we examine exactly how the action attribute gets written, we can see that because the GetActionAttribute is private, there is no way to directly change the action attribute value before it gets to the HtmlTextWriter.

Also, some of the logic in the RenderAttributes method simply can't be reproduced using a derived class because many of the necessary methods and attributes are private.

writer.WriteAttribute("action", this.GetActionAttribute(), true);

So, we need to take a different approach to take control over if / how the Form tag action attribute gets written.

Using the code

My approach was to intercept the attribute before it was written to the HtmlTextWriter. To do this, I created a derived class called SelectiveHtmlTextWriter and overrode the WriteAttribute method:

public class SelectiveHtmlTextWriter : HtmlTextWriter
{
    ...
    
    public override void WriteAttribute(string name, string value, bool fEncode) 
    { 
        base.WriteAttribute(name, "/newpagename/goes/here/", fEncode);
    }
}


public class SmartForm : HtmlForm
{
    ...

    protected override void RenderAttributes(HtmlTextWriter writer)
    {
        SelectiveHtmlTextWriter customWriter = new SelectiveHtmlTextWriter(writer);
        base.RenderAttributes(customWriter);
    }
}

A new SelectiveHtmlTextWriter object is created and passed into the HtmlForm class RenderAttribute method in place of the normal HtmlTextWriter.

This works because of polymorphism. All the HtmlForm class cares about is that it gets a reference to an HtmlTextWriter. However, when it calls RenderAttribute, my override gets called instead.

This works great, but I have never been fond of hard-coded values, so in order to make this more extensible, I added an event to the new SelectiveHtmlTextWriter called WritingAttribute which the SmartForm class subscribes to. This allows any attribute, specifically action, to be modified before its value is written. In this case, I am substituting the Request.RawUrl value which holds the actual URL that the request came from.

public class SmartForm : HtmlForm
{
    public SmartForm()
        : base()
    {
    }

    protected override void RenderAttributes(HtmlTextWriter writer)
    {
        SelectiveHtmlTextWriter customWriter = new SelectiveHtmlTextWriter(writer);
        customWriter.WritingAttribute += 
          new SubstituteValueEventHandler(customWriter_WritingAttribute);
        base.RenderAttributes(customWriter);
    }

    void customWriter_WritingAttribute(object sender, SubstituteValueEventArgs e)
    {
        if (e.Name == "action")
        {
            e.NewValue = Context.Request.RawUrl;
        }
    }
}

In conclusion, if I had a choice, I would not use URL rewriting at all. I would find some other way to make the client happy. But, the SmartForm that we outlined here will ease the pain quite a bit. One last caveat, using the SmartForm breaks the Visual Studio designer. I have been unsuccessful at creating a custom designer for this. If anybody comes up with a designer for this, email me and I'll add it to the article with the appropriate credits.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here