Click here to Skip to main content
15,880,543 members
Articles / Web Development / ASP.NET
Article

Programmatically Setting Control Adapters for URL Rewriting and AJAX

Rate me:
Please Sign up or sign in to vote.
4.23/5 (7 votes)
25 Jun 2008CPOL3 min read 66.5K   196   22   23
A guide to injecting control adapters at runtime

Introduction

Anyone who has spent time developing URL rewriters will know that these do not always play nicely with AJAX components. This is because the HtmlForm element written out by .NET uses the actual URL for post-backs, and not the page's virtual URL that you are trying to preserve. This article discusses how this can be resolved cleanly, and also shows how control adapters may be set programmatically using Reflection. This is useful for creating plug & play components such as URL rewriters in order to minimise the amount of configuration required.

Background

In order to preserve the rewritten URL on post-back (and fix AJAX components), the HtmlForm's action attribute needs to be changed to the virtual URL.

Without this fix, you may experience the following error when using Asp.Net Ajax. This happens when your virtual URL is in a different directory than the real path.

Sys.WebForms.PageRequestManagerServerErrorException:
   An Unknown error occurred while processing the request on the server.

The standard approach might be to extend the HtmlForm and override its Render method so that it alters the action attribute on render. Whereas this would work, it is a poor solution. It requires that all ASPX pages are edited in order to apply the fix. It also requires prior knowledge of the issue when other developers start creating pages, and it makes the URL rewriter component less pluggable.

A better solution would be to use a control adapter. This can be added via configuration settings in order to change the rendered HTML of all HtmlForm elements within an application. This would then not only apply the fix to all existing pages, but also to any future pages.

For anyone new to control adapters, these are discreet classes extended from the ControlAdapter class. These can be mapped to specific .NET controls via config settings located in the App_Browsers special directory. The adapters are invoked by the framework at runtime, and can alter the rendered HTML of any .NET control. A common use is to make the standard .NET controls more CSS friendly.

OK, so sounds like a good solution? Not yet! Imagine we are developing a component to be used by a third party that requires a control adapter. After they have plugged in our component, how do we ensure they add the control adapter to the solution? The standard approach is to provide heaps of documentation, and if it breaks... well... it's user error.

A smarter solution is to set the control adapter programmatically from within our component. In this way, the component is self-contained. It will be attached, when required, automatically, wherever and whenever our component is used. The bad news is, this ability isn't supported in the current framework. The good news is, it can be done, and here's how.

Solution

Firstly, we need a control adapter to correct the action attribute of the HtmlForm element. Here's one I made earlier.

C#
public class HtmlFormAdapter : ControlAdapter
{
  protected override void Render(HtmlTextWriter writer)
  {
    base.Render(new HtmlFormWriter(writer));
  }
  private class HtmlFormWriter : HtmlTextWriter
  {
    public HtmlFormWriter(HtmlTextWriter writer)
      : base(writer)
    {
      this.InnerWriter = writer.InnerWriter;
    }
    public HtmlFormWriter(TextWriter writer)
      : base(writer)
    {
      this.InnerWriter = writer;
    }
    public override void WriteAttribute(string key, string value, bool fEncode)
    {
      if (string.Compare(key, "action")==0)
      {
        value = HttpContext.Current.Request.RawUrl;
      }
      base.WriteAttribute(key, value, fEncode);
    }
  }
}

The next step is to set the control adapter programmatically. In order to do this, we use Reflection to access the hidden private _adapter property of the HtmlForm control and set this to an instance of our adapter. In this example, we assume we are running from within a HttpModule that is doing the URL rewriting, and hook the PreRequestHandlerExecute event. We can then hook the page's PreRender event in order to grab the HtmlForm control and attach the adapter.

C#
private void context_PreRequestHandlerExecute(object sender, EventArgs e)
{
 HttpApplication application = (HttpApplication)sender;
 HttpContext context = application.Context;
 if (context.Handler is Page)
 {
    Page page = (Page)context.Handler;
    page.PreRender += new EventHandler(RegisterControlAdapters);
 }
}

private void RegisterControlAdapters(object sender, EventArgs e)
{
 Page page = (Page)sender;
 page.PreRender -= new EventHandler(RegisterControlAdapters);
 if (page.Form != null)
 {
    // attach new instance of control adapter
    FieldInfo adapterFieldInfo = page.Form.GetType().GetField("_adapter",
                                 BindingFlags.NonPublic | BindingFlags.Instance);
    if (adapterFieldInfo != null)
    {
        HtmlFormAdapter adapter = new HtmlFormAdapter();
        FieldInfo controlFieldInfo = adapter.GetType().GetField("_control",
                                     BindingFlags.NonPublic | BindingFlags.Instance);
        if (controlFieldInfo != null)
        {
             controlFieldInfo.SetValue(adapter, page.Form);
             adapterFieldInfo.SetValue(page.Form, adapter);
        }
    }
 }
}

Summary

This is a pretty specific example; however, the point is that using this technique greatly aids the creation of smart components that require minimal configuration. It also provides a greater degree of power to component developers, who are then able to hook into the actual rendering of all the controls in an application from a single line in the web.config.

XML
<add name="RewriteModule" type="CodeKing.Web.UrlRewriter, CodeKing.Web"/>

This leads to more pluggable components and fewer problems when it comes to development cycles or swapping out of components.

Further Reading

History

  • 19th June 2008: First published
  • 23rd June 2008: Further Reading section updated
  • 24th June 2008: Updated demo to use HtmlTextWriter

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
United Kingdom United Kingdom
Mike Carlisle - Technical Architect with over 20 years experience in a wide range of technologies.

@TheCodeKing

Comments and Discussions

 
GeneralMy vote of 5 Pin
Tech Code Freak23-Feb-12 1:47
Tech Code Freak23-Feb-12 1:47 
QuestionNot working in firefox? Pin
Member 15187121-Aug-08 17:42
Member 15187121-Aug-08 17:42 
AnswerRe: Not working in firefox? Pin
TheCodeKing21-Aug-08 21:00
TheCodeKing21-Aug-08 21:00 
GeneralRe: Not working in firefox? Pin
Member 15187125-Aug-08 14:17
Member 15187125-Aug-08 14:17 
GeneralRe: Not working in firefox? Pin
TheCodeKing25-Aug-08 22:18
TheCodeKing25-Aug-08 22:18 
The sample code attached works in both IE and Firefox. Can you be more specific about which ajax library you are using and which code you are having problems with?


GeneralRe: Not working in firefox? Pin
Member 15187126-Aug-08 19:09
Member 15187126-Aug-08 19:09 
GeneralRe: Not working in firefox? Pin
TheCodeKing26-Aug-08 21:11
TheCodeKing26-Aug-08 21:11 
GeneralRe: Not working in firefox? Pin
Member 15187127-Aug-08 2:28
Member 15187127-Aug-08 2:28 
GeneralRe: Not working in firefox? Pin
TheCodeKing27-Aug-08 3:08
TheCodeKing27-Aug-08 3:08 
GeneralRe: Not working in firefox? Pin
Member 15187128-Aug-08 3:54
Member 15187128-Aug-08 3:54 
GeneralA better implementation of the control adapter Pin
Artiom Chilaru23-Jun-08 9:24
Artiom Chilaru23-Jun-08 9:24 
AnswerRe: A better implementation of the control adapter Pin
TheCodeKing23-Jun-08 9:38
TheCodeKing23-Jun-08 9:38 
GeneralRe: A better implementation of the control adapter Pin
Artiom Chilaru23-Jun-08 9:41
Artiom Chilaru23-Jun-08 9:41 
GeneralRe: A better implementation of the control adapter Pin
TheCodeKing23-Jun-08 9:55
TheCodeKing23-Jun-08 9:55 
GeneralRe: A better implementation of the control adapter Pin
Artiom Chilaru23-Jun-08 9:58
Artiom Chilaru23-Jun-08 9:58 
GeneralRe: A better implementation of the control adapter Pin
TheCodeKing23-Jun-08 10:34
TheCodeKing23-Jun-08 10:34 
GeneralRe: A better implementation of the control adapter Pin
Artiom Chilaru23-Jun-08 11:12
Artiom Chilaru23-Jun-08 11:12 
GeneralRe: A better implementation of the control adapter Pin
TheCodeKing23-Jun-08 10:59
TheCodeKing23-Jun-08 10:59 
GeneralRe: A better implementation of the control adapter Pin
Artiom Chilaru23-Jun-08 11:13
Artiom Chilaru23-Jun-08 11:13 
GeneralI like it Pin
Mike Ellison20-Jun-08 2:37
Mike Ellison20-Jun-08 2:37 
GeneralRe: I like it Pin
Rafael Nicoletti20-Jun-08 7:20
Rafael Nicoletti20-Jun-08 7:20 
GeneralRe: I like it Pin
TheCodeKing20-Jun-08 7:49
TheCodeKing20-Jun-08 7:49 
GeneralRe: I like it Pin
Mike Ellison20-Jun-08 7:59
Mike Ellison20-Jun-08 7:59 

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

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