Or – The Complete Basics of Writing Server Controls that Communicate with the Client.

I have a bone to pick with Microsoft and their ASP.NET AJAX Control Toolkit. The controls aren’t AJAX.

AJAX stands for Asynchronous Javascript and XML – although you can also use JSON. Something is only AJAX if you are getting data client-side and refreshing some HTML. The AJAX controls from the toolkit only do Dynamic HTML. Just using Javascript does not make something AJAX.

At least the Script Control class is named correctly – because that is all it is, a control with some Javascript.

Which is awesome, but it is missing something. When I need to get data from the server, I don’t want to have the entire page redraw – a la Update Panel. I just want my data. But, in the world of ASP.NET, I’d rather not have to write a web service and call it from the client – when there is already this idea of the Post Back.

Luckily, there is a way to make a Script control that allows for Call Backs from the client code. I wish it was built into Script Control, but it isn’t hard to add.

Let’s write a little sub-class for script control, called, AjaxControl. Because, it should have been there from the beginning anyway.

Add a new Class

To start with, we’ll need to add a new class to our project.

  1. Add a new class file to your project.
  2. Name it AjaxControl and click OK.
  3. Add a using statement for System.Web.UI
  4. Inherit from ScriptControl
  5. Add an implementation for ICallbackEventHandler
  6. Mark the class as abstract
    1. Since this class doesn’t really do anything other than define some methods, we don’t want someone to try and instantiate it.

    using System;

    using System.Collections.Generic;

    using System.Linq;

    using System.Text;

    using System.Web.UI;

     

    namespace TestAjaxServerControl

    {


    public abstract
    class
    AjaxControl : ScriptControl, ICallbackEventHandler

    {

    }

    }

 

Implement ICallbackEventHandler

Now we need to hook up ICallbackEventHandler so we can use this class to handle callbacks from the client.

  1. Right-click on ICallbackEventHandler and select Implement Interface -> Implement Interface
  2. Add a private string named callbackResult
  3. Add a protected abstract string function named OnCallback that takes a string argument.
    1. When you inherit from this class, you’ll have to implement OnCallback

protected
string callbackResult;

 


///
<summary>


/// Gets the result of the callback.


///
</summary>


///
<param name=”eventArgument”>The message from the client.</param>


///
<returns>The result to send to the client.</returns>


protected
abstract
string OnCallback(string eventArgument);

 

  1. In the RaiseCallbackEvent method, delete the exception and add:

public
void RaiseCallbackEvent(string eventArgument)

{

callbackResult = OnCallback(eventArgument);

}

 

  1. In the GetCallbackResult method, return callbackResult

public
string GetCallbackResult()

{


return callbackResult;

}

 

Create and Implement the Script Control

If you already have a script control setup, then you can just change it to inherit from AjaxControl and add an override for OnCallback – and skip down to the Javascript section. Here I’m going to follow the process of writing a script control from scratch, because I think it would be helpful to document the whole process.

  1. Create a new class file
  2. Add a using statement for System.Web.UI
  3. Mark it public
  4. Inherit from AjaxControl
  5. Right-click on AjaxControl and select Implement Abstract Class
  6. It should look like this now:

    using System;

    using System.Collections.Generic;

    using System.Linq;

    using System.Text;

    using System.Web.UI;

     

    namespace TestAjaxServerControl.TestAjaxControl

    {


    public
    class
    TestAjaxControl : AjaxControl

    {


    protected
    override
    string OnCallback(string eventArgument)

    {


    throw
    new
    NotImplementedException();

    }

     


    protected
    override
    IEnumerable<ScriptDescriptor> GetScriptDescriptors()

    {


    throw
    new
    NotImplementedException();

    }

     


    protected
    override
    IEnumerable<ScriptReference> GetScriptReferences()

    {


    throw
    new
    NotImplementedException();

    }

    }

    }

 

  1. Add a new JScript file to the project.
    1. It is easiest to just name it the same as your control class.
  2. In the JScript file, add a reference to MicrosoftAjax.js
  3. Register the Namespace your control is in
    1. The Namespaces will need to match between the server and client

     

    /// <reference name=”MicrosoftAjax.js”/>

     

    Type.registerNamespace(“TestAjaxServerControl.TestAjaxControl”);

 

  1. Now declare the “class” instance for your client control
    1. In javascript it isn’t really a class, it’s a function, but it ends up working similar
    2. Note that you need to use the full namespace too

    TestAjaxServerControl.TestAjaxControl.TestAjaxControl = function (element) {

    TestAjaxServerControl.TestAjaxControl.TestAjaxControl.initializeBase(this, [element]);

    }

 

  1. Now add the meat of the class
    1. I’ve added placeholder comments for where to put other code

    TestAjaxServerControl.TestAjaxControl.TestAjaxControl.prototype = {

    initialize: function () {

    TestAjaxServerControl.TestAjaxControl.TestAjaxControl.callBaseMethod(this, ‘initialize’);


    // do initialization

    },

    dispose: function () {


    // do cleanup

     


    // base dispose

    TestAjaxServerControl.TestAjaxControl.TestAjaxControl.callBaseMethod(this, ‘dispose’);

    }


    // EXTRA FUNCTIONS

     


    // PROPERTIES

     


    // EVENTS

    }

 

  1. Then, tell Microsoft AJAX to hook it all up
    1. This will be the last thing in the Jscript file

    TestAjaxServerControl.TestAjaxControl.TestAjaxControl.registerClass(‘TestAjaxServerControl.TestAjaxControl.TestAjaxControl’, Sys.UI.Control);

     

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

 

  1. Now, we need to register this file as a script resource…
  2. In the Solution Explorer, select your Jscript file
  3. In the Properties window, change the Build Action to Embedded Resource
  4. In the Solution Explorer, under the Properties folder, open the AssemblyInfo.cs file
  5. Add the following line at the bottom to set up the web resource:
    1. Notice that while my Jscript file is named “TestAjaxControl.js”, when we add it as a web resource it needs to have the folders before it. This is due to how resources are stored inside the assembly.
    2. In the project, “TestAjaxServerControl”, I have the Jscript file in the folder, “TestAjaxControl”
    3. You can also add a resource file for the script to use, and it would be declared here as a ScriptResource, but that is outside this example.

    [assembly: WebResource(“TestAjaxServerControl.TestAjaxControl.TestAjaxControl.js”, “text/javascript”)]

 

  1. Now we need to tell the script manager about the script file…
  2. Open the class file for the Control (inherited from AjaxControl) that we made earlier
  3. In the GetScriptDescriptors function, add:

protected
override
IEnumerable<ScriptDescriptor> GetScriptDescriptors()

{


ScriptControlDescriptor descriptor = new
ScriptControlDescriptor(“TestAjaxServerControl.TestAjaxControl.TestAjaxControl”, this.ClientID);


// set properties here

 


yield
return descriptor;

}

 

  1. In the GetScriptReferences function, add:
    1. The name here has to be the same one declared in the assemblyinfo class
    2. Note that we’re using a tiny bit of reflection to get the assembly fullname
      1. Some examples will tell you to just write in the partial name here, but don’t do it. It can cause all kinds of headaches in the future, especially if you end up deciding to run it in SharePoint. The web.config work needed to make the partial name resolve isn’t worth it.

protected
override
IEnumerable<ScriptReference> GetScriptReferences()

{


yield
return
new
ScriptReference(“TestAjaxServerControl.TestAjaxControl.TestAjaxControl.js”, this.GetType().Assembly.FullName);

}

 

Setting up the Javascript for Call Backs

Now that the groundwork is laid, it is time to make this class do something. For this example, I’m just going to get the time from the server, because that is one of the Update panel examples that I read back in the day (and it is super easy).

  1. Add a using statement for Sytem.Web.UI.Controls
  2. In the server control code, declare a Label and a Button.

private
Label timeLabel;


private
Button updateButton;

 

  1. Add an override for OnPreRender
    1. I usually don’t bother with create child controls in one of these. You can if you want, or if you are creating a bunch of stuff.
  2. Add a check for making sure there is a ScriptManager on the page and register this control with it
    1. I usually just throw an exception if it is null, as a reminder to the Page developer that they need one.

protected
override
void OnPreRender(EventArgs e)

{


// check for script manager


ScriptManager sm = ScriptManager.GetCurrent(Page);


if (sm == null)

{


throw
new
InvalidOperationException(“ScriptManager required on Page.”);

}


else

{

sm.RegisterScriptControl(this);

}

 

}

 

  1. After the Script Manager check, add a call to get the callback event reference.
    1. I’ve never actually used the information that comes back, lol. But, if you don’t call it, your callbacks won’t work.
    2. I should probably research this more, but, meh. This works.

// if I don’t call this then nothing happens


string temp = Page.ClientScript.GetCallbackEventReference(this, “blah”, “blah”, null);

 

  1. After that, create the controls and add them to the control collection.
    1. Notice that I’m giving them IDs. This is so I have readable Client IDs for client-side debugging if things go wrong. In a bigger control, trying to figure out what “Label1” was can be painful.

