We often need/want to implement web applications with groving interaction.
Such application most of the time need continous polling to the server to retrieve updates.
But how often should the client poll the server?
Short polling times could create too many connections to the server if the number of concurrent users grow.
Long polling times could transform our interactive web application in a not-so-interactive one.
So, here is what I mean for "smart polling".
We can implement the client in such a way it will poll the server, wait for a response and perform a new poll as soon as a response is received.
This is nothing new.
On the server side, we can let the server keep the pending connection as long as possible untile there is nothing new to communicate to the client.
This way we limit the number of polls, while keeping interative responsiveness of the application.
Here we go with a simple chat example.
This is our aspx page.
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="Chat_Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager runat="server">
<Services>
<asp:ServiceReference Path="~/Chat/ChatWebService.asmx" />
</Services>
</asp:ScriptManager>
<div>
<asp:UpdatePanel runat="server" ID="ChatUpdatePanel">
<ContentTemplate>
<asp:ListBox runat="server" ID="ChatHistoryList" Rows="10" Width="100%" />
</ContentTemplate>
</asp:UpdatePanel>
<br />
<input id="Message" /><input type="button" value="Send" onclick="send()" />
</div>
</form>
</body>
</html>
Here we are using a simple listbox to expose the chat history. It is incapsuladed in an UpdatePanel because it will be updated asynchronously on the server-side. The listbox could be updated client-side in this example, but updating it in an UpdatePanel could be useful if the application is growing in complexity and functionalities it exposes.
Let's add the javascript code for submitting the message and to poll the server for incoming messages:
<script type="text/javascript">
function poll() {
var count = <%= ChatHistoryList.Items.Count %>;
ChatWebService.PollForNewMessages(count, successCallback, failCallback);
}
function successCallback(result) {
if(result == true)
__doPostBack('ChatHistoryList', '');
poll();
}
function failCallback() {
poll();
}
function send() {
var message = $get('Message').value;
$get('Message').value = "";
ChatWebService.SendMessage(message);
}
window.onload = poll;
</script>
The poll() function is first invoked on page load. It will then be re-invoked within the success and fail callbacks of the webservice's generated proxy. An asynchronous postback is invoked to refresh the UpdatePanel only if the webservice returns a true value, indicating some new message has arrived.
Here is the webservice code:
/// <summary>
/// Summary description for WebService
/// </summary>
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.Web.Script.Services.ScriptService]
public class ChatWebService : System.Web.Services.WebService
{
/// <summary>
/// A simple list of string to store the chat history in a static member
/// </summary>
public static List<string> ChatHistory = new List<string>();
/// <summary>
/// A configurable timeout (milliseconds)
/// </summary>
private const int TIMEOUT = 120000;
/// <summary>
/// Waits for new incoming messages
/// </summary>
/// <param name="count">Current count of messages on the calling client</param>
/// <returns>True if some new message arrived, False if timeout was reached</returns>
[WebMethod]
public bool PollForNewMessages(int count) {
DateTime start = DateTime.Now;
while ((DateTime.Now - start).TotalMilliseconds < TIMEOUT)
{
if (ChatHistory.Count > count)
{
return true;
}
System.Threading.Thread.Sleep(500);
}
return false;
}
/// <summary>
/// Add a new message to the chat history
/// </summary>
/// <param name="message">Message to be added</param>
[WebMethod]
public void SendMessage(string message)
{
lock (ChatHistory)
{
ChatHistory.Add(message);
}
}
}
So, here we have a client polling for new incoming messages. Poll is performend against a webservice. The PollForNewMessages() method compares the number of messages the calling client refers to have in its list with the updated number of messages in the static ChatHistory member. It will sleep and wait until some new messag has arrived, or the configured timeout is reached.
On the client, an asynchronous postback is invoked only if the webservice return a true value.
Finally, this codebehind for our aspx page will simply re-bind the ListBox with the static ChatHistory list:
protected void Page_Load(object sender, EventArgs e)
{
ChatHistoryList.DataSource = ChatWebService.ChatHistory;
ChatHistoryList.DataBind();
}
Hope you will find this idea useful in your projects.
Comments and feedbacks are welcome!