在线用户:ASP.NET 中的虚拟地球和用户会话集成






4.63/5 (19投票s)
本教程介绍如何在您的网站上实现一个模态弹出窗口,该窗口将显示一个 Microsoft Virtual Earth 地图,并在地图上标记出所有当前正在浏览您网站的用户的位置。
- 查看在线演示 (点击导航栏正下方的“X 个用户在线”)
引言
我经常查看我的网站分析软件提供的地图,并觉得如果我能实时为当前浏览我网站的用户提供这类地图,那将是多么酷的事情。于是,我开始 quest,想为我的个人网站添加这个精美的趣味功能。我找到了 Virtual Maps JavaScript API 和 hostip.info API,经过一些工作和研究,我成功了。
下面的教程介绍如何在您的网站上实现一个模态弹出窗口,该窗口将显示一个 Microsoft Virtual Earth 地图,并在地图上标记出所有当前正在浏览您网站的用户的位置。
开始所需的准备
- Microsoft Visual Studio 2008 及 AJAX Controls Toolkit 3.5/3.6。
- - 或 - Microsoft Visual Studio 2005 及 Ajax.NET Framework 和 AJAX Controls Toolkit 2.0。
- Virtual Earth (Microsoft Live Maps) JavaScript API。
- hostip.info API 处理程序。
- 用于在应用程序级别存储会话信息的单例集合。
- 用于捕获和记录会话信息的 Global.asax 文件。
使用代码
我将实现所需的代码分解为下面的五个步骤
第一步:UserIpInfo 单例集合。
这用于在应用程序级别存储每个用户的会话信息;这是必要的,以便每个用户都可以查看其他人的信息。或者,您可以使用 web.caching 或应用程序变量来代替单例集合,但这会在访问时导致对象被序列化/反序列化。由于这是一个趣味性功能,您希望确保其添加不会影响您网站的性能。
VB.NET
''' <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="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#
/// <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()
{
}
}
第二步:在 Global.asax 中收集用户信息
您可以使用 global.asax 文件在会话开始时收集用户信息,并在会话结束时删除。下面的代码块将获取用户的 IP 地址,查找该地址的位置,并将其存储在您的单例集合中,以会话 ID 作为键。当用户会话结束时,它将根据唯一的会话 ID 被移除。
VB.NET
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#
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();
}
第三步:在页面中放置地图到模态弹出窗口
下一步是简单地将地图放置在您的页面中的模态弹出窗口中。您需要在页面上包含 JavaScript API 的引用,创建一个加载地图的函数,创建一个模态弹出窗口,并将您的 JavaScript 函数添加到 `onload` 事件中。网上有很多关于自定义地图显示的信息,所以我这里就简单处理。
<!-- 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" />
第四步:在地图上添加标记点
下一步是遍历您的单例集合,并将标记点添加到您的地图中。我们将通过在代码隐藏页面中动态创建 JavaScript 来实现。然后,您需要从 `onload` 事件调用您的新函数来显示它们,因为地图需要在添加标记点之前创建。
VB.NET
'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("'", _
"’") & "','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#
{
//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("'",
"’") + "','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);
}
将此添加到 body
标签中
<!--call load map script when page loads-->
<body onload="GetMap();ShowPins();">
第五步:自定义您的 CSS
您需要对 CSS 进行一些自定义;首先是控制模态窗口以及地图上标记点的显示。第二个是在您的 aspx 页面中添加一个 style
标签,以便标题窗口能够显示在弹出窗口之上。这需要在地图加载后添加,因此不能将其存储在 CSS 文件中。
/*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>
关注点
- hostip.info 服务是一项免费服务,作为免费服务,其性能与您对免费服务的预期大致相符。它似乎会将任何无法识别的 IP 地址定位在加利福尼亚州的 Camarillo。如果您能够访问更好的地理位置服务或数据库,我建议使用它,尽管 hostip.info 服务速度非常快,并且对于添加到您网站的趣味性功能而言性能足够。
- 您会注意到我正在使用 Virtual Maps JavaScript API 的版本 3。这是我找到的唯一一个在此类小型地图上稳定且加载速度最快的版本。如果您发现您在使用其他版本时也很稳定,请告诉我。
历史
- 2008 年 2 月 20 日 - 添加了 C# 版本的代码。
- 2008 年 2 月 28 日 - 转义了 JScript 输出中的撇号。