A high performance web part in 10 lines of code using ICallbackEventHandler

Ok, maybe a few more lines of code than that, but not much more.  To set the stage here – don’t expect a deep review of the myriad of ways to create asynchronous web parts. However, the sample code offered will provide a valid option for the creation of a high performance web part with the minimum of code.

This started because I was recently asked to provide advice for a webpart that would not impact page load time in MOSS when calling out to slow loading external data. Obviously AJAX and Silverlight come to mind but the AJAX library was not available and Silverlight was still in the early adopter stage. Another option included using the PageAsyncTask class as outlined in Andrew Connells very good book “SharePoint 2007 Web Content Management Development”. The code he presents is good but I still wanted a cleaner approach because I was returning a simple string from a SOAP service (although it could contain other serialized data).

The approach I finally took was to leverage an older .NET interface called ICallbackEventHandler with a newer technology for client side scripting called JQuery. I talked about JQuery a little in a previous post. The result was a incredibly concise webpart that did not noticeably impact page load time.  My sample is partly based off a Microsoft article I came across and I am certainly pleased with the result.     

For the purpose of this post a high performance web part really means any webpart that does not substantially impact page load time when calling out to external data. In the “real” world external data could be accessed via some web service with substantial delays. Under these circumstances you don’t want your page load time impacted in anyway.  High performance web part’s also must minimize render time once the data is returned from the source. Render time will not be a consideration in this post.         

Lets start with a some code that simulates a simple web service having a 15 second delay. The code I used was this:

[WebMethod]
public string HelloWorld()
  {
      // web service that returns a string after 15 seconds have elapsed
      System.Threading.Thread.Sleep(new TimeSpan(0, 0, 0, 15));

      // return string
      return "Hello World";
  }

In Visual Studio just create a web services project and add the above code. The method is setup to return a string approximately 15 seconds after the web part makes a call. I think you’ll agree that’s pretty slow. The next thing we do is construct an asynchronous web part that calls out to this service but minimizes the impact on page load time.

To begin, lets look at the bones of a simple web part. The code appears as follows:

public class CallBackWebPart : System.Web.UI.WebControls.WebParts.WebPart  { 

protected override void CreateChildControls() {

           base.CreateChildControls();

           Controls.Add( {some control} );
}

}

What asp.net controls get displayed are setup in CreateChildControls() using one or more Controls.Add() calls, as shown. Any number of controls can be added to the webpart this way.

Next, to begin making this web part asynchronous we add the ICallbackEventHandler interface and implement the RaiseCallbackEvent and GetCallbackResult methods required of the interface. So now our code looks as follows:

public class CallBackWebPart : System.Web.UI.WebControls.WebParts.WebPart, System.Web.UI.ICallbackEventHandler   { 

protected override void CreateChildControls() {

           base.CreateChildControls();

           Controls.Add( {some control} );
}

public string GetCallbackResult() {
            
}

public void RaiseCallbackEvent(string eventArgument) {

}

}

Because the ICallbackEventHandler communicates back to the client some additional client side scripts must be defined. Also, because these scripts need to be created during page load we also need to override the OnLoad method as follows:

public class CallBackWebPart : System.Web.UI.WebControls.WebParts.WebPart, System.Web.UI.ICallbackEventHandler   { 

protected override void OnLoad(EventArgs e) {

}

protected override void CreateChildControls() {

           base.CreateChildControls();

           Controls.Add( {some control} );
}

public string GetCallbackResult() {
            
}

public void RaiseCallbackEvent(string eventArgument) {

}

}

Lastly, the actual client side scripts need to be added. Lets start by defining exactly our aim here. We need some set of client side methods that connect to the ICallbackEventHandler. We make these “connections” within the OnLoad method above using the client script manager that comes with asp.net pages. So we define the code as follows

ClientScriptManager clientmanager = Page.ClientScript;

// callback function
string getData = clientmanager.GetCallbackEventReference(this, "arg","GetDataFromServer", "");

where GetDataFromServer is the javascript function called after receiving an event that the data has arrived (from our slow connection).

