Click here to Skip to main content
15,891,184 members
Articles / Web Development / IIS

HTTP Push from SQL Server — Comet SQL

Rate me:
Please Sign up or sign in to vote.
4.91/5 (55 votes)
27 Nov 2012CPOL10 min read 205.7K   4.2K   169  
This article provides an example solution for presenting data in "real-time" from Microsoft SQL Server in an HTML browser. The article presents how to implement Comet functionality in ASP.NET and how to connect Comet with Query Notification from SQL Server.
// Copyright (c) 2010 Daniel Wojciechowski 
// E-mail: at gmail.com daniel.wojciechowski

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Web;
using System.Web.SessionState;

namespace HttpPushFromMsSql
{
    public class CometAsyncHandler : IHttpAsyncHandler, IReadOnlySessionState
    {
        public static List<CometAsyncResult> AllWaitingClients = new List<CometAsyncResult>();
        public static object AllWaitingClientsSync = new object();
        private static bool threadForTimeoutsWorking = false;

        public void ProcessRequest(HttpContext context)
        {
            throw new NotImplementedException();
        }

        public bool IsReusable
        {
            get { return true; }
        }

        ///<summary>Initiates an asynchronous call to the HTTP handler.</summary>
        ///
        ///<returns>An <see cref="T:System.IAsyncResult"></see> that contains
        /// information about the status of the process.
        /// </returns>
        ///
        ///<param name="context">An <see cref="T:System.Web.HttpContext"></see>
        /// object that provides references to intrinsic server objects (for
        /// example, Request, Response, Session, and Server) used to service
        /// HTTP requests. </param>
        ///<param name="extraData">Any extra data needed to process the request. </param>
        ///<param name="cb">The <see cref="T:System.AsyncCallback"></see> to
        /// call when the asynchronous method call is complete. If cb is null,
        /// the delegate is not called. </param>
        public IAsyncResult BeginProcessRequest(HttpContext context,
            AsyncCallback cb, object extraData)
        {
            context.Response.ContentType = "text/plain";

            // Get wait time from request
            int waitTime;
            ParseRequest(context.Request, out waitTime);

            // Get last seen record ID from Session
            object o = context.Session["LastRecId"];
            CometAsyncResult result;

            
            if (o == null) // some error handling
            {
                context.Response.Write("ERROR: Problem with session");
                result = new CometAsyncResult();
                result.CompletedSynchronously = true;
                result.IsCompleted = true;
                return result;
            }

            // Wait for data or timeout
            int lastRecId = (int) o;

            result = new CometAsyncResult(context, cb, waitTime, lastRecId);
            lock (AllWaitingClientsSync)
            {
                // register to Query Notification or complete
                // request synchronously in case if there is
                // already new data:
                if (!MessageDal.WaitMessageDataAsync(lastRecId))
                {
                    // if not waiting (there is new data)
                    // result is to be completed synchronously
                    result.IsCompleted = true;
                    result.CompletedSynchronously = true;
                    result.Result = true; // new data is available

                    WriteResponseToClient(result);
                    return result;
                }
                else
                {
                    // asynchronous (normal case):
                    AllWaitingClients.Add(result);
                    if (AllWaitingClients.Count == 1)
                        StartClientTimeouter();
                }
            }
            return result;
        }

        ///<summary>
        /// Provides an asynchronous process End
        /// method when the process ends.
        ///</summary>
        ///
        ///<param name="result">An <see cref="T:System.IAsyncResult"></see>
        /// that contains information about the status of the process. </param>
        public void EndProcessRequest(IAsyncResult result)
        {
            Debug.WriteLine("EndProcessRequest");
            CometAsyncResult cometAsyncResult = (CometAsyncResult) result;
            
            if(cometAsyncResult.CompletedSynchronously)
                return;

            WriteResponseToClient(cometAsyncResult);
        }

        public void WriteResponseToClient(CometAsyncResult cometAsyncResult)
        {
            if (cometAsyncResult.Result)
                cometAsyncResult.Context.Response.Write("NEWDATAISAVAILABLE");
            else
                cometAsyncResult.Context.Response.Write("TOOLONG-DOITAGAIN"); // timeout - client must make request again
        }

        public static void ProcessAllWaitingClients()
        {
            ThreadPool.QueueUserWorkItem(
                delegate
                    {
                        lock (AllWaitingClientsSync) // this will block all new clients that wants to be inserted in list
                        {
                            Trace.WriteLine("Processing all clients: " + AllWaitingClients.Count);
                            foreach (CometAsyncResult asyncResult in AllWaitingClients)
                            {
                                asyncResult.Result = true; // New data available
                                asyncResult.Callback(asyncResult);
                                Trace.WriteLine("Callback() " + asyncResult.LastRecId);
                            }
                            AllWaitingClients.Clear();
                        }
                    });
        }

        public static void StartClientTimeouter()
        {
            lock (AllWaitingClientsSync)
            {
                if (threadForTimeoutsWorking)
                    return;
                else
                    threadForTimeoutsWorking = true;
            }

            ThreadPool.QueueUserWorkItem(
                delegate
                    {
                        int count;
                        lock (AllWaitingClientsSync)
                            count = AllWaitingClients.Count;
                        while( count > 0)
                        {
                            // Call Callback() to all timed out requests and
                            // remove from list.
                            lock (AllWaitingClientsSync)
                            {
                                DateTime now = DateTime.Now;
                                AllWaitingClients.RemoveAll(
                                    delegate(CometAsyncResult asyncResult)
                                        {
                                            if (asyncResult.StartTime.Add(asyncResult.WaitTime) < now)
                                            {
                                                asyncResult.Result = false; // timeout
                                                asyncResult.Callback(asyncResult);
                                                return true; // true for remove from list
                                            }

                                            return false; // not remove (because not timed out)
                                        });
                            }

                            // This sleep causes that some timeouted clients are removed with delay
                            // Example: if timeout=60s, sleep=1s then timeouted client can be removed after 60,7s.
                            // In some cases this can be considered as bug. TODO: Change it to WaitOne() and
                            // calculate proper sleep time
                            Thread.Sleep(1000); 
                            lock (AllWaitingClientsSync)
                            {
                                count = AllWaitingClients.Count;
                                if (count == 0)
                                    threadForTimeoutsWorking = false;
                            }
                        }
                    });
        }

        private static void ParseRequest(HttpRequest request, out int waitTime)
        {
            int defaultWaitTime = 1000 * 30; // 30 sec.
            int maxWaitTime = 1000 * 60 * 60; // 1 hour
            string waitTimeString = request["waitTime"];
            if (!int.TryParse(waitTimeString, out waitTime))
                waitTime = defaultWaitTime;
            if (waitTime < 0)
                waitTime = 0;
            if (waitTime > maxWaitTime)
                waitTime = maxWaitTime;
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

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


Written By
Software Developer
Poland Poland
I graduated from the Jagiellonian University. After defence of master thesis (link to presentation) I have started professional programming in C and C++ using OpenGL. After that, my Company gave me a chance to switch to .Net framework and C# and I have betrayed my passion for computer graphics. Now I'm C#, SQL and sometimes ASP.NET programmer.

Comments and Discussions