Click here to Skip to main content
15,880,469 members
Articles / Web Development / CSS
Article

Who's Online?: A Virtual Earth And User Session Mashup in ASP.NET

Rate me:
Please Sign up or sign in to vote.
4.63/5 (22 votes)
28 Feb 2008CPOL4 min read 69.9K   76   16
A tutorial for implementing a modal popup window on your web site which will display a Microsoft Virtual Earth map with pinpoints on the locations of everyone who is currently browsing your web site.

whosonline

  • View Online Demo (Click on X users online in directly underneath navigation bar)

Introduction

I often looked at the maps provided by my web analytic software and thought how cool it would be if I could provide these types of maps for the users currently browsing my web site, in real time. So my quest began to add this lush novelty item to my personal web site. I found the virtual maps JavaScript API and hostip.info API, and with a little bit more work and research, I had it.

The tutorial below describes how you can implement a modal popup window on your web site which will display a Microsoft Virtual Earth map with pinpoints on the locations of everyone who is currently browsing your web site.

What you will need to get started

  1. Microsoft Visual Studio 2008 with AJAX Controls Toolkit 3.5/3.6.
  2. -or- Microsoft Visual Studio 2005 with Ajax.NET Framework and AJAX Controls Toolkit 2.0.
  3. Virtual Earth (Microsoft Live Maps) JavaScript API.
  4. hostip.info API handler.
  5. Singleton collection for storing session information at application level.
  6. Global.asax file for catching and recording session info.

Using the code

I have broken the code you will need to implement, down into five steps below:

Step 1: A UserIpInfo singleton collection.

This is used to store session information for each user at the application level; this is necessary so that each user will have access to view the information of others. Alternatively, web.caching or an application variable could be used in place of a Singleton collection, but those would cause the objects to be serialized/deserialized when accessed. Since this is a novelty item, you want to make sure the addition does not hurt the performance of your website.

VB.NET

VB
''' <summary />
''' UserIPInfoList
''' </summary />
''' <remarks /></remarks />
Public Class UserIPInfoList
    Inherits Generic.Dictionary(Of String, UserIpInfo)

    ''' <summary />
    ''' Instance
    ''' </summary />
    ''' <remarks />
    ''' The current running instance.
    ''' </remarks />
    Private Shared m_instance As UserIPInfoList
    Public Shared ReadOnly Property Instance() As UserIPInfoList
        Get
            ' initialize if not already done
            If m_instance Is Nothing Then
                m_instance = New UserIPInfoList
            End If
            ' return the initialized instance of the Singleton Class
            Return m_instance
        End Get
    End Property

    ''' <summary />
    ''' New
    ''' </summary />
    ''' <remarks /></remarks />
    Private Sub New()
        MyBase.New()
    End Sub

End Class

