ASP.NET: Create AJAX Server Controls using the ScriptControl base class

May 15, 2008  • ASP.NET

I’ve been doing a lot with ASP.NET AJAX for almost 2 years now, including my Web.Maps.VE product. So, I’ve decided to spread some of my knowledge in this area by writing up some articles on things that I’ve learned and figured out.

As a note, since .NET 3.5 is the “latest and greatest” version of the framework out at the time of this writing, this article targets .NET 3.5. This article also assumes you have a basic understanding of ASP.NET AJAX and JavaScript.

What is ScriptControl?

First, what exactly is ScriptControl? Well, the base class I’m referring to is the System.Web.UI.ScriptControl base class located within the System.Web.Extensions.dll assembly.

The ScriptControl is basically the first building block you want to use to start building out a WebControl (server control) that will have some kind of rich AJAX functionality in the browser. You may have heard of ASP.NET AJAX Control Extenders, especially if you’re familiar with the AJAX Control Toolkit, so why would we use ScriptControl instead of creating an ExtenderControl in order to give a control some rich AJAX functionality within the browser? Well, you want to create an ExtenderControl to extend or enhance an existing control with some rich AJAX functionality, and you want to inherit from the ScriptControl class when you are creating a full control that will have some rich AJAX functionality.

According to MSDN the ScriptControl class is:

ScriptControl inherits from the WebControl class and implements the IScriptControl interface. The WebControl class is a base class for ASP.NET Web server controls. The ScriptControl is an abstract class, which you cannot instantiate directly. Instead, you derive a class based on this abstract class.

The ScriptControl base class tests the page for a ScriptManager control during the PreRender stage of the derived control. The ScriptControl base class also makes sure that the derived control calls methods of the ScriptManager control to register script during the Render event. This includes registering ScriptDescriptor objects for the ScriptControl when the Render method is called. The Render method makes sure that ScriptDescriptor objects are not rendered unless the ScriptControl itself is rendered. This enables a ScriptControl to work inside a closed WebPart control.

You can add as many descriptors and types as necessary to any class that you derive from ScriptControl.

Basics of Creating Server Controls that Inherit from ScriptControl

When first creating your custom server control you inherit from ScriptControl instead of WebControl like usual. The first thing you’ll see is ScriptControl requires you to implement two methods: GetScriptDescriptors and GetScriptReferences. The GetScriptDescriptors method is used to get an enumeration of ScriptDescriptor objects that basically define any of the controls client-side AJAX properties. The GetScriptReferences method is used to get an enumeration of ECMAScript (JavaScript) files that will be loaded on the client-side once the page loads; this is used to basically define any client-side scripts the control will require to run on the client.

Here’s a basic stub of a control that inherits from ScriptControl:

public class CustomScriptControl : ScriptControl
{
    protected override IEnumerable GetScriptDescriptors()
    {
        throw new NotImplementedException();
    } 

    protected override IEnumerable GetScriptReferences()
    {
        throw new NotImplementedException();
    }
}

Define ScriptReferences

In the GetScriptReferences method, return an enumeration of the ScriptReference objects to include in the page on the client-side.

Here’s a basic example of setting up a ScriptReference that includes a script resource that is embedded within the assembly the CustomScriptControl is contained in:

protected override IEnumerable GetScriptReferences()
{
    List references = new List();
    references.Add(new ScriptReference("CustomScriptControl.CustomScriptControl.js", "CustomScriptControl"));
    return references;
}

When inheriting from ScriptControl you’ll generally always have at least one ScriptReference because you need to reference the script that creates the client-side JavaScript representation of your control.

Define ScriptDescriptors

In the GetScriptDescriptors method, return an enumeration of ScriptDescriptor objects that define what properties will be passed down to the client-side JavaScript representation of your control.

The only ScriptDescriptor you need to return in the enumeration is a ScriptControlDescriptor instance that contains all the descriptors for your control.

Here’s a basic example of returning a ScriptControlDescriptor without any properties being defined:

protected override IEnumerable GetScriptDescriptors()
{
    ScriptControlDescriptor descriptor = new ScriptControlDescriptor(this.GetType().FullName, this.ClientID);
    return new ScriptDescriptor[] { descriptor };
}

What ECMAScript (JavaScript) code is needed?

There is a basic block of JavaScript code that you’ll need to include in the ScriptReference that’s being defined by the GetScriptReferences method that will define the client-side JavaScript representation of your control. This basic block of code that you include in this script file is the same for all ScriptControls that you’ll create.

Here’s the basic JavaScript that will define the client-side JavaScript representation of the CustomScriptControl we’re using in this example. One thing to remember is that the namespaces and object name in the JavaScript file need to be the same as they are in the server-side .NET code.

Type.registerNamespace("CustomScriptControl");
CustomScriptControl.CustomScriptControl = function(element) {
    CustomScriptControl.CustomScriptControl.initializeBase(this, [element]);
};
CustomScriptControl.CustomScriptControl.prototype = {
    initialize:function() {
        CustomScriptControl.CustomScriptControl.callBaseMethod(this, "initialize");
    },
    dispose:function() {
        CustomScriptControl.CustomScriptControl.callBaseMethod(this, "dispose");
    }
};
CustomScriptControl.CustomScriptControl.registerClass("CustomScriptControl.CustomScriptControl", Sys.UI.Control);
if(typeof(Sys)!=="undefined")Sys.Application.notifyScriptLoaded();

Also, in order for the ScriptControl to correctly load the “CustomScriptControl.CustomScriptControl.js” script reference that we’re using in this example, we must not forget to define the assemblies embedded resource as a Web Resource, like so:

[assembly: System.Web.UI.WebResource("CustomScriptControl.CustomScriptControl.js", "text/javascript")]

Let’s Add a Little “Rich” AJAX Functionality to the Control

Now that we have a basic ScriptControl created, we can start to add out “rich” AJAX functionality to it.

In this article, I’m going to keep things extremely simple, and we’re going to do the following:

  1. Pass a Name (type string) property to the client by defining it as a ScriptDescriptor, and displaying it to the user via a JavaScript Alert dialog.

Add the Name Property to the Control

public string Name { get; set; }

Add the ScriptDescriptor for the Name Property

Just modify the GetScriptDescriptors method above to look like the following:

protected override IEnumerable GetScriptDescriptors()
{
    ScriptControlDescriptor descriptor = new ScriptControlDescriptor(this.GetType().FullName, this.ClientID); 

    // Add our Name property to be passed down to the client
    descriptor.AddProperty("Name", this.Name); 

    return new ScriptDescriptor[] { descriptor };
}

Add the Client-Side Name Property that will receive the value

To do this we need to add a “private” like variable to hold the value and property accessor methods to our JavaScript file.

Here’s the above JavaScript file with the “private” variable and property accessors added for the Name property:

Type.registerNamespace("CustomScriptControl");
CustomScriptControl.CustomScriptControl = function(element) {
    CustomScriptControl.CustomScriptControl.initializeBase(this, [element]);
    // "private" variable to hold the Name properties value
    this._name = null;
};
CustomScriptControl.CustomScriptControl.prototype = {
    initialize:function() {
        CustomScriptControl.CustomScriptControl.callBaseMethod(this, "initialize");
    },
    dispose:function() {
        CustomScriptControl.CustomScriptControl.callBaseMethod(this, "dispose");
    },
    // Name Property Accessors
    get_Name:function() {
        return this._name;
    },
    set_Name:function(value) {
        this._name = value;
    }    
};
CustomScriptControl.CustomScriptControl.registerClass("CustomScriptControl.CustomScriptControl", Sys.UI.Control);
if(typeof(Sys)!=="undefined")Sys.Application.notifyScriptLoaded();

Display the Name to the User

In this example we’re going to display the Name property to the user using a JavaScript Alert within the objects client-side initialize property.

Here’s the initialize method modified to include the Alert:

initialize:function() {
    CustomScriptControl.CustomScriptControl.callBaseMethod(this, "initialize"); 

    // Display Name property to user
    alert(this.get_Name()); 
},

Tips and Tricks

One trick that I’ve learned that proves useful when creating custom ScriptControls, is to create your own base class that inherits from ScriptControl and extends it to use some custom attributes to make adding ScriptReferences and ScriptDescriptors easier.

Here’s what the final server-side code of the CustomScriptControl object would look like when using custom attributes to add the ScriptReferences and ScriptDescriptors:

[ScriptReference("CustomScriptControl.CustomScriptControl.js", "CustomScriptControl")]
public class CustomScriptControl : ScriptControlBase
{
    [ScriptControlProperty]
    public string Name { get; set; }
}

As you can see, the new code for the CustomScriptControl using this base class with custom attributes is much cleaner and easier to read.

Here’s the complete code for the ScriptControlBase, ScriptReferenceAttribute and ScriptControlPropertyAttribute objects:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Web.UI;

public class ScriptControlBase : ScriptControl
{
    protected override IEnumerable GetScriptDescriptors()
    {
        ScriptControlDescriptor descriptor = new ScriptControlDescriptor(this.GetType().FullName, this.ClientID); 

        // Add all the ScriptControls Client-Side Object Properties
        PropertyDescriptorCollection props = TypeDescriptor.GetProperties(this);
        foreach (PropertyDescriptor prop in props)
        {
            ScriptControlPropertyAttribute propAttr = prop.Attributes[typeof(ScriptControlPropertyAttribute)] as ScriptControlPropertyAttribute;
            if (propAttr != null)
            {
                object value = prop.GetValue(this);
                string name = (propAttr.Name != null) ? propAttr.Name : prop.Name;
                if (value != null)
                {
                    descriptor.AddProperty(name, value);
                }
            }
        } 

        return new ScriptDescriptor[] { descriptor };
    } 

    protected override IEnumerable GetScriptReferences()
    {
        List references = new List(); 

        // Add all the ScriptControls Client-Side JavaScript References
        object[] scriptReferences = Attribute.GetCustomAttributes(this.GetType(), typeof(ScriptReferenceAttribute), false);
        foreach (ScriptReferenceAttribute r in scriptReferences)
        {
            references.Add(r.GetScriptReference());
        } 

        return references;
    }
} 

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class ScriptControlPropertyAttribute : Attribute
{
    public ScriptControlPropertyAttribute() { } 

    public ScriptControlPropertyAttribute(string name)
    {
        this.Name = name;
    } 

    public string Name { get; set; }
} 

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class ScriptReferenceAttribute : Attribute
{
    public ScriptReferenceAttribute(string path)
    {
        this.Path = path;
    } 

    public ScriptReferenceAttribute(string name, string assembly)
    {
        this.Name = name;
        this.Assembly = assembly;
    } 

    private string _path = null;
    public string Path
    {
        get { return _path; }
        set { _path = value; }
    } 

    private string _name = null;
    public string Name
    {
        get { return _name; }
        set { _name = value; }
    } 

    private string _assembly = null;
    public string Assembly
    {
        get { return _assembly; }
        set { _assembly = value; }
    } 

    public ScriptReference GetScriptReference()
    {
        ScriptReference r = null; 

        if (this.Path == null)
        {
            r = new ScriptReference(this.Name, this.Assembly);
        }
        else
        {
            r = new ScriptReference(this.Path);
        } 

        return r;
    }
}

Conclusion

As you can see it’s fairly simple to get started creating your own custom server controls that inherit from ScriptControl, especially if you use the above ScriptControlBase class along with its ScriptControlPropertyAttribute and ScriptReferenceAttribute attributes.

Download Full Source Code: CreateAJAXServerControlUsingScriptControlBaseClass.zip (33.31 kb)

Update 5/31/2008: I recently posted an article titled “Create AJAX Extender Controls using the ExtenderControl base class” on how to create Extender Controls in ASP.NET using the System.Web.UI.ExtenderControl base class.