Click here to Skip to main content
Click here to Skip to main content

A Smart Form Control for ASP.NET URL Rewriting

, 20 Jul 2007
Rate this:
Please Sign up or sign in to vote.
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 <a href="%22http://msdn2.microsoft.com/en-us/library/ms972974.aspx%22">URL Rewriting in ASP.NET</a> 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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

David T Nelson
Web Developer
United States United States
I have been doing web development for about 11 years. I specialize in Dynamic HTML, C#, and SQL database design.

Comments and Discussions

 
GeneralMy vote of 1 PinmemberAmol_Joshi24-May-12 19:59 
GeneralAn easier option is here Pinmemberrobert_fairbrother27-Aug-07 6:45 
GeneralRe: An easier option is here PinmemberAmol_Joshi24-May-12 19:57 
GeneralWeb.Config PinmemberXela22023-Jul-07 15:41 
GeneralClasses derived from HtmlForm break the designer Pinmembernikkilong23-Jul-07 7:08 
GeneralRe: Classes derived from HtmlForm break the designer PinmemberRichard Deeming27-Jul-07 10:04 
GeneralActually the best workaround would be.... PinmemberGevorg23-Jul-07 5:05 
All you need to do is simply rewrite it back to what URL was on the page. Then Form tag would pick it up.
 
Example:

protected void Application_BeginRequest(Object sender, EventArgs e)
{
HttpContext ctx = HttpContext.Current;
ctx.Items["OriginalPath"] = ctx.Request.Path;
...blalblalbla
}
 

on the page
Page_OnInit(..)
{
HttpContext ctx = HttpContext.Current;
ctx.RewritePath((string)ctx.Items["OriginalPath"] );
....
}

 
Inheritance helps. All my pages derived from my StandardPage that does this code.

 

 
My site - Body Jewelry
Not my site - Piercing info

QuestionReverse engineering? PinmemberDario Solera21-Jul-07 1:42 
AnswerRe: Reverse engineering? PinmemberDavid T Nelson23-Jul-07 7:05 
AnswerRe: Reverse engineering? Pinmembernsimeonov24-Jul-07 1:26 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140721.1 | Last Updated 20 Jul 2007
Article Copyright 2007 by David T Nelson
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid