Development With A Dot

Blog on development in general, and specifically on .NET

Sponsors

News

My Friends

My Links

Permanent Posts

Portuguese Communities

ASP.NET Web Forms Extensibility: Control Adapters

All ASP.NET controls from version 2.0 can be associated with a control adapter. A control adapter is a class that inherits from ControlAdapter and it has the chance to interact with the control(s) it is targeting so as to change some of its properties or alter its output. I talked about control adapters before and they really a cool feature.

The ControlAdapter class exposes virtual methods for some well known lifecycle events, OnInit, OnLoad, OnPreRender and OnUnload that closely match their Control counterparts, but are fired before them. Because the control adapter has a reference to its target Control, it can cast it to its concrete class and do something with it before its lifecycle events are actually fired. The adapter is also notified before the control is rendered (BeginRender), after their children are renderes (RenderChildren) and after itself is rendered (Render): this way the adapter can modify the control’s output.

Control adapters may be specified for any class inheriting from Control, including abstract classes, web server controls and even pages. You can, for example, specify a control adapter for the WebControl and UserControl classes, but, curiously, not for Control itself. When specifying a control adapter for a page, it must inherit from PageAdapter instead of ControlAdapter. The adapter for a control, if specified, can be found on the protected Adapter property, and for a page, on the PageAdapter property.

The first use of control adapters that came to my attention was for changing the output of standard ASP.NET web controls so that they were more based on CSS and less on HTML tables: it was the CSS Friendly Control Adapters project, now available at http://code.google.com/p/aspnetcontroladapters/. They are interesting because you specify them in one location and they apply anywhere a control of the target type is created. Mind you, it applies to controls declared on markup as well as controls created by code with the new operator.

So, how do you use control adapters? The most usual way is through a browser definition file. In it, you specify a set of control adapters and their target controls, for a given browser. This browser definition file is a XML file with extension .Browser, and can either be global (%WINDIR%\Microsoft.NET\Framework64\vXXXX\Config\Browsers) or local to the web application, in which case, it must be placed inside the App_Browsers folder at the root of the web site. It looks like this:

   1: <browsers>
   2:     <browser refID="Default">
   3:         <controlAdapters>
   4:             <adapter controlType="System.Web.UI.WebControls.TextBox" adapterType="MyNamespace.TextBoxAdapter, MyAssembly" />
   5:         </controlAdapters>
   6:     </browser>
   7: </browsers>

A browser definition file targets a specific browser, so you can have different definitions for Chrome, IE, Firefox, Opera, as well as for specific version of each of those (like IE8, Firefox3). Alternatively, if you set the target to Default, it will apply to all. The reason to pick a specific browser and version might be, for example, in order to circumvent some limitation present in that specific version, so that on markup you don’t need to be concerned with that.

Another option is through the the current Browser object of the request:

   1: this.Context.Request.Browser.Adapters.Add(typeof(TextBox).FullName, typeof(TextBoxAdapter).FullName);

This must go very early on the page lifecycle, for example, on the OnPreInit event, or even on Application_Start. You have to specify the full class name for both the target control and the adapter. Of course, you have to do this for every request, because it won’t be persisted.

As an example, you may know that the classic TextBox control renders an HTML input tag if its TextMode is set to SingleLine and a textarea if set to MultiLine. Because the textarea has no notion of maximum length, unlike the input, something must be done in order to enforce this. Here’s a simple suggestion:

   1: public class TextBoxControlAdapter : ControlAdapter
   2: {
   3:     protected TextBox Target
   4:     {
   5:         get
   6:         {
   7:             return (this.Control as TextBox);
   8:         }
   9:     }
  10:  
  11:     protected override void OnLoad(EventArgs e)
  12:     {
  13:         if ((this.Target.MaxLength > 0) && (this.Target.TextMode == TextBoxMode.MultiLine))
  14:         {
  15:             if (this.Target.Page.ClientScript.IsClientScriptBlockRegistered("TextBox_KeyUp") == false)
  16:             {
  17:                 if (this.Target.Page.ClientScript.IsClientScriptBlockRegistered(this.Target.Page.GetType(), "TextBox_KeyUp") == false)
  18:                 {
  19:                     String script = String.Concat("function TextBox_KeyUp(sender) { if (sender.value.length > sender.getAttribute('MaxLength')) { sender.value = sender.value.substr(0, sender.getAttribute('MaxLength')); } }\n");
  20:  
  21:                     this.Target.Page.ClientScript.RegisterClientScriptBlock(this.Target.Page.GetType(), "TextBox_KeyUp", script, true);
  22:                 }
  23:  
  24:                 this.Target.Attributes["onkeyup"] = "TextBox_KeyUp(this)";this.Target.Attributes["MaxLength"] = this.Target.MaxLength.ToString();
  25:             }
  26:         }
  27:  
  28:         base.OnLoad(e);
  29:     }
  30: }

What it does is, for every TextBox control, if it is set for multi line and has a defined maximum length, it injects some JavaScript that will filter out any content that exceeds this maximum length. This will occur for any TextBox that you may have on your site, or any class that inherits from it. You can use any of the previous options to register this adapter.

Stay tuned for more ASP.NET Web Forms extensibility tips! Winking smile

Comments

RichardD said:

Your script won't work if you have multiple multi-line TextBox controls on the same page with different maximum lengths; they'll all use the maximum length of the first control to be loaded.

# September 14, 2012 1:48 PM

Ricardo Peres said:

RichardD:

Yes, thanks, that was a quick example, and I fixed it.

# September 15, 2012 4:45 AM

Boynton said:

Good post but I was wondering if you could write a litte more on this subject?

I'd be very grateful if you could elaborate a little bit more. Appreciate it!

# September 17, 2012 3:58 AM

Domlia said:

This is quite nice point of view to Forms extensibility. Thanks.

# September 28, 2012 6:44 PM

Bob said:

yet another example as to why Microsoft is going down the toilet and LAMP is booming.  Let me get this straight, a developer has to dump thousands of hours into learning how to code in asp or vb or vb.net so that asp/vb can then output css / html for you? wow. why not learn html, css and js straight up? A true web developer does not developer in Microsoft technologies. Devs develope in Microsoft technologies because they have to or its mandated from their current working environment.

# October 4, 2012 3:01 PM