ASP.NET AJAX, Inheritance in Components, and Script Registration/Description
ASP.NET AJAX supports inheritance in components. While easy to setup, here's a little useful information regarding the script registration/description process for you to be aware of. To start, imagine this hierarchy of components:
Server
ScriptControl
BaseButtonControl
Button
Client
Sys.UI.Control
AjaxSamples.BaseButtonControl
AjaxSamples.Button
First, script registration is the process of selecting which JS files to download to the client in order for your components to work. Both the BaseButonControl and Button components need scripts downloaded to the client, unless it happens that the same script file is used for both. This means that both BaseButtonControl and Button server components would create their own instances of a ScriptReference object, which points to the scripts that it needs. If there are duplicates, no worries. Duplicate scripts get ignored.
Script Description, however, has to happen one per object. This means that the BaseButtonControl cannot create it's own ScriptDescriptor, and Button render its own component as well. There must be only one Descriptor object returned to the ScriptControl class. Here are some of the issues you may experience if you don't:
You may see something in the script like:
$create(AjaxSamples.Button, .... $get("buttonElement"));
$create(AjaxSamples.BaseButtonControl, ..... $get("buttonElement"));
An exception will be thrown that more than one control targets the same element, which is true, because two components reference "buttonElement" html element. In some instances, you may see something like:
$create(AjaxSamples.Button, .... $get("buttonElement"));
$create(AjaxSamples.Button, ..... $get("buttonElement"));
Which is a unique situation. Also, the create statement pushes up properties to the client using one of the $create parameters to do so. You may see something like the following as well.
$create(AjaxSamples.Button, { "text" : "click me", "tooltip" : "click me too" }, .. , $get("buttonElement"));
$create(AjaxSamples.BaseButtonControl, { "clientStateFieldID" : "buttonElement_Client" }, .. , $get("buttonElement"));
So why might all this happen? Let's first look at the right approach to setting up a descriptor. Each component should have one descriptor, and that one descriptor should contain the properties and client-side events from that object and all of its inherited members. This means that a descriptor has to be shared up the levels of hierarchy. That is the proper way to setup a descriptor, and it varies from the registration process.
The AJAX control toolkit actually handles this, using reflection to interpret the finalized type. This way, all of the logic concerning getting the correct descriptor is behind the scenes. I'm writing about this because I was trying to do something more complicated when it comes to registration, and as such I ran into an issue with the approach to describing the components. At first, I was creating a descriptor for the base class, then creating a descriptor for the derived class. This created two descriptors, one for the base class (which isn't supposed to be an instantiable component even on the client-side), and one for the derived.
So if you create a control hierarchy, you have to ensure that the Button component only creates one descriptor, and that it includes both Button and BaseButtonControl's information. There are a couple of ways to do this. Create a method in BaseButtonMethod that does:
protected ScriptControlDescriptor GetBaseDescriptor(string clientType, string clientID)
{
ScriptControlDescriptor desc = new ScriptControlDescriptor(clientType, clientID);
//Add properties/events to descriptor
.
.
return desc;
}
The Button class can call this method to get the descriptor with the base properties, but return the client-type's full name to append to it, as:
IEnumerable<ScriptDescriptor> IScriptControl.GetScriptDescriptors()
{
var desc = this.GetBaseDescriptor("AjaxSamples.Button", this.ClientID);
//Add properties/events to descriptor
.
.
return desc;
}
This way, only one descriptor reference is created, but contains both base/derived type references.