Click here to Skip to main content
Click here to Skip to main content

Using the XMLHTTP object to synchronise pages in ASP.NET

, 17 Feb 2008
Rate this:
Please Sign up or sign in to vote.
How to use the XMLHTTP object to synchronise pages in ASP.NET.

xmlhttpsync.jpg

Introduction

Imagine the scenario where you have multiple users viewing a page in your web application. You want to have each client page updated automatically when there are changes to the data the page is displaying.

Because of the request/response nature of web applications, we can not simply get our application to communicate directly to clients that are viewing our page and tell them to update their content as with a traditional client/server application. This article explores a way to synchronise your user's views using the XMLHTTP handler.

Why not use META HTTP-EQUIV="Refresh"?

If we use the META tag in the header of our ASP.NET page, we could get a page to reload every few seconds, showing updated content since the last reload. So, what is wrong with doing this?

In a situation where we have a 100 users viewing a page with our META tag set to reload the page every 5 seconds, each page would generate a request for an ASP.NET page, which in turn might be querying a database. That is a lot of page requests, doing a lot of work, which is mainly unnecessary seeing that the data is unlikely to be changing every 5 seconds.

We need a way for a client to ask our application if it is necessary to update its content ... We can do this using the XMLHTTP object.

Components of the demo web app

There are four main components of the demonstration web application which are used to synchronise the ASP.NET page in our demo web app.

  • DemoWebAppCore.cs
  • A controller class representing the bowels of the application (our business layer, if you like ...).

  • Default.aspx
  • Our ASP.NET page displaying data we want synchronized across multiple client sessions. This contains a GridView control displaying products with functions to add, edit, and remove products.

  • CheckSync.js
  • JavaScript code using the XMLHTTP object to perform 'behind the scenes' requests to CheckSync.ashx. This is included in the Default.aspx page.

  • CheckSync.ashx
  • An HTTP handler with a simple job of responding to an XMLHTTP request, with a flag determining if a page should update its content.

Synchronization using an incrementing number

To achieve our page synchronization, we are going to keep two references. One reference is going to be kept with the client (in the session state), and the other is going to be kept with the server (in the application state).

When any action takes place such as adding, editing, or deleting, the server will increment its reference. The client will regularly check its reference against the server reference. If the server reference is higher, the client will synchronize its content and copy the server reference to its own. This process repeats until the client is no longer viewing the data.

I have built a set of classes to accommodate simple synchronization in the demo.

syncclasses.jpg

  • ViewSynchronisation
  • Responsible for creating a server reference and ensuring that only one copy exists in the application.

  • ServerSynchronisationReference
  • Holds a server synchronization reference.

  • ClientSynchronisationReference
  • Holds a client synchronization reference.

The client synchronization process

The process of the client checking its reference against the server is detailed below.

On application startup

  1. DemoWebAppCore is instantiated in the Application_Start() method of Global.asax.
  2. protected void Application_Start(object sender, EventArgs e)
    {
        ...
        Application["Engine"] = new DemoWebAppCore();
        ...
    }
  3. DemoWebAppCore.ProductViewSync is instantiated.
  4. public class DemoWebAppCore
    {
        ...
        private ViewSynchronisation _productViewSync = 
                new ViewSynchronisation("Products");
        public ViewSynchronisation ProductViewSync
              { get { return _productViewSync; } }
        ...
    }

On every client connection

  1. Session["ClientSyncRef"] is instantiated with a ClientSynchronisationReference using DemoWebAppCore.ProductViewSync.CreateClientReference() in the Session_Start() method of Global.asax.
  2. protected void Session_Start(object sender, EventArgs e)
    {
        Session["ClientSyncRef"] = 
           Engine.ProductViewSync.CreateClientReference();
    }
  3. Default.aspx loads and displays its content.
  4. The JavaScript in CheckSync.js runs and generates an XMLHTTP request for SyncCheck.ashx.
  5. var pollInterval = 5000;
    var checkStatusUrl = "CheckSync.ashx";
    var pollID = window.setInterval(checkStatus, pollInterval);
    
    function checkStatus() 
    {
        // create XMLHTTP object
        req = createReq();
    
        if(req != null)
        {
            req.onreadystatechange = process;
            req.open("GET", checkStatusUrl, true);
            req.send(null);
        }
        else
            window.removeInterval(pollID);
    }
    
    function createReq()
    {
        // Create XMLHTTP compatible in various browsers
        try
        {
             req = new ActiveXObject("Msxml2.XMLHTTP");
        }
        catch(e)
        {
             try
             {
                  req = new ActiveXObject("Microsoft.XMLHTTP");
             }
             catch(oc)
             {
                  req = null;
             }
        }
    
        if (!req && typeof XMLHttpRequest != "undefined")
        {
            req = new XMLHttpRequest();
        }
        
        return req;
    }
  6. CheckSync.ashx checks if the ClientSynchronisationReference in Session["ClientSyncRef"] is invalid using ClientSynchronisationReference.IsInvalid. If it is, CheckSync.ashx returns the character "1" in its response. Otherwise, it will return "0".
  7. public void ProcessRequest(HttpContext context)
    {
        // Make sure that the response of this
        // handler is not cached by the browser
        context.Response.Cache.SetCacheability(HttpCacheability.NoCache);
        context.Response.ContentType = "text/plain";
    
        if (ClientSyncRef(context) != null)
        {
            if (ClientSyncRef(context).IsInvalid)
            {
                // Client ref was invalid so ...
                // return 1 to CheckSync.js
                context.Response.Write("1");
                return;
            }
        }
    
        // Client ref was not invalid so return 0 to CheckSync.js
        context.Response.Write("0");
    }
  8. CheckSync.js checks the response from SyncCheck.ashx and performs a reload on the page if the character "1" was returned; otherwise, it does nothing.
  9. function process()
    {
        // simply determine response from CheckSync.ashx
        // and refresh current window if 1 is returned
        if (req.readyState == 4 && req.status == 200 && 
                                   req.responseText == '1')
            window.location.replace(window.location.href);
    }
  10. The process repeats from step 3 until the page Default.aspx is no longer being viewed.

Invalidating client views on the server

As mentioned previously, whenever data changes, we need to invalidate our client views. To do this, we simply call the ViewSynchronisation.InvalidateClients() method.

public void AddProduct()
{
    ...
    
    ProductViewSync.InvalidateClients();
}

public void UpdateProduct(long Id, string Name, 
            string Description, decimal Price)
{
    ...
    
    ProductViewSync.InvalidateClients();
}

public void RemoveProduct(long Id)
{
    ...
    
    ProductViewSync.InvalidateClients();
}

By calling InvalidateClients(), we are simply incrementing the reference stored in ServerSynchronisationReference._syncRef.

public void InvalidateClients()
{
    Interlocked.Increment(ref _syncRef);
}

So, when a client checks ClientSynchronisationReference.IsInvalid, a simple comparison is made of its own client reference and the server reference.

public bool IsInvalid
{
    get
    {
        long _serverSyncRef = _serverRef.Value;

        // If server sync ref is greater than the client,
        // then client is invalid
        if (_serverSyncRef > _clientSyncRef)
        {
            _clientSyncRef = _serverSyncRef;
            return true;
        }

        return false;
    }
}

Points to note and food for thought ...

  • Yes, our client pages will still be sending a request every few seconds to the server - but, by using a combination XMLHTTP and an ASHX handler, we cut out most of the overheads involved with a full postback to an ASPX page. Also, we cut out the annoyance of full page refresh at the browser side.
  • Try to use on pages which display information only. It would be annoying if a user was in the middle of entering data into a form on the same page and it reloaded ...
  • How often the client pages check for view invalidation depends on the application. In the demo app, we are checking every 5 seconds. If once every minute is enough, change the var pollInterval line in the CheckSync.js file.
  • In the demo app, when CheckSync.js receives "1" back from CheckSync.ashx, it just actions a full page reload to refresh the page. At this point, instead of the page refresh, we theoretically could use another XMLHTTP request to pull down modified data and update the page dynamically for a totally transparent update.
  • So far, our incrementing number synchronization is only used for the purpose of signaling an invalid client view. Why not attach in some way details of changes made with each server reference update so that the client page can specifically update only what has changed?
  • Remember also, CheckSync.ashx doesn't only have to return "1" and "0" ...

    Server reference = 1
    Product 234 added 
    Server reference = 2
    Product 634 updated 
    Server reference = 3
    Product 231 removed 
    

History

  • Version 1.0 (17 February 2008) - Initial article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Allan Eagle
Software Developer Globe Business Publishing Ltd
United Kingdom United Kingdom
Started tinkering on an old BBC Microcomputer using BBC BASIC and progressed up to dabbling with C and ARMCode on an Acorn RiscPC. Moving to the PC platform I progressed from C++ to now a lot of C# and ASP.NET.

Comments and Discussions

 
GeneralMy vote of 2 PinmemberDilip Tripathy25-Aug-11 21:16 
GeneralInteresting article Allan, tying together a lot of techniques PinmemberMember 436622017-Feb-08 22:52 
GeneralRe: Interesting article Allan, tying together a lot of techniques PinmemberAllan Eagle18-Feb-08 2:39 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140721.1 | Last Updated 17 Feb 2008
Article Copyright 2008 by Allan Eagle
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid