Sunday 4 March 2012

How to get detailed info about all online visitors that are currently browsing your ASP.NET website?


In one of our previous answers to very common question that is often asked by beginner developers:
how to show the current number of online visitors for ASP.NET website?
we demonstrated that its really simple to count the number of people that are currently browsing your website.

If you have added this option to your website, at some point of time, you have undoubtedly asked yourself:
"who are those 17 people that are currently browsing my precious website ?"  Description: http://www.aspdotnetfaq.com/fckeditor/editor/images/smiley/msn/teeth_smile.gif
"What are their IP addresses? when did they come here, what page they first opened and from where did they came from?"

Yes i know you can see that information later by examining the WebSite logs, but having the current information is something that no later examination of the logs can compensate.

The trouble is that there is no standard way to enumerate active visitor sessions on your ASP.NET website.
You can only enumerate keys and values of the current HTTPContext Session for each user but this is not enough.

This is caused by the builtin ASP.NET security measure that forbids individual user sessions to access other sessions.
(If this would not be forbidden, many bad things could happen to you while you browse a website, so this is a good thing).

But if there is no easy way to get the list of all current user sessions, that does not mean we cannot track them.

Here is what we are going to do:
We will create a global collection that will hold the list of all active user sessions, and as each new user hits our website we will add his/hers information to this list, also when any of the session expires, we will remove its information from this global list.

First we must setup our WebSite session configuration in web.config like this:
    <sessionState mode="InProc" cookieless="false" timeout="30"/>

This will tell the ASP.NET runtime to create a session in the memory of the Web Server for each visitor and keep it alive for 30 minutes.
If user keeps browsing our website and opens some of the pages every few minutes, his session is kept alive.
But if he does not open any new pages on our website for 30 or more minutes, his session will expire.

Now how do we know when a new session is created or when it expires?

This is why ASP.NET is great: you can add to your website Global.asax class (file) where you can easily add code to handlers for some very important application-level events.
(To create a new Global.asax class file in your ASP.NET website, right-click on the website in the Solution Explorer, and choose options:
ADD NEW ITEM->GLOBAL APPLICATION CLASS).

There are number of application events that you can handle in this class, but in this case, events that are important for us are Session_Start and Session_End.

Session_Start event handler code is fired when new visitor comes to our website, and Session_End is fired for each visitor if he does not open any pages on our website longer than our TIMEOUT setting in sessionState configuration of web.config file.

NOTE: ASP.Net runtime has no other way of knowing if user has left our website. If user opens one page, and then immediately closes his web-browser, this will not trigger the Session_End event at once. This event will eventually trigger when the Timeout timer elapses.

Now that we know how to hook into the process of creating and destroying visitor sessions, we need to define a simple WebsiteVisitor object that we will create for each visitor to store information about him and keep it in our global collection.

Here is the definition of WebsiteVisitor class:

namespace
Roboblob.Utility
{
    public class WebsiteVisitor
    {
        private string sessionId;
        public string SessionId
        {
            get { return sessionId; }
            set { sessionId = value; }
        }

        public string IpAddress
        {
            get { return ipAddress; }
            set { ipAddress = value; }
        }
        private string ipAddress;

        public string UrlReferrer
        {
            get { return urlReferrer; }
            set { urlReferrer = value; }
        }
        private string urlReferrer;

        public string EnterUrl
        {
            get { return enterUrl; }
            set { enterUrl = value; }
        }
        private string enterUrl;

        public string UserAgent
        {
            get { return userAgent; }
            set { userAgent = value; }
        }
        private string userAgent;

        public DateTime SessionStarted
        {
            get { return sessionStarted; }
            set { sessionStarted = value; }
        }
        private DateTime sessionStarted;


        public WebsiteVisitor(HttpContext context)
        {
            if  ((context != null) && (context.Request != null) && (context.Session != null) )
            {
                this.sessionId = context.Session.SessionID;
                sessionStarted = DateTime.Now;
                userAgent = string.IsNullOrEmpty(context.Request.UserAgent) ? "" : context.Request.UserAgent;
                ipAddress = context.Request.UserHostAddress;
                if (context.Request.UrlReferrer != null)
                {
                    urlReferrer = string.IsNullOrEmpty(context.Request.UrlReferrer.OriginalString) ? "" : context.Request.UrlReferrer.OriginalString;
                }
                enterUrl = string.IsNullOrEmpty(context.Request.Url.OriginalString) ? "" : context.Request.Url.OriginalString;
            }
        }

    }
}
 
As you can see its very simple object with some public properties for the data we want to track about our visitor.
In the constructor of the object we pass current HttpContext object as the parameter that will allow our object to extract all the data needed.
In this example we track the visitors IP Address, SessionID, Browser User Agent string, Date and Time when user entered our website, First Page he opened, and Referrer Url from which he visited us (If any).
Here you can add some other interesting data if you can retrieve it. Its up to you.

Now let us create a global collection where we will keep our active WebsiteVisitor objects:

To make it available to all of our pages, we will put it in public static class in the same file where we placed WebsiteVisitor object:
    public static class OnlineVisitorsContainer
    {
      public static Dictionary<string, WebsiteVisitor> Visitors = new Dictionary<string,WebsiteVisitor>();
    }

As you can see its a generic Dictionary class, where keys (that are used to store and retrieve values from the dictionary) are of string type, while the values are of WebsiteVisitor type.