''' <summary />
''' UserIpInfo
''' </summary />
''' <remarks /></remarks />
Public Class UserIpInfo

    ''' <summary />
    ''' IPAddress
    ''' </summary />
    ''' <remarks /></remarks />
    Private m_IPAddress As String
    Public Property IPAddress() As String
        Get
            Return m_IPAddress
        End Get
        Set(ByVal value As String)
            m_IPAddress = value
        End Set
    End Property

    ''' <summary />
    ''' Location
    ''' </summary />
    ''' <remarks /></remarks />
    Private m_Location As String
    Public Property Location() As String
        Get
            Return m_Location
        End Get
        Set(ByVal value As String)
            m_Location = value
        End Set
    End Property

    ''' <summary />
    ''' Coordinates
    ''' </summary />
    ''' <remarks /></remarks />
    Private m_Coordinates As String
    Public Property Coordinates() As String
        Get
            Return m_Coordinates
        End Get
        Set(ByVal value As String)
            m_Coordinates = value
        End Set
    End Property

    ''' <summary />
    ''' CoordinatesReversed
    ''' </summary />
    ''' <value /></value />
    ''' <returns /></returns />
    ''' <remarks /></remarks />
    Public ReadOnly Property CoordinatesReversed()
        Get
            Dim corray As String() = m_Coordinates.Split(&quot;,&quot;)
            Return corray(1) & "," & corray(0)
        End Get
    End Property

    ''' <summary />
    ''' New
    ''' </summary />
    ''' <param name="iPAddress" /></param />
    ''' <param name="location" /></param />
    ''' <param name="coordinates" /></param />
    ''' <remarks /></remarks />
    Public Sub New(ByVal iPAddress As String, _
                   ByVal location As String, _
                   ByVal coordinates As String)
        Me.IPAddress = iPAddress
        Me.Location = location
        Me.Coordinates = coordinates
    End Sub

    ''' <summary />
    ''' New
    ''' </summary />
    ''' <remarks /></remarks />
    Public Sub New()

    End Sub

End Class

C#

C#
/// <summary> 
/// UserIPInfoList 
/// </summary> 
/// <remarks></remarks> 
public class UserIPInfoList : Generic.Dictionary<string, UserIpInfo> 
{ 
    
    /// <summary> 
    /// Instance 
    /// </summary> 
    /// <remarks> 
    /// The current running instance. 
    /// </remarks> 
    private static UserIPInfoList m_instance; 
    public static UserIPInfoList Instance { 
        get { 
            // initialize if not already done 
            if (m_instance == null) { 
                m_instance = new UserIPInfoList(); 
            } 
            // return the initialized instance of the Singleton Class 
            return m_instance; 
        } 
    } 
    
    /// <summary> 
    /// New 
    /// </summary> 
    /// <remarks></remarks> 
    private UserIPInfoList() : base() 
    { 
    } 
    
} 

/// <summary> 
/// UserIpInfo 
/// </summary> 
/// <remarks></remarks> 
public class UserIpInfo 
{ 
    
    /// <summary> 
    /// IPAddress 
    /// </summary> 
    /// <remarks></remarks> 
    private string m_IPAddress; 
    public string IPAddress { 
        get { return m_IPAddress; } 
        set { m_IPAddress = value; } 
    } 
    
    /// <summary> 
    /// Location 
    /// </summary> 
    /// <remarks></remarks> 
    private string m_Location; 
    public string Location { 
        get { return m_Location; } 
        set { m_Location = value; } 
    } 
    
    /// <summary> 
    /// Coordinates 
    /// </summary> 
    /// <remarks></remarks> 
    private string m_Coordinates; 
    public string Coordinates { 
        get { return m_Coordinates; } 
        set { m_Coordinates = value; } 
    } 
    
    /// <summary> 
    /// CoordinatesReversed 
    /// </summary> 
    /// <value></value> 
    /// <returns></returns> 
    /// <remarks></remarks> 
    public object CoordinatesReversed { 
        get { 
            string[] corray = m_Coordinates.Split(","); 
            return corray(1) + "," + corray(0); 
        } 
    } 
    
    /// <summary> 
    /// New 
    /// </summary> 
    /// <param name="iPAddress"></param> 
    /// <param name="location"></param> 
    /// <param name="coordinates"></param> 
    /// <remarks></remarks> 
    public UserIpInfo(string iPAddress, string location, string coordinates) 
    { 
        this.IPAddress = iPAddress; 
        this.Location = location; 
        this.Coordinates = coordinates; 
    } 
    
    /// <summary> 
    /// New 
    /// </summary> 
    /// <remarks></remarks> 
    public UserIpInfo() 
    { 
        
    } 
   
}

Step 2: Gathering user information in your Global.asax

You can use the global.asax file to gather information about your user on session start, and remove it on session end. The code block below will get the user's IP address, look up the location of that address, and store it in your singleton collection with the session ID as the key. When the user session ends, it will be removed based on the unique session ID.

VB.NET

VB
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
    ' Fires when the session is started
    Application.Lock()

    'add session ip address to list.
    If Session.IsNewSession() AndAlso Not _
       UserIPInfoList.Instance.ContainsKey(Session.SessionID) Then

        Try
            'make a call to hostip handler to get ip info
            Dim hostIPLookupXML As New XmlDocument
            hostIPLookupXML.Load("http://api.hostip.info/?ip=" & _
                                 Request.UserHostAddress)
            'uncomment to test.
            'hostIPLookupXML.Load("http://api.hostip.info/?ip=12.215.42.19")

            'add namespace
            Dim nsMgr As New XmlNamespaceManager(hostIPLookupXML.NameTable)
            nsMgr.AddNamespace("gml", "http://www.opengis.net/gml")

            'select node
            Dim Hostip As XmlNode = _
                hostIPLookupXML.DocumentElement.SelectSingleNode("gml:featureMember", _
                                                                 nsMgr).FirstChild

            'check for bad results.
            If Hostip IsNot Nothing Then
                'parse data to local vars.
                Dim locationCityState As String = Hostip.ChildNodes(0).InnerText
                Dim locationCountry As String = Hostip.ChildNodes(1).InnerText

                If Hostip.ChildNodes.Count > 3 AndAlso _
                          Hostip.ChildNodes(4) IsNot Nothing Then
                    'check that we have cooridinates.
                    Dim coordinates As String = Hostip.ChildNodes(4).InnerText

                    If coordinates <> String.Empty Then
                        'add user info to list.
                        UserIPInfoList.Instance.Add(Session.SessionID, _
                                  New UserIpInfo(Request.UserHostAddress, _
                                  locationCityState & " " & locationCountry, _
                                  coordinates))
                    End If

                End If

            End If

        Catch ex As Exception
            'service is unavialable, ignore error.
        End Try

    End If

    Application.UnLock()
End Sub

Sub Session_End(ByVal sender As Object, ByVal e As EventArgs)
    ' Fires when the session ends
    Application.Lock()

    Try
        Dim ipi As New BusinessObjects.ProgrammersJournal.UserIpInfo
        If UserIPInfoList.Instance.TryGetValue(Session.SessionID, ipi) Then _
            UserIPInfoList.Instance.Remove(Session.SessionID)
    Catch ex As Exception
        'move on
    End Try
    Application.UnLock()
End Sub

C#

C#
public void Session_Start(object sender, EventArgs e)
{ 
    // Fires when the session is started 
    Application.Lock(); 
    
    //add session ip address to list. 
    if (Session.IsNewSession() && _
        !UserIPInfoList.Instance.ContainsKey(Session.SessionID)) { 
        
        try { 
            //make a call to hostip handler to get ip info 
            XmlDocument hostIPLookupXML = new XmlDocument(); 
            hostIPLookupXML.Load("http://api.hostip.info/?ip=" + _
                                 Request.UserHostAddress); 
            //uncomment to test. 
            //hostIPLookupXML.Load("http://api.hostip.info/?ip=12.215.42.19") 
            
            //add namespace 
            XmlNamespaceManager nsMgr = new XmlNamespaceManager(hostIPLookupXML.NameTable); 
            nsMgr.AddNamespace("gml", "http://www.opengis.net/gml"); 
            
            //select node 
            XmlNode Hostip = _
              hostIPLookupXML.DocumentElement.SelectSingleNode("gml:featureMember", _
                                                               nsMgr).FirstChild; 
            
            //check for bad results. 
            if (Hostip != null) { 
                //parse data to local vars. 
                string locationCityState = Hostip.ChildNodes(0).InnerText; 
                string locationCountry = Hostip.ChildNodes(1).InnerText; 
                
                if (Hostip.ChildNodes.Count > 3 && Hostip.ChildNodes(4) != null) { 
                    //check that we have cooridinates. 
                    string coordinates = Hostip.ChildNodes(4).InnerText; 
                    
                    if (coordinates != string.Empty) { 
                        //add user info to list. 
                        UserIPInfoList.Instance.Add(Session.SessionID, 
                               new UserIpInfo(Request.UserHostAddress, 
                               locationCityState + " " + locationCountry, coordinates)); 
                    } 
                    
                } 
                
            } 
        } 
        
        catch (Exception ex) { 
            //service is unavialable, ignore error. 
        } 
        
    } 
    
    Application.UnLock(); 
} 
public void Session_End(object sender, EventArgs e) 
{ 
    // Fires when the session ends 
    Application.Lock(); 
    
    try { 
        BusinessObjects.ProgrammersJournal.UserIpInfo ipi = 
                new BusinessObjects.ProgrammersJournal.UserIpInfo(); 
        if (UserIPInfoList.Instance.TryGetValue(Session.SessionID, ipi)) 
            UserIPInfoList.Instance.Remove(Session.SessionID); 
    } 
    catch (Exception ex) { 
        //move on 
    } 
    Application.UnLock(); 
}

Step 3: Placing the map on your page in a modal popup

The next step is to simply place the map on your page in a modal pop up window. You will need to include a reference to the JavaScript API on your page, create a function for loading the map, create a modal pop up window, and add your JavaScript function to the body onload event. There is plenty of information available online to customize the display of your map, so I will just keep it simple here.

ASP.NET
<!-- Add link to jscript api -->

<script src="http://dev.virtualearth.net/mapcontrol/v3/mapcontrol.js">
    </script>

<!--add script to load map-->

    <script type="text/javascript">
    //<![CDATA[
    var map = null;


    function GetMap()
    {
        //display map
        map = new VEMap('myMap');
        map.LoadMap(new VELatLong(115,-150),1,'r',false);


        //hide dashboard.
        map.HideDashboard();
    }


    //]]>
    </script>

<!--call load map script when page loads-->

<body onload="GetMap();">

<!--Add link button to display modal window-->

     <asp:LinkButton ID="LinkButton2" runat="server" ToolTip="whats this?">
                    <asp:Literal ID="numonline" runat="server"></asp:Literal>
                    users online
                </asp:LinkButton>

<!--add panel to hold map-->

        <asp:Panel ID="Panel2" runat="server" Style="display: none" CssClass="modalPopup">
              <asp:Panel ID="Panel4" runat="server" 
                      Style="cursor: move; background-color: #a5c863; padding: 3px; 
                             border: solid 1px Gray; color: white; margin-bottom: 3px;">
                    <div>
                        <strong>Whos Online?</strong>
                    </div>
                </asp:Panel>
                <div id="myMap" style="position: relative; width: 400px; height: 200px;">
                </div>
                <br />
              <asp:Button ID="OkButton" runat="server" Text="done" CssClass="loginbox" />
        </asp:Panel>

<!--add modal extender-->

            <cc1:ModalPopupExtender ID="ModalPopupExtender1" 
                runat="server" TargetControlID="LinkButton2"
                PopupControlID="Panel2" BackgroundCssClass="modalBackground" 
                OkControlID="OkButton" DropShadow="true" 
                Y="35" PopupDragHandleControlID="Panel4" />

Step 4: Adding pinpoints to your map

The next step is to loop through your singleton collection and add the pinpoints to your map. We will do this by dynamically creating the JavaScript in the code-behind page. You will then need to call your new function from the body onload to display them, this is because the map needs to be created prior to adding the pinpoints.

VB.NET

VB
'set number of users online.
 Me.numonline.Text = UserIPInfoList.Instance.Keys.Count

    'add pinpoints to map.
    Dim sb As New StringBuilder()
    Dim count As Integer = 1
    sb.AppendLine("function ShowPins()")
    sb.AppendLine("{")
    For Each key As String In UserIPInfoList.Instance.Keys
        sb.AppendLine("var pinID = " & count & ";")
        sb.AppendLine("var pin = new VEPushpin(")
        sb.AppendLine("pinID, ")
        sb.AppendLine("new VELatLong(" & _
           UserIPInfoList.Instance(key).CoordinatesReversed & "), ")
        sb.AppendLine("null, ")
        sb.AppendLine("'" & UserIPInfoList.Instance(key).IPAddress & "', ")
        sb.AppendLine("'" & UserIPInfoList.Instance(key).Location.Replace("'", _
                      "&rsquo;") & "','pinEvent', 'Century 16'")
        sb.AppendLine(");")
        sb.AppendLine("")
        sb.AppendLine("map.AddPushpin(pin);")
        count = count + 1
    Next
    sb.AppendLine("}")

    'add script to output.
    Me.Page.ClientScript.RegisterClientScriptBlock(Me.GetType(), _
           "MapScript", sb.ToString(), True)

C#

C#
{ 
    //set number of users online. 
    this.numonline.Text = UserIPInfoList.Instance.Keys.Count; 
    
    //add pinpoints to map. 
    StringBuilder sb = new StringBuilder(); 
    int count = 1; 
    sb.AppendLine("function ShowPins()"); 
    sb.AppendLine("{"); 
    foreach (string key in UserIPInfoList.Instance.Keys) { 
        sb.AppendLine("var pinID = " + count + ";"); 
        sb.AppendLine("var pin = new VEPushpin("); 
        sb.AppendLine("pinID, "); 
        sb.AppendLine("new VELatLong(" + 
                      UserIPInfoList.Instance(key).CoordinatesReversed + "), "); 
        sb.AppendLine("null, "); 
        sb.AppendLine("'" + UserIPInfoList.Instance(key).IPAddress + "', "); 
        sb.AppendLine("'" + 
                      UserIPInfoList.Instance(key).Location.Replace("'",
                      "&rsquo;")  + "','pinEvent', 'Century 16'"); 
        sb.AppendLine(");"); 
        sb.AppendLine(""); 
        sb.AppendLine("map.AddPushpin(pin);"); 
        count = count + 1; 
    } 
    sb.AppendLine("}"); 
    
    //add script to output. 
    this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(), 
              "MapScript", sb.ToString(), true); 
}

Add this to the body tag:

ASP.NET
<!--call load map script when page loads-->
<body onload="GetMap();ShowPins();">

Step 5: Customizing your CSS

There are a few customizations you will need to make to your CSS; the first is to control the display of the modal window as well as the pins on the map. The second is to add a style tag to your aspx page so the title windows will appear over the popup window. This needs to be added after the map has loaded, so it cannot be stored in your CSS file.

HTML
/*Modal Popup*/
.modalBackground {
    background-color:Gray;
    filter:alpha(opacity=50);
    opacity:0.7;
}

.modalPopup {
    background-color:#f9f9e5;
    border-width:1px;
    border-style:solid;
    border-color:Gray;
    padding:3px;
    width:400px;
    text-align:center;
}

/*Map*/
.pinEvent
{
    width:10px;height:15px;
    overflow:hidden;
    cursor:pointer;
}


<style type="text/css">
     .ero{z-index: 100002 !important;}
     .ero-progressAnimation{z-index: 100002 !important;}
     .VE_Message{z-index: 100002 !important;}
</style>

Points of interest

  • The hostip.info service is a free service, and as a free service, it performs about as good as you would expect a free service to. It seems to place any IP address it cannot determine in Camarillo, CA. If you have access to a better geo-location service or database, I would recommend using that, though the hostip.info service is extremely fast and performs well enough for a novelty addition to your web site.
  • You will notice that I am using version 3 of the Virtual Maps JavaScript API. This is the only one I found to be stable, with a map this small and had the fastest loading times. If you find that you are stable using one of the other versions, please let me know.

History

  • 2/20/2008 - Added C# versions of code.
  • 2/28/2008 - Escapes apostrophe's in JScript output.

License

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


Written By
Software Developer (Senior)
United States United States
For more articles please visit my website:
http://www.aprogrammersjournal.com

A Programmers Journal
RSS

Comments and Discussions

 
GeneralMy vote of 1 Pin
crouchie0425-Feb-11 14:21
crouchie0425-Feb-11 14:21 
Generalhave 5- nice Pin
Pranay Rana11-Jan-11 19:23
professionalPranay Rana11-Jan-11 19:23 
QuestionNot working properly? Pin
K3211-Jan-09 22:37
K3211-Jan-09 22:37 
GeneralUpdate Pin
Jason Witty28-Feb-08 9:02
Jason Witty28-Feb-08 9:02 
GeneralAwesome ! Pin
dapoussin28-Feb-08 8:26
dapoussin28-Feb-08 8:26 
GeneralRe: Awesome ! Pin
Jason Witty28-Feb-08 8:59
Jason Witty28-Feb-08 8:59 
GeneralC# Pin
yassir hannoun20-Feb-08 6:52
yassir hannoun20-Feb-08 6:52 
GeneralRe: C# Pin
Jason Witty20-Feb-08 6:55
Jason Witty20-Feb-08 6:55 
GeneralRe: C# Pin
Jason Witty20-Feb-08 12:36
Jason Witty20-Feb-08 12:36 
GeneralRe: C# Pin
yassir hannoun21-Feb-08 2:04
yassir hannoun21-Feb-08 2:04 
General[Message Removed] Pin
Mojtaba Vali18-Feb-08 17:21
Mojtaba Vali18-Feb-08 17:21 
GeneralRe: Working Sample? Pin
Jason Witty18-Feb-08 19:06
Jason Witty18-Feb-08 19:06 
GeneralNeat Pin
Ben Daniel18-Feb-08 11:52
Ben Daniel18-Feb-08 11:52 
AnswerRe: Neat Pin
Jason Witty18-Feb-08 12:16
Jason Witty18-Feb-08 12:16 
GeneralRe: Neat Pin
Ben Daniel18-Feb-08 12:26
Ben Daniel18-Feb-08 12:26 
GeneralRe: Neat Pin
Jason Witty20-Feb-08 13:53
Jason Witty20-Feb-08 13:53 

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

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