We also need to define some way to start the entire process off. Now this can happen in two ways, first as a result of a “click event” initiated by a user working on the page (say from a button) or during a page load (this is our case). In both instances we need to call some client side javascript function. For this sample project we define are function called CallServer(). This is added to the page using the following code

// call callback wrapper
clientmanager.RegisterClientScriptBlock(this.GetType(),"CallServer", _callServerJS, true);

Where _callServerJS is the actual javascript code implementing the client side call. I also implemented a call out to the server on page load. This is where JQuery comes into action. I register my startup script using JQuery as follows

// call startup procedure
clientmanager.RegisterStartupScript(this.GetType(), "StartUp", _startScript,true);

Where _startScript is the javascript function that executes when the page load is complete. The actual code is as follows:

$(document).ready(function() {
          CallServer(); // call the web service on the server
});";

As you can see almost no part of this sample is complex. A couple more pieces of housekeeping and we are done. As you recall we had to implement RaiseCallbackEvent and GetCallbackResult. RaiseCallbackevent is actually where you place the service call and GetCallbackResults is where the data is returned. The actual code for these are:

public string GetCallbackResult()  {  return this._asyncData; }

public void RaiseCallbackEvent(string eventArgument)  {
     localhost.Service1 service = new global::CallBackWebPart.localhost.Service1();
     this._asyncData = service.HelloWorld(); 
}

See below for the final bits. In any event not much is left. So now when I visit the page it loads fast and is fully available….and after 15 seconds I see an alert with the returned data. Very cool indeed!

 

image

Of course, a little more wiring up needs to happen. 

Please keep in mind this is sample code but does work extremely well when a simple solution is desired. Here are the last few bits.

namespace CallBackWebPart {

    public class CallBackWebPart : WebPart, System.Web.UI.ICallbackEventHandler
    {

        // store server returned data here
        string _asyncData = default(string);

        string _getDataJS = @"
                   function GetDataFromServer(arg, context) {
                       alert(‘String data from server: ‘ + arg);
                   }";

        string _startScript = @"
                  $(document).ready(function() {
                      CallServer();
                   });";

        protected override void OnLoad(EventArgs e)
        {
            // instantiate page script manager
            ClientScriptManager clientmanager = Page.ClientScript;

            clientmanager.RegisterClientScriptInclude("JQuery", "~/_layouts/JQUERY/jquery.js");
            clientmanager.RegisterClientScriptBlock(this.GetType(), "GetDataFromServer", _getDataJS, true);

            // callback function
            string getData = clientmanager.GetCallbackEventReference(this, "arg","GetDataFromServer", "");

            string _callServerJS = "function CallServer(arg, context) {" + getData + "; }";
            // call callback wrapper
            clientmanager.RegisterClientScriptBlock(this.GetType(),"CallServer", _callServerJS, true);

            // call startup procedure
            clientmanager.RegisterStartupScript(this.GetType(), "StartUp", _startScript,true); 
        }

        protected override void CreateChildControls() {

            base.CreateChildControls();
            Label lblHello = new Label();
            lblHello.Text = "Click Me";
            lblHello.Attributes.Add("onclick","CallServer()");
            Controls.Add(lblHello);
        }

        public string GetCallbackResult()
        {
            return this._asyncData;
        }

        public void RaiseCallbackEvent(string eventArgument)
        {
            localhost.Service1 service = new global::CallBackWebPart.localhost.Service1();
            this._asyncData = service.HelloWorld(); 
        }

  }
}

Some other considerations will need to be taken into account such as error handling, timeouts, CAS – certainly, a production worthy web part will need to include those features. Here are the related references

Interface Specifics – http://msdn.microsoft.com/en-us/library/system.web.ui.icallbackeventhandler.aspx

Sample MS Code – http://msdn.microsoft.com/en-us/library/ms178208.aspx

JQuery – http://jquery.com/

I really enjoyed this little sample and I think you will as well – have fun

Advertisements

2 thoughts on “A high performance web part in 10 lines of code using ICallbackEventHandler

    • window.load waits for a period of time, true – but that time is based on how long the host server takes to respond. This web part waits for some external data from a third-party (not always related to the web server) but then lets the rest of the page load without delay. Only when the data arrives from the third party does the web part load. Does not stop any other part of the page from loading…that was the idea

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s