We have declared our container class OnlineVisitorsContainer and Dictionary collection  Visitors  as static.
This way the container class cannot be instantiated, and there can be only one instance of Visitors collection to be shared amongst all of our pages and code.

Now you are probably thinking: what we will use as a string key for each Visitor?
Well, this is a tricky question.
The most logical answer is to use the SessionID that is anyway created for each visitor as unique identifier for his session data.

Now that we solved this dilemma, lets us move to the code in the event handlers in Global.asax file:

First add these lines to top of your Global.asax file to import namespaces that we will use:
<%@ Import Namespace="System.Collections.Generic" %>
<%@ Import Namespace="Roboblob.Utility" %>

Here is the code for the application event that gets fired when sesion is started (when visitor comes to our website):
    void Session_Start(object sender, EventArgs e)
    {
        // add some data to the Session so permanent SessionId is assigned
        // otherwise new SessionId is assigned to the user until some data
        // is actually written to Session
        Session["Start"] = DateTime.Now;

        // get current context
        HttpContext currentContext = HttpContext.Current;

        if (currentContext != null)
        {
            if (!OnlineVisitorsContainer.Visitors.ContainsKey(currentContext.Session.SessionID))
            {
                WebsiteVisitor currentVisitor = new WebsiteVisitor(currentContext);               
                OnlineVisitorsContainer.Visitors.Add(currentVisitor.SessionId, currentVisitor);
            }
        }
    }

So in the event handler that is fired when session is created, we first place current Date and Time in the current session so that permanent SessionID is assigned to the Session.

If we do not do that, it can happen that new SessionID is created for the same user again and again until something is actually written to the Session collection.

Afterwards, we get the reference to current HttpContext and pass it to the constructor of the new WebsiteVisitor object and add this object to the global Visitors collection.

Now to the Session_End event handler code:
    void Session_End(object sender, EventArgs e)
    {
        // Code that runs when a session ends.
        // Note: The Session_End event is raised only when the sessionstate mode
        // is set to InProc in the Web.config file. If session mode is set to StateServer
        // or SQLServer, the event is not raised.

        if (this.Session != null)
            OnlineVisitorsContainer.Visitors.Remove(this.Session.SessionID);
    }

In Session_End event handler we cannot access current HttpContext instance, but we can access current Session and therefore we can retrieve current SessionID and use it to remove current visitors session from the our global collection of online visitors.

We have finished most of our work. We have implemented all the necessary code that will keep our global collection updated, and it will always hold the list of the visitors that are currently browsing our website.

Lets add GridView and some code to Default.aspx page that will show our active website visitors:
    <asp:GridView ID="gvVisitors" runat="server" AutoGenerateColumns="False"
        CellPadding="2" ForeColor="#333333" GridLines="Both" >
        <FooterStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" />
        <RowStyle BackColor="#EFF3FB" />
    <Columns>
        <asp:TemplateField HeaderText="Session Started">
        <ItemTemplate>
            <%# ((DateTime)Eval("SessionStarted")).ToString("dd/MM/yyyy HH:mm:ss") %><br />
        </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Ip">
        <ItemTemplate>
            <%# Eval("IpAddress") %>
        </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Other">
        <ItemTemplate>
            <span style="font-size:small;">
            <%# Eval("UserAgent") %><br />
            <%# Eval("EnterUrl") %><br />
            </span>

            <asp:HyperLink ID="refurl" Text='<%# Eval("UrlReferrer") %>' Font-Size="Small"
            NavigateUrl='<%# Eval("UrlReferrer") %>' runat="server" Target="_blank" />
        </ItemTemplate>
        </asp:TemplateField>

    </Columns>
        <PagerStyle BackColor="#2461BF" ForeColor="White" HorizontalAlign="Center" />
        <SelectedRowStyle BackColor="#D1DDF1" Font-Bold="True" ForeColor="#333333" />
        <HeaderStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" />
        <EditRowStyle BackColor="#2461BF" />
        <AlternatingRowStyle BackColor="White" />
    </asp:GridView>

And now we must only bind our global visitors collection from OnlineVisitorsContainer class to the GridVIew control:
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!Page.IsPostBack)
        {
            if (OnlineVisitorsContainer.Visitors != null)
            {
                gvVisitors.DataSource = OnlineVisitorsContainer.Visitors.Values;
                gvVisitors.DataBind();
            }
        }
    }

Open your webpage in your browser (for example first do it in Firefox) and you will see single visitor displayed in GridView. To test if it works, fire up another browser (different one maybe Internet Explorer or Opera) and refresh the page. There should be two visitors shown in the list.
If you dont click on any pages for 30 minutes in one of the browsers it will eventually disappear from the list.

here is how it should look:

Description: http://www.aspdotnetfaq.com/userfiles/image/FaqFiles/Show-Online-Visitors-Details/Show-Online-Visitors.jpg

This was not complicated, right? Other nice feature we could add is to filter some IPs that we know are Spiders or Bots so we see only real people visiting our website, then we could determine Country of the visitor based on his IP Address etc, also we could show list of Top Referrers on our website etc...

There are many more feature we could add here, but I will leave some work for you to do also...


I have stripped out the IP Address of the visitors to keep your privacy Description: http://www.aspdotnetfaq.com/fckeditor/editor/images/smiley/msn/wink_smile.gif.

Download Visual Studio Solution from this article

No comments :