Creating an asynchronous webpart

I have rescued this text using the Wayback Machine at archive.org as the original blog was removed.
http://web.archive.org/web/20120702152958/http://blog.idevteam.nl/2011/02/creating-asynchronous-webpart.html

Working on a webpart that accesses an external webservice, the page response time became really slow. To solve this I had to implement an asynchronous webpart. Luckily my colleague found a blog post that showed an easy way to implement this.

Blog post: http://blogs.msdn.com/b/pavankumar/archive/2009/02/16/code-sample-for-a-asynchronous-webpart.aspx

As I prefer jQuery, I converted the javascript to jQuery and it worked like a charm. This evening I thought, wouldn't it be nice to have a reusable piece of code to provide this functionality and to make it even easier to implement an asynchronous webpart. So I sat down and created the following piece of code based on the original.

[ToolboxItemAttribute(false)]
public abstract class AsyncWebPartBase : WebPart, ICallbackEventHandler {

private string _id;
private string _asyncContent;
private string _waitImageUrl = "/_layouts/IMAGES/gears_anv4.gif"; //Default wait image...

public string WaitImageUrl {
	get {
		return _waitImageUrl;
	}
	set {
		_waitImageUrl = value;
	}
}

protected override void OnLoad(EventArgs e) {
	base.OnLoad(e);
	_id = this.ID + "_itemsdata";
	string js = Page.ClientScript.GetCallbackEventReference(this, "'getcontent'", "fill" + _id, _id, true);
	string contentloader = "function fill" + _id + "(arg, ctx) { var content = $('#" + _id + "'); content.html(arg); }";
	if (Page.ClientScript.IsClientScriptBlockRegistered("contentloader" + _id) == false)
		Page.ClientScript.RegisterClientScriptBlock(Page.ClientScript.GetType(), "contentloader" + _id, contentloader, true);
	Page.ClientScript.RegisterStartupScript(this.GetType(), "myloader" + _id, "$(document).ready(function() { " + js + " });", true);
}

protected sealed override void Render(HtmlTextWriter writer) {
	if (!string.IsNullOrEmpty(this.CssClass))
		writer.AddAttribute(HtmlTextWriterAttribute.Class, this.CssClass);
	writer.AddAttribute(HtmlTextWriterAttribute.Id, _id);
	writer.RenderBeginTag(HtmlTextWriterTag.Div);
	writer.AddAttribute(HtmlTextWriterAttribute.Alt, "Loading...");
	writer.AddAttribute(HtmlTextWriterAttribute.Src, _waitImageUrl);
	writer.RenderBeginTag(HtmlTextWriterTag.Img);
	writer.RenderEndTag();
	writer.RenderEndTag();
}

protected abstract void RunProcesses();

protected abstract void RenderAsync(HtmlTextWriter writer);

public void RaiseCallbackEvent(string eventArgument) {
	switch (eventArgument) {
		case "getcontent":
			using (StringWriter stringWriter = new StringWriter())
			using (HtmlTextWriter writer = new HtmlTextWriter(stringWriter)) {
				EnsureChildControls();
				RunProcesses();
				RenderAsync(writer);
				_asyncContent = stringWriter.ToString();
			}
			break;
	}
}

public string GetCallbackResult() {
	return _asyncContent;
}

}

I also added two webparts that implemented this base class. This is the code of one of these webparts:

[ToolboxItemAttribute(false)]
public class SampleOne : AsyncWebPartBase {

Label _lblOne;

protected override void CreateChildControls() {
	this.CssClass = "SampleOneWebPart";
	_lblOne = new Label();
	_lblOne.Text = "Async Content Goes Here...";
	this.Controls.Add(_lblOne);
}

protected override void RunProcesses() {
	System.Threading.Thread.Sleep(4000);
}

protected override void RenderAsync(HtmlTextWriter writer) {
	writer.AddAttribute(HtmlTextWriterAttribute.Class, "SampleOne");
	writer.RenderBeginTag(HtmlTextWriterTag.Span);
	_lblOne.RenderControl(writer);
	writer.RenderEndTag();
}

}

As you can see, creating an asynchronous webpart becomes much easier like this. Things to note are: you have to add a link to jquery in you (master)page and this code will not work in a sandboxed solution (because of the javascript).

I have only created two samples to test different webparts on the same page, so if you use this, test the code for your usage. But as you hopefully know, always test code you find online. Do honor the original poster, and visit his blog post.

You can download the sample solution here.

Added content: Conversion to SharePoint 2013

As I am now thinking about using a async webpart in SharePoint 2013, I have updated the base webpart code. For this I have decided to use standard JavaScript, so no jQuery is needed in the masterpage, this eliminates the dependency.

I did however had some difficulty getting things to work. After some time and frustration I found the following answer. As I was running SharePoint 2013 with the March 2013 public CU, I was experiencing this validation problem. I then installed the August 2013 CU and the webpart started working like a charm.

Most notable changes:

private string _waitImageUrl = "/_layouts/15/IMAGES/gears_anv4.gif"; //Default wait image...

protected override void OnLoad(EventArgs e) {
    base.OnLoad(e);
    _id = this.ID + "_itemsdata";
    string js = Page.ClientScript.GetCallbackEventReference(this, "'getcontent'", "fill" + _id, _id, true);
    string contentloader = "function fill" + _id + "(arg, ctx) { var content = document.getElementById('" + _id + "'); content.innerHTML = arg; }";
    if (SPPageContentManager.IsClientScriptBlockRegistered(Page, this.GetType(), "contentloader" + _id) == false)
        SPPageContentManager.RegisterClientScriptBlock(Page, this.GetType(), "contentloader" + _id, contentloader);
    SPPageContentManager.RegisterStartupScript(Page, this.GetType(), "myloader" + _id, "_spBodyOnLoadFunctions.push(function() {" + js + "});");
}

You can download the SP2013 sample solution here.