Introduction

In this tutorial we're going to examine the principals and construction of a basic ASP.NET 3.5 AJAX extender control. We'll discover how powerful extender controls can be for the reusability of code and behaviors. For more information on extenders refer to my books ASP.NET 3.5 AJAX Pocket Guide and ASP.NET AJAX Programming Tricks.

As we know, ASP.NET comes packed with a vast number of different controls, all having their own particular uses. However, it may become desirable to extend the behavior of these controls to better suit our needs. In most cases, the extension of these controls would involve building a new custom control, which inherits from the control we wish to extend and the new features coded. For example, consider a TextBox control, you may wish to limit the input of this control, change the background color when focus of the control has been changed, or display a watermark within the content of the control, etc. Building individual controls implementing each of the discussed behaviors, while definitely an option, is perhaps not the best option. What you’ll end up with is several different controls, all of which actually TextBox controls, however, each implementing a slightly different behavior, aside from being a tedious and repetitive task, it’s not the most efficient approach.

Welcome to concept of an extender control. Extender controls represent logical behaviors that can be attached to an existing control. Extender controls are server controls and can be attached to another control simply by identifying the client ID of the control we wish to extend.

Extender controls are standalone server controls, they implement from the Control class, have a Render() method and functions in a manner expected from any other server control. However, extenders offer the ability of injecting client-side code into the pages markup, hence granting the ability of extending the physical behavior of a server control. The controls an extender extends are not changed programmatically and are simply extended via client-side code, for example the addition of JavaScript event handlers to perform specific tasks based on a particular trigger. Extenders are specific to the ASP.NET AJAX Framework, this Framework simplifies the development of extender controls, as it offers a detailed client-side library we may make use of and also offers base classes we should inherit from when developing extender controls.

Extenders usually include a number of client-side objects defining the behavior of the target control. These objects are then written to the output stream by registering themselves with the ScriptManager control included within the page. A ScriptManager control must be present when making use of the ASP.NET AJAX base extender class.

The Base Extender Class

All extender controls should inherit from either the base abstract class ExtenderControl or optionally inherit from Control and implement the IExtenderControl interface. The ExtenderControl base abstract class does just this, that is, it inherits from Control and implements the IExtenderControl interface. This interface is defined as shown in the code listing below.

public interface IExtenderControl
{
    // Methods
    IEnumerable<ScriptDescriptor> GetScriptDescriptors(Control targetControl);
    IEnumerable<ScriptReference> GetScriptReferences();
}

These methods are implemented within the ExtenderControl base class and simply invoke a new abstract implementation that must be implemented by all extender controls. These methods are discussed in the table below.

Method Description
GetScriptDescriptors This method is invoked by the ScriptManager when processing extender controls and should return a collection of ScriptDescriptor objects.
GetScriptReferences This method is invoked by the ScriptManager when processing extender controls and should return a collection of ScriptReference objects.

First and foremost, what is a script descriptor object? Excellent question! A ScriptDescriptor object represents the client-side object description, that is, ScriptDescriptor objects contain information about instances of client components. This information includes the client type to create, the properties to assign, and the events to add handlers for when the client-side object instance is created. For example, consider the properties set declaratively when an extender is defined on a page, generally speaking, these properties should be passed to the client-side behavior. A ScriptDescriptor can be used to define these properties and values that should be passed to the client-side behavior object when created.

We should already be familiar with the ScriptReference object, however, for completeness, a ScriptReference object represents a script file, either embedded into an assembly or the URL. A collection of ScriptReference objects is returned to the ScriptManager, hence rendering the script include block for each script reference.

You should note that the ScriptDescriptor object is a base abstract class. That is, instances of this object are not created directly and properties assigned values, but rather instances of the class ScriptBehaviorDescriptor should be created and populated with data.

Extender Descriptors

We know that the GetScriptDescriptors() method should be implemented within our extender controls and we also know that we must return a collection of ScriptBehaviorDescriptor objects representing information used to create an instance of the client-side object. We can define the following information:

  • Component properties
  • Element properties
  • Events
  • Properties

Component Properties

The AddComponentProperty() method of the ScriptDescriptor can be used to register component properties. Component properties represent the ID of AJAX components. An AJAX component inherits from the Sys.UI.Component class and usually represents non-visual objects, such as a timer. The AddComponentProperty() method has the following prototype:

