Export (0) Print
Expand All

Adding Client Capabilities to a Web Server Control

The AJAX functionality of ASP.NET enables you to expand the capabilities of a Web application in order to create a rich user experience. You can use ECMAScript (JavaScript), DHTML, and AJAX capabilities of the Web browser to include visual effects, client processing such as validation, and so on.

This topic shows you how to create a custom Web server control that uses AJAX features of ASP.NET for expanded functionality in the browser. You can add functionality to client Document Object Model (DOM) elements by using a client control. The client control can be associated with a server control by implementing the IScriptControl interface in the server control.

NoteNote

This is an advanced tutorial for developers who want to create custom controls or add Ajax capabilities to a control. To work with controls that already have Ajax capabilities, see the ASP.NET AJAX Control Toolkit.

In this topic you will learn how to do the following:

  • Create a Web server control that encapsulates client behavior and that includes properties that users can set to control the behavior.

  • Create a client control that is associated with the Web server control.

  • Handle events from the browser DOM in the client control.

    NoteNote

    You can also add rich client capabilities to Web server controls by creating an extender control. This extender control encapsulates client capabilities in a behavior and can then be attached to a Web server control. Because an extender control is not part of its associated control, you can create a single extender control that can be associated with several types of Web server controls. For an example, see Creating an Extender Control to Associate a Client Behavior with a Web Server Control.

  • Compile the custom Web server control into an assembly and embed associated JavaScript files as resources in the same assembly.

  • Reference the compiled custom Web server control in an ASP.NET AJAX-enabled Web page.

A Visual Studio project with source code is available to accompany this topic: Download.

The first step in adding client behavior to a Web server control is to decide what the behavior of the control will be in the browser. You then determine the client functionality that is needed to implement the behavior.

The Web server control that is created in this topic implements a simple client behavior. The control (a TextBox control) is highlighted when it is selected (or has focus) in the browser. For example, the control might change background color when it has focus, and then return to the default color when focus moves to another control.

To implement this behavior, the client control in this topic requires the capabilities that are listed in the following table.

A way to highlight a DOM element.

To highlight a DOM element in an ASP.NET Web page, the client control applies a cascading style sheet (CSS) style, which is identified by a class name. This style is user configurable.

A way to return the DOM element to its non-highlighted state.

To remove the highlight from a DOM element in an ASP.NET Web page, the client control applies a CSS style, which is identified by a class name. This style is user configurable and is applied to the DOM element as the default style.

A way to identify when a DOM element is selected.

To identify when a DOM element is selected (has focus), the control handles the onfocus event of the DOM element.

A way to identify when a DOM element is not selected.

To identify when a control is no longer selected, the control handles the onblur event of the DOM element.

A Web server control that includes client features by using ASP.NET AJAX functionality is like any other Web server control. However, the control also implements the IScriptControl interface from the System.Web.UI namespace. The control in this topic extends the ASP.NET TextBox control by inheriting the TextBox class and implementing the IScriptControl interface.

The following example shows the class definition.


public class SampleTextBox : TextBox, IScriptControl


The new Web server control includes two properties that are used to implement the client requirements:

  • HighlightCssClass, which identifies the CSS class that will be applied to the DOM element to highlight the control when it has focus.

  • NoHighlightCssClass, which identifies the CSS class that will be applied to the DOM element when it does not have focus.

Implementing the IScriptControl Interface

The following table lists members of the IScriptControl interface that you must implement in a Web server control.

GetScriptDescriptors

Returns a collection of ScriptDescriptor objects that contain information about instances of client components that are used with the Web server control. This includes the client type to create, the properties to assign, and the events to add handlers for.

GetScriptReferences

Returns a collection of ScriptReference objects that contain information about the client-script libraries to be included with the control. The client-script libraries define the client types and any other JavaScript code that is required for the behavior.

The Web server control in this topic uses the GetScriptDescriptors method to define the instance of the client control type. The control creates a new ScriptControlDescriptor object (the ScriptControlDescriptor class derives from the ScriptDescriptor class) and includes the object in the return value for the GetScriptDescriptors method.

The ScriptControlDescriptor object includes the name of the client class (Samples.SampleTextBox) and the ClientID value for the Web server control. This value is used as the id value for the rendered DOM element. The client class name and the ClientID property values are passed to the constructor for the ScriptControlDescriptor object.

The ScriptControlDescriptor class is used to set the client control's property values, which are obtained from properties of the Web server control. To define the client control's properties, the Web server control uses the ScriptComponentDescriptor.AddProperty method of the ScriptControlDescriptor class. The Web server control then specifies a name and value for the property of the client control, based on the corresponding property of the Web server control. This example uses a ScriptControlDescriptor object to set the values of the highlightCssClass and nohighlightCssClass properties in the client control.

The Web server control supplies the ScriptControlDescriptor object in the return value for the GetScriptDescriptors method. Therefore, whenever the Web server control is rendered to the browser, ASP.NET renders JavaScript that creates an instance of the client control with all defined properties and event handlers. The control instance is attached to the DOM element, based on the ClientID property that is rendered from the Web server control. The following example shows declarative ASP.NET markup that includes the Web server control from this topic in a page.

<sample:SampleTextBox runat="server"
  ID="SampleTextBox1"
  HighlightCssClass="MyHighLight"
  NoHighlightCssClass="MyLowLight" />

The rendered output of the page includes a call to the $create method that identifies the client class to create. It also provides values for the client properties and the id value of the DOM object that the client control is associated with. The following example shows a rendered $create method.

$create(Samples.SampleTextBox, {"highlightCssClass":"MyHighLight","nohighlightCssClass":"MyLowLight"}, null, null, $get('SampleTextBox1'));

The example Web server control uses the GetScriptReferences method to pass the location of the script library that defines the client control type. In this example, this is a URL to the script file named SampleTextBox.js, which you will create later. The reference is made by creating a new ScriptReference object, and by then setting the Path property to the URL of the file that contains the client code.

The following example shows the implementations of the GetScriptDescriptors and GetScriptReferences methods.


protected virtual IEnumerable<ScriptReference> GetScriptReferences()
{
    ScriptReference reference = new ScriptReference();
    reference.Path = ResolveClientUrl("SampleTextBox.js");

    return new ScriptReference[] { reference };
}

protected virtual IEnumerable<ScriptDescriptor> GetScriptDescriptors()
{
    ScriptControlDescriptor descriptor = new ScriptControlDescriptor("Samples.SampleTextBox", this.ClientID);
    descriptor.AddProperty("highlightCssClass", this.HighlightCssClass);
    descriptor.AddProperty("nohighlightCssClass", this.NoHighlightCssClass);

    return new ScriptDescriptor[] { descriptor };
}

IEnumerable<ScriptReference> IScriptControl.GetScriptReferences()
{
    return GetScriptReferences();
}

IEnumerable<ScriptDescriptor> IScriptControl.GetScriptDescriptors()
{
    return GetScriptDescriptors();
}


Registering the Client Control

Client controls must be registered with the ScriptManager object for the current page. This is accomplished by calling the RegisterScriptControl<TScriptControl> method of the ScriptManager class and supplying a reference to the client control.

The example Web server control registers itself as a client control with the ScriptManager control on the page. To do this, the control overrides the OnPreRender method of the base TextBox control. It then calls the RegisterScriptControl<TScriptControl> method to register itself as a client control. Additionally, the control registers the script descriptors that are created by the GetScriptDescriptors method. It does so by calling the RegisterScriptDescriptors method in the control's Render method.

The following example shows the calls to the RegisterScriptControl<TScriptControl> and RegisterScriptDescriptors methods.


protected override void OnPreRender(EventArgs e)
{
    if (!this.DesignMode)
    {
        // Test for ScriptManager and register if it exists
        sm = ScriptManager.GetCurrent(Page);

        if (sm == null)
            throw new HttpException("A ScriptManager control must exist on the current page.");

        sm.RegisterScriptControl(this);
    }

    base.OnPreRender(e);
}

protected override void Render(HtmlTextWriter writer)
{
    if (!this.DesignMode)
        sm.RegisterScriptDescriptors(this);

    base.Render(writer);
}


The following example shows the complete code for the Web server control.


using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Collections.Generic;

namespace Samples.CS
{
    public class SampleTextBox : TextBox, IScriptControl
    {
        private string _highlightCssClass;
        private string _noHighlightCssClass;
        private ScriptManager sm;

        public string HighlightCssClass
        {
            get { return _highlightCssClass; }
            set { _highlightCssClass = value; }
        }

        public string NoHighlightCssClass
        {
            get { return _noHighlightCssClass; }
            set { _noHighlightCssClass = value; }
        }

        protected override void OnPreRender(EventArgs e)
        {
            if (!this.DesignMode)
            {
                // Test for ScriptManager and register if it exists
                sm = ScriptManager.GetCurrent(Page);

                if (sm == null)
                    throw new HttpException("A ScriptManager control must exist on the current page.");

                sm.RegisterScriptControl(this);
            }

            base.OnPreRender(e);
        }

        protected override void Render(HtmlTextWriter writer)
        {
            if (!this.DesignMode)
                sm.RegisterScriptDescriptors(this);

            base.Render(writer);
        }

        protected virtual IEnumerable<ScriptReference> GetScriptReferences()
        {
            ScriptReference reference = new ScriptReference();
            reference.Path = ResolveClientUrl("SampleTextBox.js");

            return new ScriptReference[] { reference };
        }

        protected virtual IEnumerable<ScriptDescriptor> GetScriptDescriptors()
        {
            ScriptControlDescriptor descriptor = new ScriptControlDescriptor("Samples.SampleTextBox", this.ClientID);
            descriptor.AddProperty("highlightCssClass", this.HighlightCssClass);
            descriptor.AddProperty("nohighlightCssClass", this.NoHighlightCssClass);

            return new ScriptDescriptor[] { descriptor };
        }

        IEnumerable<ScriptReference> IScriptControl.GetScriptReferences()
        {
            return GetScriptReferences();
        }

        IEnumerable<ScriptDescriptor> IScriptControl.GetScriptDescriptors()
        {
            return GetScriptDescriptors();
        }
    }
}


In the Web server control, the GetScriptReferences method specifies a JavaScript file (SampleTextBox.js) that contains the client code for the control type. This section describes the JavaScript code in that file.

The client control code matches the members that were specified in the ScriptDescriptor objects that are returned by the GetScriptDescriptors method. A client control can also have members that do not correspond to members in the Web server control class.

The example Web server control sets the name of the client control to Samples.SampleTextBox, and it defines two properties of the client control: highlightCssClass and nohighlightCssClass.

For more information about how to create client components and controls, see Creating a Client Component Class Using the Prototype Model.

Registering the Client Namespace

The client control code must first call the registerNamespace method of the Type class to register its namespace (Samples). The following example shows how to register the client namespace.


// Register the namespace for the control.
Type.registerNamespace('Samples');


Defining the Client Class

After the client namespace is registered, the code defines the Samples.SampleTextBox class. This class includes two properties to hold the property values supplied by the Web server control. It also includes two event delegates that specify handlers for the onfocus and onblur events of the DOM element that is associated with the Samples.SampleTextBox control.

The following example shows the Samples.SampleTextBox class definition.


Samples.SampleTextBox = function(element) { 
    Samples.SampleTextBox.initializeBase(this, [element]);

    this._highlightCssClass = null;
    this._nohighlightCssClass = null;
}


Defining the Class Prototype

After the Samples.SampleTextBox class is defined, the client code defines the prototype for the class. The prototype includes get and set accessors for properties as well as event handlers. It also includes an initialize method that is called when an instance of the control is created, and a dispose method that performs cleanup when the control is no longer required by the page.

Defining the Event Handlers for the DOM Element

Event handlers for a client class are defined as methods of the class prototype. The handlers are associated with event delegates and with events of the browser DOM by using the addHandlers method, which is discussed later in this topic with the initialize method.

The following example shows the event handler methods for the Samples.SampleTextBox control.


_onFocus : function(e) {
    if (this.get_element() && !this.get_element().disabled) {
        this.get_element().className = this._highlightCssClass;          
    }
},

_onBlur : function(e) {
    if (this.get_element() && !this.get_element().disabled) {
        this.get_element().className = this._nohighlightCssClass;          
    }
},


Defining the Property Get and Set Methods

Each property identified in the ScriptDescriptor object of the Web server control's GetScriptDescriptors method must have corresponding client accessors. The client property accessors are defined as get_<property name> and set_<property name> methods of the client class prototype.

NoteNote

JavaScript is case-sensitive. The get and set accessor names must exactly match the property names identified in the ScriptDescriptor object of the GetScriptDescriptors method in the Web server control.

The following example shows the property get and set accessors for the Samples.SampleTextBox control.


get_highlightCssClass : function() {
    return this._highlightCssClass;
},

set_highlightCssClass : function(value) {
    if (this._highlightCssClass !== value) {
        this._highlightCssClass = value;
        this.raisePropertyChanged('highlightCssClass');
    }
},

get_nohighlightCssClass : function() {
    return this._nohighlightCssClass;
},

set_nohighlightCssClass : function(value) {
    if (this._nohighlightCssClass !== value) {
        this._nohighlightCssClass = value;
        this.raisePropertyChanged('nohighlightCssClass');
    }
}


Implementing the Initialize and Dispose Methods

The initialize method is called when an instance of the control is created. Use this method to set default property values, to create function delegates, and to add delegates as event handlers.

The initialize method of the Samples.SampleTextBox class does the following:

  • Calls the initialize method of the Sys.UI.Control base class.

  • Calls the addHandlers method to add event delegates as handlers for the onfocus and onblur events of the associated HTML element (<input type="text">). The "on" part of the event name (for example, onfocus) is not specified.