timeLabel = new
Label()

{

Text = DateTime.Now.ToString(),

ID = “timeLabel”

};


this.Controls.Add(timeLabel);

 

updateButton = new
Button()

{

Text = “Update”,

ID = “updateButton”

};


this.Controls.Add(updateButton);

 

  1. Now to tell the client code some information…
  2. Go to the GetScriptDescriptors function in the server control
  3. Before the yield statement, call descriptor.AddProperty
    1. We need to tell the client code the Client IDs for our controls and the ID to make callbacks to
    2. Note: later we’ll add the client code to take this in
    3. It should look like this now:

protected
override
IEnumerable<ScriptDescriptor> GetScriptDescriptors()

{


ScriptControlDescriptor descriptor = new
ScriptControlDescriptor(“TestAjaxServerControl.TestAjaxControl.TestAjaxControl”, this.ClientID);


// set properties here

descriptor.AddProperty(“TimeLabelId”, timeLabel.ClientID);

descriptor.AddProperty(“UpdateButtonId”, updateButton.ID);

descriptor.AddProperty(“CallbackId”, this.ClientID.Replace(this.ClientIDSeparator, this.IdSeparator));

 


yield
return descriptor;

}

 

  1. This last step in the Server control code is to handle the Callback event…
  2. In the OnCallback function, check that the eventArgument equals “GetCurrentTime” and return DateTime.Now
    1. I did this with a simple switch statement
    2. The idea here is that the client code will send some message telling the server what data it expects, this could be a simple string, such as in this case, or a blob or XML or JSON.

protected
override
string OnCallback(string eventArgument)

{


switch (eventArgument)

{


case
“GetCurrentTime”:


return
DateTime.Now.ToString();


default:


return
string.Empty;

}

}

 

  1. That’s it for the server control. Switch back over to the Jscript file you created.
  2. After the dispose method, add a comma
  3. Down after the Properties placeholder (if you copied the code from earlier), add these properties:
    1. The name (after the “get_” and “set_” has to be exactly the same as when you added the property to the descriptor in the server code. This is how the script manager knows what properties to set when the client code is initialized.
    2. Also, through the magic of Javascript, you don’t have to declare the local variables anywhere, they get created the first time they are set
    3. Also, remember that you need a comma after every function, because we are inside a prototype section

    // PROPERTIES

    get_TimeLabelId: function () { return
    this._timeLabelId; },

    set_TimeLabelId: function (value) { this._timeLabelId = value; },

     

    get_UpdateButtonId: function () { return
    this._updateButtonId; },

    set_UpdateButtonId: function (value) { this._updateButtonId = value; },

     

    get_CallbackId: function () { return
    this._callbackId; },

    set_CallbackId: function (value) { this._callbackId = value; }

 

  1. Now we need to create some event handlers to do the callback
  2. After the properties (and remembering to add a comma after the last “set”), add event handlers for updateButton_click and handleCallback

    // EVENTS

    _updateButton_click: function (e, context) {

     

    },

    _handleCallback: function (results, context) {

     

    }

 

  1. In the click handler, create a delegate to _handleCallback and a call WebForm_DoCallBack
    1. You could setup the delegates during in the Initialize function, but I like to keep everything in one place.
    2. Also, notice we are passing in “GetCurrentTime”, which is the string the Server Control is expecting.

_updateButton_click: function (e, context) {


// create callback delegate


if (this._callbackDelegate == null) {


this._callbackDelegate = Function.createDelegate(this, this._handleCallback);

}

WebForm_DoCallback(this._callbackId, ‘GetCurrentTime’, this._callbackDelegate, null, null, false);

},

 

  1. In the _handleCallback function, add code to update the time displayed in the label

_handleCallback: function (results, context) {


var timeLabel = $get(this._timeLabelId);

timeLabel.innerHTML = results;

}

 

  1. Go back to the initialize function in the client code.
  2. After the callBaseMethod call, hook up the click event on the button

    initialize: function () {

    TestAjaxServerControl.TestAjaxControl.TestAjaxControl.callBaseMethod(this, ‘initialize’);


    // do initialization


    var updateButton = $get(this._updateButtonId);


    if (this._clickDelegate == null) {


    this._clickDelegate = Function.createDelegate(this, this._updateButton_click);

    }

    $addHandler(updateButton, ‘click’, this._clickDelegate);

    },

 

Conclusion

If you add that control to a page, it will run. Since it handles registering itself with the script manager, you don’t even need to worry about that. When you click the update button, a callback to the server control will happen that gets the current server time. All of this happens really fast – faster than an Update Panel – and uses way less bandwidth.