public void AddComponentProperty(string name, 
    string componentID)

The first parameter, name, represents the client-side property that will represent the ID of the component and the second parameter, componentID, should be the actual component ID.

descriptor.AddComponentProperty("timer", "componentID");

Element Properties

The AddElementProperty() method can be used to define element properties. An element property is a property that represents a DOM element. When the client-side behavior object is created, the DOM element will be retrieved using the $get shortcut and the indicated property will represent that DOM element. The AddElementProperty() method has the following prototype:

public void AddElementProperty(string name, 
    string elementID)

The first parameter, name, represents the client-side property that will represent the DOM element and the second parameter, elementID, is the ID of the DOM element to be retrieved.

descriptor.AddElementProperty("textBox", "domID");

Events

The AddEvent() method can be used to bind an event handler method to a specified event. The event is a component event, not a DOM element event such as onclick. For example, a timer control could have a tick event, which is raised when the specified interval elapses. The AddEvent() method has the following prototype:

public void AddEvent(string name, 
    string handler)

The first parameter, name, is the name of the event and the second parameter, handler, is the function handler to be bound to the event.

descriptor.AddEvent("trick", "handerMethod");

Properties

The AddProperty() method can be used to assign string values to standard properties. For example, perhaps the client-side object has a property named cssClass, the CSS class specified by the user when the extender was declaratively defined in markup could be passed as a descriptor property and can thus assign the client property cssClass the value of the class specified declaratively in markup. The AddProperty() method has the following prototype:

public void AddProperty(string name, 
    object value)

The first parameter, name, represents the property name and the second, parameter, value, is of type object and represents the value of the property.

descriptor.AddProperty("cssClass", this.CssClass);

Finally, the ScriptDescriptor doesn’t create objects or properties, that is, if a property is added to a descriptor, the specified client-side property, along with its set and get accessor methods must be part of the client-side object.

The ScriptBehaviorDescriptor

The ScriptBehaviorDescriptor object has the following constructor prototype:

public ScriptBehaviorDescriptor(
    string type,
    string elementID)

The table below examines each parameter.

Parameter Description
type A string representation of the type of the client behavior to describe. That is, the client-side object name to instantiate.
elementID The DOM ID of the target control to extend.

The Focus Extender

We’ll end our brief discussion with a sample extender. The extender we’ll build herein is a focus extender and can extend multiple different types of controls, including TextBox and Button controls.

From Visual Studio create an ASP.NET AJAX Server Control Extender. Select File, New Project, the New Project dialog box will appear. Select your desired language, the Web item beneath this language, and choose the template ASP.NET AJAX Server Control Extender.

Functionality Requirements

What is it that our focus extender should do? Examine the following checklist, as the following features are what we desire within our focus extender.

  • Multi-control capable
  • Focus and Blur styles defined via CSS classes

Server-side Object

As we know, extenders are simply server-side controls, which make use of script descriptors and script references to extend the behavior of the target DOM. The ScriptManager control must be present on the page, otherwise an exception will be thrown when the extender control is parsed and rendered by the ASP.NET Framework.

Our focus extender control will inherit from the ExtenderControl base class, as shown below.

