I had an email exchange with a couple of folks about fixing the form's action attribute as rendered by asp.net that gets in the way of URL rewriting, by rendering out an action attribute pointing to the rewritten URL rather than the prettier original URL as shown on the browser's address bar when the page is initially browsed. I've brought this up for fixing in the past. I know, it is unfortunate that it is still not fixed. I am going to try again, and maybe it'll happen in the next SP... fingers crossed.
In the mean time I thought I'd post my solution here as well. I know various techniques have been blogged about before. I have seen various approaches that I don't quite like personally. For instance I've seen solutions based on control adapters and derived HtmlTextWriter implementations (these suffer from perf overhead of using control adapters, or from having to inspect every attribute rendered out). I have also seen some solutions involving response filtering, and that gets pretty expensive as well.
The solution I use on my own site is a derived form control that renders out a bit of script to essentially rewrite the form's action attribute back to the original URL on the client. Here is my implementation, which also adds support for partial rendering scenarios enabled through the use of ScriptManager, if you're using ASP.NET Ajax:
public class Form : HtmlForm {
private const string FormActionScript =
@"document.getElementById('{0}').action = window.location.href;
";
private const string AjaxFormActionScript =
@"Sys.WebForms.PageRequestManager.getInstance().add_endRequest(function() {{
document.getElementById('{0}').action = window.location.href;
}});
";
protected override void OnPreRender(EventArgs e) {
base.OnPreRender(e);
StringBuilder sb = new StringBuilder();
sb.AppendFormat(FormActionScript, ClientID);
ScriptManager scriptManager = ScriptManager.GetCurrent(Page);
if ((scriptManager != null) && scriptManager.SupportsPartialRendering) {
sb.AppendFormat(AjaxFormActionScript, ClientID);
}
Page.ClientScript.RegisterStartupScript(typeof(Form), String.Empty,
sb.ToString(),
/* addScriptTags */ true);
}
}
The issue generally cited with this approach is that every page has to be touched, which is pretty painful, and every new page has to use a non-standard <form> tag. Additionally, I would have to implement a custom designer to associate with my derived form control to allow natural editing its contents on the design surface. However, there is a simple solution to both of these issues: I can use the tag mapping feature in ASP.NET to map all occurrences of the HtmlForm control with my derived Form control. Thereafter, as the parser encounters any existing <form> tag, it automatically uses my derived type instead without requiring the developer to make the change explicitly. This makes the approach less intrusive, and quite a bit more natural. All code that accessed the control still thinks its a Form tag.
Specifically, I just need to add this entry in web.config:
<system.web>
<pages>
<tagMapping>
<add tagType="System.Web.UI.HtmlControls.HtmlForm"
mappedTagType="Site.Form"/>
</tagMapping>
</pages>
The tag mapping functionality is a mechanism to instruct the parser to subtitute a different derived type implementation whenever it encounters the type being mapped. It was originally invented as part of the WebPart framework so as to allow the ASP.NET WebPartManager to be mapped to the derived Sharepoint WebPartManager implementation without having to change individual pages. However, it is quite powerful and generally applicable.
Tag mapping is one of the esoteric features that comes in handy every once in a while. :-)
Posted on Saturday, 12/8/2007 @ 6:59 PM
| #
ASP.NET