The dispose method is called when an instance of the control is no longer used on the page and is removed. Use this method to free any resources that are no longer required for the control, such as DOM event handlers.

The dispose method of the Sample.SampleTextBox class does the following:

  • Calls the clearHandlers method to clear the event delegates as handlers for the onfocus and onblur events of the associated DOM element.

  • Calls the dispose method of the Control base class.

    NoteNote

    The dispose method of a client class might be called more than one time. Make sure that the code you include in the dispose method takes this into account.

The following example shows the implementation of the initialize and dispose methods for the Samples.SampleTextBox prototype.



initialize : function() {
    Samples.SampleTextBox.callBaseMethod(this, 'initialize');

    this._onfocusHandler = Function.createDelegate(this, this._onFocus);
    this._onblurHandler = Function.createDelegate(this, this._onBlur);

    $addHandlers(this.get_element(), 
                 { 'focus' : this._onFocus,
                   'blur' : this._onBlur },
                 this);

    this.get_element().className = this._nohighlightCssClass;
},

dispose : function() {
    $clearHandlers(this.get_element());

    Samples.SampleTextBox.callBaseMethod(this, 'dispose');
},


Registering the Control

The final task in creating the client control is to register the client class by calling the registerClass method. Because the class is a client control, the call to the registerClass method includes the JavaScript class name to register. It also specifies Control as the base class.

The following example shows the call to the registerClass method for the Samples.SampleTextBox control. The example also includes a call to the notifyScriptLoaded method of the Sys.Application object.


Samples.SampleTextBox.registerClass('Samples.SampleTextBox', Sys.UI.Control);