[TargetControlType(typeof(Control))]
public class FocusExtender : ExtenderControl
{

First, notice the metadata attribute defined for the focus extender class. The TargetControlType attribute defines the controls which can be extended by our extender. In the above case, we indicate that the Control type may be extended by our extender, in other words, any control inheriting from this object may be extended by our extender control.

We must also define two server-side properties, which can be defined by implementers of the extender control. These properties are used to specify the CSS classes to be used under normal circumstances and when the target control receives focus. Notice that in the code below, our CSS class names are retrieved from view state; if the view state object doesn’t exist we’ll utilize default class names.

public string OnFocusCssClass
{
    get
    {
        String s = (String)ViewState["OnFocusCssClass"];
        return ((s == null) ? "AptOnFocus" : s);
    }

    set
    {
        ViewState["OnFocusCssClass"] = value;
    }
}

public string DefaultCssClass
{
    get
    {
        String s = (String)ViewState["DefaultCssClass"];
        return ((s == null) ? "AptFocusDefault" : s);
    }

    set
    {
        ViewState["DefaultCssClass"] = value;
    }
}

As we’ve indicated default CSS classes to be used if no others have been specified by the implementer of our extender we should embed the CSS style sheets into our control assembly and register them with the page. To do this, we can override the PreRender event, hence enabling us to add a link tag within the pages header pointing to the path of our CSS style sheet, as shown in the listing below.

protected override void OnPreRender(EventArgs e)
{
    // Register CSS styles
    HtmlLink cssLink = new HtmlLink();
    cssLink.Href = this.Page.ClientScript.GetWebResourceUrl(this.GetType(), "Chapter7.FocusStyles.css");
    cssLink.Attributes.Add("type", "text/css");
    cssLink.Attributes.Add("rel", "stylesheet");

    this.Page.Header.Controls.Add(cssLink);

    base.OnPreRender(e);
}

Note the use of the GetWebResourceUrl() method. This method is used to construct the WebResource.axd URL, as the CSS file is embedded within our controls assembly.

Below you will find the implementation of the two abstract methods discussed above, that is, GetScriptDescriptors() and GetScriptReferences().

protected override IEnumerable<ScriptReference>
        GetScriptReferences()
{
    ScriptReference sRef = new ScriptReference();
    sRef.Name = "Chapter7.FocusBehavior.js";
    sRef.Assembly = this.GetType().Assembly.FullName;

    yield return sRef;
}

protected override IEnumerable<ScriptDescriptor>
        GetScriptDescriptors(Control targetControl)
{
    ScriptBehaviorDescriptor desc = new ScriptBehaviorDescriptor("Chapter7.FocusBehavior", targetControl.ClientID);

    // Add Properties
    desc.AddProperty("onFocusCssClass", OnFocusCssClass);
    desc.AddProperty("defaultCssClass", DefaultCssClass);

    yield return desc;
}

Note the two properties added to the descriptor object. When initialized, an instance of our client-side class will be created with these two corresponding properties matching the values of the OnFocusCssClass and DefaultCssClass server-side properties respectively.

Also note that the scripts are embedded within the assembly of the extender, as indicated by specifying both the Name and Assembly properties of the ScriptReference object.

Client-side Object

The WebResource attribute identifying our JavaScript file as an embedded resource attribute should be defined within our assembly’s AsssemblyInfo.cs file, as shown below.

[assembly: WebResource("Chapter7.FocusBehavior.js", "text/javascript")]

We can now begin defining our client script, which will control the behavior of the target control. First and foremost, we must register our client-side namespace, followed by the default constructor for our new behavior object, as shown in the code below.

Type.registerNamespace('Chapter7');

Chapter7.FocusBehavior = function(element)
{
    // Initialize the base class we’re inheriting from
    Chapter7.FocusBehavior.initializeBase(this, [element]);
    
    this._onFocusCssClass = null;
    this._defaultCssClass = null;
    this._focusHandler = null;
    this._blurHandler = null;
}

Notice the declaration and the four different private properties, the first two being assigned values via the $create method. That is, an instance of the class is first created and the set accessor method for these two properties will be invoked, passing the value specified in the script descriptor. The next two properties are used as delegates of the onFocus and onBlur events. The value of these two delegates are assigned in the initialize() method.

What comes next is the prototype definition of our class, as shown in the code listing below.

Chapter7.FocusBehavior.prototype =
{
    initialize : function()
    {
        Chapter7.FocusBehavior.callBaseMethod(this, 
            'initialize');
        
        var e = this.get_element();
        
        this._focusHandler = Function.createDelegate(
            this, this._onFocus);
        this._blurHandler = Function.createDelegate(
            this, this._onBlur);
        
        $addHandler(e, 'focus', this._focusHandler);
        $addHandler(e, 'blur', this._blurHandler);
    },

What I want you to notice in the above listing is the delegates created for the focus and blur event handler methods. Notice that after the creation of these delegates, we pass them as the event handler methods for the two events. The question is, why use delegates? Why not just assign a function directly as a handler of the two events? The “this” pointer is context related, meaning that if a method is registered as an event handler for a control in the typical manner the result of the “this” pointer will be that of the control whose event is being handled, not that of an instance of our behavior class. Making use of the createDelegate() method sets the context in which the method should be invoked, hence the “this” pointer points to the indicated instance. Thus, we can access private members of our behavior class and invoke methods of our behavior class from within the event handler methods.

In a nutshell, the initialize() method of our extenders client behavior class simply invokes the base initialize() method, that is, the method implemented in the Sys.UI.Behavior class, and registers event handlers for the previously discussed events.

The remaining portion of the client-side behavior control is quite simple and consists of the event handler methods discussed above, as well as the set and get accessor methods of our descriptor properties.

dispose : function()
{
    var e = this.get_element();

    // Detach events
    if (this._focusHandler)
    {
        $removeHandler(e, 'focus', 
            this._focusHandler);
        this._focusHandler = null;
    }
    if (this._blurHandler)
    {
        $removeHandler(e, 'blur', this._blurHandler);
        this._blurHandler = null;
    }
    Chapter7.FocusBehavior.callBaseMethod(this, 'dispose');
},

_onFocus : function(evt)
{
    var e = this.get_element();
    if (e != null)
    {
        e.className = this._onFocusCssClass;
    }    
},

_onBlur : function(evt)
{
    var e = this.get_element();
    if (e != null)
    {
        e.className = this._defaultCssClass;
    }
},

get_defaultCssClass : function()
{
    return this._defaultCssClass;
},

set_defaultCssClass : function(value)
{
    this._defaultCssClass = value;
    this.raisePropertyChanged('defaultCssClass');
},

get_onFocusCssClass : function()
{
    return this._onFocusCssClass;
},

set_onFocusCssClass : function(value)
{
    this._onFocusCssClass = value;
    this.raisePropertyChanged('onFocusCssClass');
}

Last, but hardly least, we should register the above object as a class with the AJAX Framework and specify the base class we wish to inherit from, as shown below.

Chapter7.FocusBehavior.registerClass('Chapter7.FocusBehavior', Sys.UI.Behavior);

The CSS

Just as with the JavaScript file containing the client-side implementation of our extender, the file containing our CSS class should be embedded within our extenders assembly. We should begin by creating a new style sheet file within Visual Studio (or any text editor for that matter), giving it the name we desire and setting it as an embedded resource within our assembly. We should then include the following WebResource attribute within our AssemblyInfo.cs file, as shown below.

[assembly: WebResource("Chapter7.FocusStyles.css", "text/css")]

The above will ensure that the CSS file is available via the WebResource.axd URL and can hence be included within the pages markup as a link tag within the header.

The content of our default CSS file is as follows.

.AptFocusDefault {
    background-color:White;
}
.AptOnFocus {
    background-color:Green;
}

Using the Extender

First and foremost, ensure that the compiled assembly of the extender control is added as a reference to your web site. Right click the root of your web site project from Solution Explorer and select Add Reference, browse to the assembly file and click OK when done. Secondly, ensure that you register the control on the page, as shown below.

<%@ Register Assembly="Chapter7.1" Namespace="Chapter7" TagPrefix="MI" %>

Using the new extender involves adding the extender control to the control collection of the object which the control should be a child. In most scenarios this would be the page. It can be added declaratively within a pages markup or programmatically. Consider the following listing.

<asp:ScriptManager ID="ScriptManager1" runat="server" />
Username: <asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
Password: <asp:TextBox ID="TextBox2" runat="server"></asp:TextBox>

Extending each TextBox is as easy as declaratively adding the extender control for each control to be extended, as shown in the markup below. Notice that the TargetControlID property must be set to the control’s ID that should be extended.

<apt:FocusExtender ID="focusExtender1"
                   runat="server" 
                   TargetControlID="TextBox1"/>
<apt:FocusExtender ID="focusExtender2" 
                   runat="server"
                   TargetControlID="TextBox2"/>

As desired, when focus has been set to one of the extended controls, the desired CSS class will be assigned to that control, hence modifying the background color of the textbox, indicating that the textbox has been given focus, as shown in the screenshot below.

Figure7.1

Conclusion

Extender controls are a great way to reuse the code that you've already built. I highly recommend that you make use of them whenever possible. For more information refer to my books ASP.NET 3.5 AJAX Pocket Guide and ASP.NET AJAX Programming Tricks.

Download all source files