Monday, 21 April 2014
Who's Online?: A Virtual Earth And User Session Mashup in Asp.Net

Author: Jason Witty

 


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 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.

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) javacript 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 5 steps below:

Step 1: A UserIpInfo singleton Collection.

This is used to store session information for each user at the application level, this is nessasary 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.

 ''' <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(",")
                Return corray(1) & "," & corray(0)
            End Get
        End Property

        ''' <summary />
        ''' New
        ''' </summary />
        ''' <param name=&quot;iPAddress&quot; /></param />
        ''' <param name=&quot;location&quot; /></param />
        ''' <param name=&quot;coordinates&quot; /></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

 

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 users 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.


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

 

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.


<!-- Add link to jscript api -->

<script src="/Portals/0/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.


'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 & "','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)

<!--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 to 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 loading so it cannot be stored in your css file.


/*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 geolocation service or database I would recomend 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.
 
Copyright 2009. Sponsored by nsquared.   |  Terms Of Use  |  Privacy Statement
Content on this site is generated from the developer community and shared freely for your enjoyment and benefit. This site is run independently of Microsoft and does not express Microsoft's views in any way.