if (typeof(Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();


The following example shows the complete code for the Samples.SampleTextBox client control.


// Register the namespace for the control.
Type.registerNamespace('Samples');

//
// Define the control properties.
//
Samples.SampleTextBox = function(element) { 
    Samples.SampleTextBox.initializeBase(this, [element]);

    this._highlightCssClass = null;
    this._nohighlightCssClass = null;
}

//
// Create the prototype for the control.
//

Samples.SampleTextBox.prototype = {


    initialize : function() {
        Samples.SampleTextBox.callBaseMethod(this, 'initialize');

        this._onfocusHandler = Function.createDelegate(this, this._onFocus);
        this._onblurHandler = Function.createDelegate(this, this._onBlur);

        $addHandlers(this.get_element(), 
                     { 'focus' : this._onFocus,
                       'blur' : this._onBlur },
                     this);

        this.get_element().className = this._nohighlightCssClass;
    },

    dispose : function() {
        $clearHandlers(this.get_element());

        Samples.SampleTextBox.callBaseMethod(this, 'dispose');
    },

    //
    // Event delegates
    //

    _onFocus : function(e) {
        if (this.get_element() && !this.get_element().disabled) {
            this.get_element().className = this._highlightCssClass;          
        }
    },

    _onBlur : function(e) {
        if (this.get_element() && !this.get_element().disabled) {
            this.get_element().className = this._nohighlightCssClass;          
        }
    },


    //
    // Control properties
    //

    get_highlightCssClass : function() {
        return this._highlightCssClass;
    },

    set_highlightCssClass : function(value) {
        if (this._highlightCssClass !== value) {
            this._highlightCssClass = value;
            this.raisePropertyChanged('highlightCssClass');
        }
    },

    get_nohighlightCssClass : function() {
        return this._nohighlightCssClass;
    },

    set_nohighlightCssClass : function(value) {
        if (this._nohighlightCssClass !== value) {
            this._nohighlightCssClass = value;
            this.raisePropertyChanged('nohighlightCssClass');
        }
    }
}

// Optional descriptor for JSON serialization.
Samples.SampleTextBox.descriptor = {
    properties: [   {name: 'highlightCssClass', type: String},
                    {name: 'nohighlightCssClass', type: String} ]
}

// Register the class as a type that inherits from Sys.UI.Control.
Samples.SampleTextBox.registerClass('Samples.SampleTextBox', Sys.UI.Control);

if (typeof(Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();


Any Web server control, such as the control in this topic, must be compiled before you can reference it in a Web page. You can use the dynamic compilation feature of ASP.NET version 2.0 to test Web server controls without manually compiling the controls into an assembly. This saves time when you are initially writing and debugging the Web server control code. The following steps show you how to use the App_Code folder to dynamically compile your control.

To put the control in the App_Code folder for dynamic compilation

  1. Create an App_Code folder under the root folder of the Web site.

  2. Move the .cs or .vb control source files and any related classes into the App_Code folder

    -or-

    If you previously added an assembly for the control to the Bin folder, delete the assembly. You continue to edit the source files in the App_Code folder. The control source code will be compiled every time that you run your project.

    NoteNote

    You can precompile a control into an assembly and place the assembly in the Bin folder, or you can place the control's source file in the App_Code folder, but you cannot do both. If you add the control to both folders, the page parser will not be able to resolve a reference to the control in a page and will raise an error.

  3. Run the Web page.

    The control is dynamically compiled.

The following procedure describes how to test the control in an ASP.NET AJAX-enabled Web page. The code for the Web server control is dynamically compiled from the App_Code folder.

To use the behavior in an ASP.NET page

  1. Create a new ASP.NET Web page.

  2. If the page does not already have a ScriptManager control, add one.

  3. Create CSS style rules for text boxes that are highlighted and for the text boxes that are not highlighted.

    You can highlight the control any way that you like. For example, you can change the control's background color, add a border, or change the font of text.

  4. Add an @ Register directive to the page, and then specify the namespace and the TagPrefix attribute for the control, as shown in the following example.

    NoteNote

    In this case, the server control code is in the App_Code folder so that it can be dynamically compiled. Therefore, an assembly attribute is not specified.

  5. Add a TextBox and a Button control to the page, and set their ID properties.

    The markup for the controls must include runat="server".

  6. Set the HighlightCssClass property to the highlight CSS style, and set the NoHighlightCssClass property to the no highlight CSS style.

  7. Run the page and select each control.

    When you select the Button control, it is highlighted.

The following example shows an ASP.NET page that uses the behavior that was created in this topic.

run:IScriptControlTutorial1

Embedding the JavaScript component and Web server control's extension code into an assembly will make your custom control easier to deploy. Controls cannot be added to the toolbox of a designer unless they are compiled into an assembly. Creating an assembly also makes it easier to manage version control for the control.

To compile the custom server control into an assembly

  1. In the Properties window for the SampleTextBox.js file, set Build Action to Embedded Resource.

    Set script file to embedded resource
  2. Add the following property to the AssemblyInfo file.

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

    The AssemblyInfo.vb file is in the My Project node of Solution Explorer. If you do not see any files in the My Project node, do the following: on the Project menu, click Show All Files. The AssemblyInfo.cs file is in the Properties node of Solution Explorer.

    The WebResource definition for JavaScript files must follow a naming convention of [assembly namespace].[JavaScript File name].js.

    NoteNote

    By default, Visual Studio sets the assembly namespace to the assembly name. You can edit the assembly namespace in the assembly's properties.

  3. In the SampleTextBox class file, change the ScriptReference object in the GetScriptReferences method to reference the client control script that is embedded in the Samples assembly. To do so, make the following changes:

    • Replace the Path property with a Assembly property set to Samples.

    • Add a Name property and set its value to "Samples.SampleTextBox.js".

    The following example shows the result of this change.

    
            protected virtual IEnumerable<ScriptReference> GetScriptReferences()
            {
                ScriptReference reference = new ScriptReference();
    			reference.Assembly = "Samples";
    			reference.Name = "Samples.SampleTextBox.js";
    
                return new ScriptReference[] { reference };
            }
    
    
    
  4. Build the project.

    When compilation finishes, you will have an assembly named Samples.dll. The JavaScript code file (SampleTextBox.js) is embedded in this assembly as a resource.

    NoteNote

    Remember to rebuild the class library project any time that you add new source files or change existing ones.

You will now reference the compiled custom control in an ASP.NET AJAX-enabled Web page.

To reference the custom control in an ASP.NET AJAX-enabled Web page

  1. Create a new ASP.NET AJAX project.

  2. In the root directory of the Web site, create a Bin folder.

  3. Copy the Samples.dll assembly from the Bin\Debug or Bin\Release folder of the Samples class project to the new Bin folder.

  4. Add a new ASP.NET Web page named TestSampleTextBoxAssembly.aspx, and then add the following markup to the new page.

    
    <%@ Register Assembly="Samples" Namespace="Samples.CS" TagPrefix="sample" %>
    
    
    

    Because the server control is compiled into an assembly, the @ Register directive has an Assembly attribute that references the "Samples" assembly in addition to the Namespace and TagPrefix attributes.

  5. Run the page and select each control.

    When you select the SampleTextBox control, it is highlighted.

The Web page that uses the compiled custom control includes the Assembly attribute in the @ Register directive. Otherwise, it is the same as the Web page you used for the control in the App_Code folder.

Community Additions

ADD
Show:
© 2014 Microsoft