Display data updates in real-time with AJAX
AJAX, ASP.NET, Performance, UI By Dave Ward. Updated October 15, 2008I’ve recently noticed the real-time “spy” feature popping up on more and more sites. Though it’s often a huge time waster, I can’t help but love the feature. It’s a great example of AJAX leveraged to do what it does best. It struck me that an ASP.NET AJAX implementation would be an excellent use of page methods for efficiency and __doPostBack() to trigger an UpdatePanel refresh. So, I decided to put together a proof of concept, using the ASP.NET AJAX framework.
To create a fully functional example, several things need to be done:
- Choose a data source to “spy” on.
- Build an interface to add rows, for testing.
- Display that data in a row-based format.
- Create a method to find the most recent row of data.
- Use that method to asynchronously monitor row updates.
- Refresh stale data when additions are detected.
Choosing a data source.
Many data sources lend themselves to this sort of monitoring, such as database queries, web services, event logs, and just about any other form of ordered, row-based data. What most of them have in common is that you should try to use a caching layer between the actual data source and the polling mechanism.
So, we won’t worry about underlying data store, and use the .NET Cache directly. In real implementation, simply extend the cache update code to also save to your data layer if appropriate.
For this example, we’ll spy on a date sorted list of blog post titles. To initialize an example schema and some test data on first run, I used this code:
protected void Page_Load(object sender, EventArgs e) { // If no data has been cached yet, generate // test data for purposes of demonstration. if (Cache["Headlines"] == null) { // Create a DataTable. DataTable dt = new DataTable("Headlines"); // Add schema for the article example. dt.Columns.Add("Date", typeof(DateTime)); dt.Columns.Add("Title", typeof(string)); // Populate the test data. dt.Rows.Add(new object[] {DateTime.Now, "CSS style as AJAX progress indicator"}); dt.Rows.Add(new object[] {DateTime.Now.AddDays(-1.25), "AJAX, file downloads, and IFRAMEs"}); dt.Rows.Add(new object[] {DateTime.Now.AddDays(-2), "Easily refresh an UpdatePanel, using JavaScript" }); // Cache the initialized DataTable. Cache["Headlines"] = dt; } }
If the cache is empty, this populates it with some initial data. In your application, replace the row adds with an initial call to your data source, and cache that instead.
An interface to the data
For testing purposes, we’ll need an interface to manipulate the cache.
AddArticle.aspx:
Title: <asp:TextBox runat="server" ID="ArticleTitle" /> <asp:Button runat="server" ID="Add" Text="Add" OnClick="Add_Click" />
AddArticle.aspx.cs:
protected void Add_Click(object sender, EventArgs e) { // Retrieve the cached DataTable. DataTable dt = (DataTable)Cache["Headlines"]; // Add a row for the posted title. dt.Rows.Add(new object[] {DateTime.Now, ArticleTitle.Text}); // Re-cache the updated DataTable. Cache["Headlines"] = dt; }
This allows us to add new articles to the cached DataTable. Depending on where your data comes from, you might not need this at all. If you do control input of the data in this manner, make sure that you also save the additions to your underlying data store in this step.
Displaying the data
To complete the demo site, we need a display of the cached articles in our system.
Default.aspx:
<asp:ScriptManager runat="server" EnablePageMethods="true" /> <asp:UpdatePanel runat="server" ID="up1" OnLoad="up1_Load"> <ContentTemplate> <asp:GridView runat="server" ID="Headlines" /> <asp:HiddenField runat="server" ID="LatestDisplayTick" /> </ContentTemplate> </asp:UpdatePanel>
protected void up1_Load(object sender, EventArgs e) { // Retrieve the cached DataTable. DataTable dt = (DataTable)Cache["Headlines"]; // Set the hidden field's value to the // latest headline's "tick". LatestDisplayTick.Value = GetLatestHeadlineTick().ToString(); Headlines.DataSource = dt; Headlines.DataBind(); }
This will display a GridView table of all the article dates and titles in the cache. Not quite the aesthetics of DiggSpy, but it’ll work.
Since I plan to use a page method for polling, I went ahead and enabled them on the ScriptManager.
I also added a hidden field to the page that embeds the latest headline’s DateTime in “ticks”. Doing this will give us an easy way to make comparisons without having to worry about DateTime parsing in client script. By embedding it in the updated content, we help ensure that our data is always well in sync.
Finding the most recent row
Now, it’s time to create the GetLatestHeadlineTick() method in Default.aspx.cs, used in the preceding display code. This will also be the page method that we use on the client to poll for changes.
[WebMethod] public static long GetLatestHeadlineTick() { // Retrieve the cached DataTable. DataTable dt = (DataTable)HttpContext.Current.Cache["Headlines"]; // Sort by date and find the latest article. DataRow row = dt.Select("", "Date DESC")[0]; // Return that article's timestamp, in ticks. return ((DateTime)row["Date"]).Ticks; }
Again, I’m converting the DateTime to Ticks so that it’s an easy to compare quantity. You could just as easily use the ID from an identity field, or a non-temporal natural key. Whatever makes sense for your data. Just keep in mind that numeric sequence comparisons are easiest.
Note the explicit cache reference. That’s necessary since the page method must be a static method.
Monitoring cache data on the client
Finally, we need client script to poll the page method and respond accordingly. I added this as a ScriptReference to the ScriptManager in Default.aspx:
function Check() { // Call the static page method. PageMethods.GetLatestHeadlineTick(OnSucceeded, OnFailed); } function OnSucceeded(result, userContext, methodName) { // Parse the page method's result and the embedded // hidden value to integers for comparison. var LatestTick = parseInt(result); var LatestDisplayTick = parseInt($get('LatestDisplayTick').value); // If the page method's return value is larger than // the embedded latest display tick, refresh the panel. if (LatestTick > LatestDisplayTick) __doPostBack('UpdatePanel1', ''); // Else, check again in five seconds. else setTimeout("Check()", 5000); } // Stub to make the page method call happy. function OnFailed(error, userContext, methodName) {} function pageLoad() { // On initial load and partial postbacks, // check for newer articles in five seconds. setTimeout("Check()", 5000); }
In a nutshell, this checks GetLatestHeadlineTick() every 5 seconds, compares it to the embedded LatestTick in the UpdatePanel, and triggers an UpdatePanel refresh if the UpdatePanel’s data has become stale.
If you open Default.aspx in one window and AddArticle.aspx in another, adding an article title from AddArticle will cause Default.aspx’s GridView to automatically refresh itself. If no updates are added, no partial postbacks occur.
This could be accomplished by using a Timer control to constantly refresh the UpdatePanel. However, by polling a light-weight page method, we can afford to poll for updates much more frequently and/or support many more concurrent users than if we were triggering a full partial postback on every polling. There are orders of magnitude between the performance of UpdatePanels and page methods.
Room for improvement (always)
I think this example is fairly compelling, but there’s always room for improvement.
A pause/resume feature, like the one on DiggSpy would be easy to implement and definitely be handy. That could be implemented with two or three lines of client script.
You should also add error handing in OnFailed(). Ideally, this should have some more robust error handling. Maybe retrying with a longer interval, if the request timed out, assuming an overloaded server. Or, perhaps keeping a counter of how many failures in a row have occurred and displaying a user friendly message about the situation after a certain threshold.
I used a GridView for brevity, but I think it would be more ideal to use a repeater to output divs. It would be lighter weight than the GridView and lend itself to dynamic updating via DOM manipulation.
That DOM manipulation would be another nice improvement. Rather than running a full partial postback to refresh the display, another page method could be created that returns a JSON object with articles more recent than a given tick. Then, those new article divs could be added to the display less abruptly, via fade, slide, or some other client side pizazz.
It’s surprisingly fun to play with and watch in action. Give it a try yourself:
Similar posts
What do you think?
I appreciate all of your comments, but please try to stay on topic. If you have a question unrelated to this post, I recommend posting on the ASP.NET forums or Stack Overflow instead.
If you're replying to another comment, use the threading feature by clicking "Reply to this comment" before submitting your own.
6 Mentions Elsewhere
- 20 Excellent Websites for Learning Ajax - Six Revisions
- Websites for Learning Ajax | CSS Experiments
- AJAX links « bnotezz
- Sıfırdan AJAX öğrenebileceğiniz 20 kaynak
- Display data updates in real-time | Huuich.biz
- 20 Excellent Websites for Learning Ajax | graftek.net





You’re right, that is fun! Good stuff.
Although I don’t know if this will work in firefox. ASP AJAX timers and firefox are not friends.
ie, this issue, which I was only able to resolve by adding
if (browsertype == “Firefox”)
{
Response.Cache.SetNoStore();
}
more here: http://forums.asp.net/p/976239/1472501.aspx#1472501
Since I’m not using an ASP.NET AJAX Timer control, that’s not a problem here (I test in FireFox by default).
That’s good to know about FireFox and the Timer control though. Thanks for the info.
Nice method, i’m using it in a real time stats monitor.
you don’t know of a php example of this by any chance?
You might take a look at the source for Pligg and see if it has the spy functionality that Digg does.
Yes anyone know php version?
I’ve been trying to get this to work for about an hour now but haven’t had any luck. When the page loads it grabs the cache like it should and after 5 seconds it hits the web method, but when the web method is done I keep getting:
$get(“LatestDisplayTick”) has no properties /Files/scripts/spy.js
Line 12
at this line:
var LatestDisplayTick = parseInt($get(‘LatestDisplayTick’).value);
After I get that error the code seems to stop and the web method is never hit again.
The only thing that’s different in my environment is that my script manager is in a master page.
Any advice?
What’s happening is that the master page is changing the ClientID of that control. Do a view source on your page, and you’ll probably see that it’s actually ctl00_something_somethingelse_LatestDisplayTick.
Probably the best thing to do is put a global variable assignment on the ASPX page somewhere, like:
Then, change line 12 to use that:
Thanks for your help. It seems to give me a different error now:
ctl00_ContentPlaceHolder1_LatestDisplayTick is not defined
Line 187
The output of the global variable is:
var LatestDisplayTickID = ctl00_ContentPlaceHolder1_LatestDisplayTick;
to give you some more info this is what I did:
1. Added a script reference in the script manager to spy.js (add set EnablePageMethods to true)
2. Change the reference in spy. to my update panel
3. Add the following code inside the update panel where I have the gridview and hidden field (I’ve also tried putting it outside the update pnl):
var LatestDisplayTickID = ;
and made sure spy.js uses LatestDisplayTickID
Sorry, I left out some quotes in my comment. Change the ASPX’s line to this:
It should work then.
dang, I should have noticed that myself ;) Everything seems to work now. It seems to do a full page refresh and not have the “AJAX” effect when it finds a new item for the gridview, though. Not that big of a deal.
Thanks a lot for the help.
__doPostBack(‘TagCloudUpdatePanel’, ”) is causing a full refresh because of the same issue. TagCloudUpdatePanel isn’t actually the ClientID of that UpdatePanel since it’s in the master page.
You can use a similar global variable setup with the ClientID of TagCloudUpdatePanel to make it correctly work asynchronously.
Beautifull :) If you’re interested (Since I saw that you know about Pligg in one of your recent comments) I’m actually converting Pligg to ASP.NET. You can find the project at Srouceforge http://sourceforge.net/projects/pliggdotnet
Sorry to keep bothering you but I have a quick question about web methods. Is there a way to get a query string value in a web method?
I’ve tried using System.Web.HttpContext.Current.Request.Params. If not, is there a way for me to set a control (label, hidden field etc) on the aspx page that I can get the value of when the web method runs?
I need to figure out a way to find out what data I need to get within the web method.
You won’t be able to access it, because it isn’t part of the page method request. Page methods can accept parameters though. You can pass query string parameters to it that way.
location.href.split(‘?’)[1] will give you the entire query string in JavaScript to parse out what you need, or pass the whole thing to your page method.
If anyone is interested in how to get your latest items to appear at the top of the list instead of the bottom you can use a dataview in the UpdatePanel load routine. Here is my code below:
Protected Sub LivePageUpdatePanel_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles LivePageUpdatePanel.Load
Dim DT As DataTable = DirectCast(Cache(“Headlines”), DataTable)
LatestDisplayTick.Value = GetLatestHeadlineTick().ToString()
Dim DV As DataView
DV = New DataView(DT)
DV.Sort = “Date DESC”
LiveItems.DataSource = DV
LiveItems.DataBind()
End Sub
David, thanks for the useful article.
I have one question: why do you store the current data “tick” on the client and send it back to the server, while a session is more or less designed for this purpose? This enables you to let the server compare DateTime structures directly.
You can access the Session just like you access the Cache, so that is not a problem.
Also, this avoids a little more data transfer: 8 bytes per poll to be specific. This may be significant for some sites with a high poll frequency.
You’re right. That would be a much cleaner way to do it.
Then, there wouldn’t need to be a date comparison on the client side. The page method could just be something like IsGridCurrent and return a boolean that directed the polling method to __doPostBack.
Nice catch. Thanks for the comment!
Hi Johan,
Good Idea,
But what if we need addarticle from server and it appears in everwhere
where this default page is running???
because session only works that browser??
waiting for your kind reply??
regards,
samir
Is possible tu use the same with ASP not .NET.
Thanks
It’s possible, but it would require a completely different approach (other than using setTimeout on the client side). I don’t know that this example would be any help in doing that, other than the general methodology.
if this doesn’t work….
try this
then in the page initialization call Page.DataBind
I have gotten this to work in my web site but every time the up1_Load method fires and completes the whole page reloads! Kinda defeats the point. As soon as it completes the Page_Load method is fired automatically, im not calling it.
Page_Load will fire during a partial postback. That’s to be expected. In my example, I didn’t need to check IsPostBack, because checking the Cache had the same effect. In your code, you may need to check IsPostBack to only execute some of your Page_Load on initial page load.
If you’re seeing a full page reload, that generally indicates either a problem with your ASP.NET AJAX install/configuration, or the __doPostBack() call not being supplied the correct ClientID for your UpdatePanel.
Ok, i included a IsPostBack if statement at the start of my Page_load which worked perfectly, but after moving from that Page_Load it goes straight to the Page_Load of my master page?
Anything that you would normally expect to happen during a postback will still happen during a partial postback. The entire page life cycle is executed.
Doh, I’m not sure what’s actually happening on the master page load event, didn’t write any of this code. Will have to speak to the big man and see how to stop it from doing a full postback.
PS: Thanks for the help.
Would it be possible to directly call a C# method/function from my javascript?
if (LatestTick > LatestDisplayTick)]
///Execute my C# method on the page
else
setTimeout(“Check()”, 1000);
Does the method have to be a web service?
You could use a page method call there, instead of the __doPostBack().
I have implemented this example successfully, but have noticed one problem.
My GetLatestHeadlineTick method runs every 1.5 seconds. It only seems to register the new tick value after the second time around, IE: The data has changed and the tick has increased but the Webmethod executes a second time before it recognises the change. Odd though because when tracing through it I can see the values are actually changing.
I am spying on a database table.
I’m new to ASP.NET development so please forgive me — I tried creating a new project in VS2005, added your files, and tried to run, however it’s saying several variables are undefined in my .cs file (LatestDisplayTick, Headlines, and ArticleTitle). Why doesn’t it recognize these in the default.aspx file?
Thanks!
Great Article! This really helped in a project i was doing for my work. They only problem is, we use it for a demo on a kiosk. It works great for about 3 hours and then it just kind of dies and won’t update the page with new info added to the cache from AddArticle unless i refresh the page. Any idea how i can keep the page from dying? Thanks!!
Are you seeing any JavaScript errors when it stops updating? Are multiple clients connected? If so, do they all stop updating at the same time?
Thanks for the reply Dave! Yes, multiple clients are connected (3 kiosks) and no, no javascript errors pop up at all. From what i can tell, they do stop updating right around the same time (about 3 hours). Let me know what other questions i could answer for you. Thanks!
If they’re all stopping at the same time, it is probably something causing the Cache to expire. Maybe memory pressure (which automatically empties the Cache) or an app pool reset for some reason.
For a long-polling implementation like that, I would definitely suggest using a more persistent data store, like a SQL Server Express database maybe.
Thanks Dave,
I’ll give that a try! I really appreciate the response
Hello,
Thanks for this great post.
I’d like to do a similar thing but add animation when adding new items (maybe fade in effect or fly-in or another animated effect).
Can you please explain how I can do that?
Thanks a lot!
Dana
Unfortunately, you can’t do that with this technique. The UpdatePanel doesn’t allow for anything that granular.
To accomplish that, you’d want to use something like a web service returning JSON and rendering each individual line on the client-side instead of server-side.
Hi Dave,
Thanks for an excellent post.
I was just wondering, would it be possible to use SqlCacheDependency to invalidate the cache and then use a PageMethod to fire a postback if the cache was invalid?
I have it so that when the cache is up to date, the page method fires fine and just waits another 5 seconds before trying again, but when I invalidate the cache, the OnFail method gets called (with methodName=”myWebMethod” and userContext=null), do you have any idea as to why this may be?
Thanks
David
I’d suggest setting a breakpoint inside the page method and debugging. It could be a few things, but inspecting it at runtime will be the easiest way to figure it out.
Thanks, a pointer in the right direction was all I needed. Problem solved. Cheers!
very good article.
I ran into a problem because the page is protected by an authorization section in the Web.config.
When it tries to invoke the page method a logon screen appears (I am using windows authentication), and it does not accept any id I enter.
Would moving the code for the webmethod to a separate code file in a subdirectory that allows all users work? or is it even possible.
Regards
Yes, that would work. The authentication for ASMX services is URL based, same as for ASPX pages.
I’d suggest setting a breakpoint inside the page method and debugging. It could be a few things, but inspecting it at runtime will be the easiest way to figure it out.
nice article, i tried to run this code and it works.!
Hey~
This method may bring a lot of time-out connections in active connections. Through my test, it will product 129 conntections per 1 second.
Hi,
it’s greate article,
but it postback after every 5 sec.
i want like it only postback when we insert new article.
plz help
thanxxx
samir
Since the server cannot “push” an unsolicited message to the browser, the only feasible way to know when a new item is inserted is to poll for it.
You can somewhat reverse that dynamic by using an approach called comet, where the client holds a connection open to the server and the server pushes messages down. Unfortunately, holding all those connections open indefinitely and simultaneously usually turns out to have more impact on the server than efficient polling.
In the future, HTML5′s WebSockets will allow true bi-directional communication, but they aren’t widely supported enough to rely on yet.
Thanxxx for your kind reply,
But i’m using mysql and comet is using sqldependency which is not
in mysql. In more Comet also refreshed check database at some specified interval.
is there any other way for this??
thanxxx,
samir
I’d suggest staying with polling if the more advanced approaches won’t work in your situation. Even high-traffic sites like Twitter, Stack Overflow, and Facebook use polling for their “real-time” features like chat and asynchronous notification of updates.
It feels wrong to spam the server with requests, but it’s not so bad as long as the requests are light-weight. If you’re empirically finding that this approach isn’t efficient enough, look into using a web service for the polling endpoint. They are much, much more efficient than UpdatePanel refreshes.
Thanxx again for your kind reply,
But i m not much aware with this polling.
Can You pls give some refrence or source of this polling??
i m using asp.net c# and mysql.
thanxx,
samir
Polling is what this post is an example of.
But in this example it already postback on each 5 sec.
and load whole page again.
as a result it using too much bandwidth
e.g. if page size is 100kb then it loads 100kb each 5 sec.
thanxxx,
samir
That isn’t how this post’s example works at all. Take a closer look at the article and be sure to download the source and watch it in something like Firebug or Fiddler to see what traffic is actually generated.
thanxx for your reply.
Actually i using Ajax Timer for real-time it’s working great but it using too much bandwidth with requesting page on each timer tick.
i checked your code and check it in web statics and find that it also make page request on each 5 sec. I haven’t Check it for bandwidth but don’t u think if page request is made then it also use same bandwidth like ajax timer???
Is there any way that we can monitor bandwidh at our local computer while running our website locally??
thanxx,
samir
Static “Page Methods” are shorthand for ASMX ScriptServices. They don’t have any of the server-load or bandwidth overhead that a full page request does.
Ok i found the problem.
it loads the parts in Update panel which is of 3KB.
But it loads in each 5 sec. and my website is constantly running.
for ex. if 20 user keep my website Open then it constaly use the bandwidth.
Can u Plz tell me that it also be happened if i use your script??
if yes then can u plz tell the other way to overcome this problem???
thanxxx,
samir
Like I said before, using the Page Method is much lighter weight. It’s nothing like using a Timer to poll at all.
You should download the source example from this article and test it the same way that you’re testing the Timer approach.
Hi,,
It’s really great i haven’t seen your getlatest idea its really good.
i will obviously try it live.
thanxxxx
samir
Dear Dave,
It’s really great but it’s not updating data after more then half-n-hour
may be caching problem or what??
waiting for your kind reply..
thanxx,
samir
Thanks Dave,
it works very well, I made some change an looks very nice.
Tx again
I tried to implement using the above code, everything is working fine. But, the page is refreshed each time a new article is added or the grid has to be updated. Help please.
Hi Dave,
thanks for the article, it helped me a lot.
Just one note, your example won’t work properly because you set the updatepanel id to “up1″ and then call the postback as “__doPostBack(‘UpdatePanel1′, ”);”
This way it will cause a full postback, the postback line must be:
__doPostBack(‘up1′, ”);
Thanks