XLINQ to WoW Realm Status
本文将解释并演示如何使用XLINQ(LINQ to XML)来获取WoW服务器状态。
引言
如今有很多人在玩《魔兽世界》(确切地说是900万+)。所以我认为展示如何将许多人生活中的两个爱好结合起来是合适的,那就是游戏和编程。
背景
XML是一种开放的数据格式,很多人可以在任何平台上使用它,难怪大公司依赖它来存储和显示数据。一些公司/个人使用它来保存应用程序设置、博客文章以及许多其他内容。其中一个例子就是暴雪使用它来显示WoW服务器状态。本文将展示如何利用这种开放的数据格式以及.NET 3.5的全新XML to LINQ框架,将其用于任何应用程序,无论是Web还是可部署的可执行文件。
请注意,在撰写本文时,.NET 3.5和Visual Studio 2008均处于Beta 2阶段,而《魔兽世界》的订阅用户已超过900万。
Using the Code
理解数据背后的故事
如果我们要做任何查询,我们就需要数据。所以,首先,我们将看一下暴雪的WoW服务器状态XML页面。
如果您使用的是现代网页浏览器,您将看不到任何XML,您应该会看到一个格式适中的页面,其中包含每个服务器的状态、类型和流行度。现在,如果您想知道为什么您看不到任何XML,这是一个好问题。这个页面,就像许多HTML页面一样,只是带有附加样式表的XML。如果您熟悉XHTML,您会知道它是一个HTML的混合体,因为它符合XML规范。与HTML页面非常相似,它附加了一个样式表。要看到这一点,请右键单击页面并选择“查看源代码”。
您应该会看到这个
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="realmstatus.xsl"?>
第一行是XML声明,第二行是附加到数据的样式表。我将不再深入介绍XML/XML样式表的工作原理,主要是因为这不是本文的重点。我解释这一点是为了帮助您理解为什么XML文件中会出现样式。
理解数据
如果您继续查看XML的源代码,您会看到一个类似这样的行,代表每个服务器
<r n="Aegwynn" t="2" s="1" l="1"/>
暴雪创建了一些高度优化的XML,其属性名称非常非常短。我猜这会让下载速度更快。现在,这些属性中的每一个都代表某些东西。让我们试着弄清楚它们的含义。
- N:在我看来很明显,这是服务器的名称。
- T:T?那会是什么呢?有几种方法可以弄清楚,要么通过查看格式化的XML并将其与原始XML进行交叉引用,要么查看样式表。通过查看样式表,我们看到这个属性代表服务器的Type(类型)。1 = Normal(正常),2 = PvP,3 = RP(角色扮演),4 = RPPvP。
- S:通过查看样式表,我们可以看到S代表Status(状态),它代表服务器的状态。1 = Up(在线),其他所有值 = Down(离线)。
- L:如果您查看样式表的底部,您可以看到L代表Population(人口)(或Load(负载))。1 = Low(低),2 = Normal(正常),3 = High(高),4 = Max (Queued)(最大(排队中))。
如果我们看一下上面例子中的那一行,我们可以看到服务器是Aegwynn,一个PvP服务器,它在线且人口较少。很简单,不是吗?
在代码中使用数据
现在我们已经了解了我们要处理的数据。接下来,我们想在代码的上下文中能够使用它。这实际上非常简单,使用XLINQ(LINQ to XML)。
为了本文的目的,我将使用一个Windows Forms应用程序,但您可以随时在任何上下文中(ASP.NET、WCF、WPF等)使用此代码。
首先,启动Visual Studio 2008并创建一个Windows Forms应用程序。向项目中添加一个名为RealmStatus
的类。这个类基本上将成为服务器状态XML中每个服务器XML元素的包装器。为了使这个包装器更用户友好,让我们创建一些枚举。
public enum RealmPopulation
{
Low = 1,
Normal,
High,
Max
}
public enum RealmType
{
Normal = 1,
PvP,
RP,
RPPvP
}
这些枚举将在代码和数据的可视化呈现方面都对我们有所帮助。接下来,我们需要一个类来包装整个服务器状态元素。为此,我们将使用.NET的全新自动属性功能来创建一个类。
public class RealmStatus
{
public string Name { get; set; }
public RealmPopulation Population { get; set; }
public RealmType Type { get; set; }
public bool Up { get; set; }
}
现在我们有了代表WoW服务器状态的类,让我们继续获取XML并对其进行查询。
添加另一个类并将其命名为WowRealms
。在继续之前,我们需要在类的顶部添加一个using
语句。
using System.Xml.Linq;
接下来是我们的WowRealms
类,我将其设为静态,因为它不应该持有状态。
public static class WowRealms
{
}
现在我们需要创建一个方法,从我们之前使用的URL获取XDocument
。使用XLINQ,这实际上非常简单。
public static XDocument GetDocument()
{
return XDocument.Load("http://www.worldofwarcraft.com/realmstatus/status.xml");
}
是的,这真的是我们要做的所有事情,以获得我们之前看到的XML数据的有效XDocument
项。现在,有趣的开始了!
让我们创建一个方法,允许我们从服务器名称检索RealmStatus
对象。为此,我们需要使用LINQ to XML来查询元素列表并返回一个新的RealmStatus
对象。创建一个名为GetRealmStatusByName
的方法,该方法将返回一个RealmStatus
对象。
public static RealmStatus GetRealmStatusByName(string realmName)
{
XDocument document = GetDocument();
RealmStatus realmStatus =
(from s in document..Element("page").Element("rs").Elements("r")
where s.Attribute("n").Value == realmName
select new RealmStatus()
{
Name = s.Attribute("n").Value,
Population = (RealmPopulation)Enum.Parse
(typeof(RealmPopulation), s.Attribute("l").Value),
Type = (RealmType)Enum.Parse(typeof(RealmType),
s.Attribute("t").Value),
Up = s.Attribute("s").Value == "1"
}).SingleOrDefault();
return realmStatus;
}
最后,是一个内容丰富的<>方法。乍一看,这个方法可能看起来相当混乱,但老实说,它真的很简单。让我们来剖析一下这个方法。
XDocument document = GetDocument();
这真的是不言自明的,我们只是从status.xml文件中获取XML数据。
RealmStatus realmStatus = (from s in document.Element("rs").Elements("r")
where s.Attribute("n").Value == realmName
前两行是我们LINQ查询的开始。在第一行,我们通过获取“rs
”元素中的所有“r
”元素来设置查询。为了帮助可视化,让我们看一下XML文件。
<rs>
<r n="Aegwynn" t="2" s="1" l="1"/>
<r n="Aerie Peak" t="1" s="1" l="3"/>
<r n="Agamaggan" t="2" s="1" l="1"/>
<r n="Aggramar" t="1" s="1" l="3"/>
...
</rs>
如您所见,我们有根元素<rs>
和子元素<r>
。在LINQ查询的第二行,我们添加了一个where
表达式,它只查找名称与方法参数传递的服务器匹配的服务器。为了做到这一点,我们正在获取“r
”元素的“n
”属性。
select new RealmStatus()
{
Name = s.Attribute("n").Value,
Population = (RealmPopulation)Enum.Parse(typeof(RealmPopulation),
s.Attribute("l").Value),
Type = (RealmType)Enum.Parse(typeof(RealmType), s.Attribute("t").Value),
Up = s.Attribute("s").Value == "1"
}).SingleOrDefault();
LINQ查询的这一部分就像任何正常SQL查询中的SELECT
语句一样。我们将从每个元素中获取属性,并使用.NET对象初始化功能将属性值从我们的查询中插入。虽然这看起来我们只想获取一个服务器,但如果我们的查询返回的结果多于一个,此操作实际上将对返回的每个元素执行。查询的最后一行是对SingleOrDefault()
方法的调用,因为我们知道服务器名称只能有一个结果。
return realmStatus;
……并返回我们的结果!请注意——如果给定服务器名称没有结果,它将返回null
。
现在是时候测试我们的代码了!为此,让我们创建一个简单的界面,允许用户输入服务器名称进行搜索,并将一些输出显示给用户。我创建了一个非常基本的界面,看起来是这样的
在TextBox
下方还有一个Label
,我们可以用它来显示一些输出,您看不到它,因为它将其Text
属性设置为None
。现在我们已经创建了UI和代码基础,让我们为Button
的Click
事件添加一个事件处理程序,并向用户输出一些数据。
这是我在Button
的Click
事件处理程序中使用的代码
private void SearchButton_Click(object sender, EventArgs e)
{
string realmName = RealmNameTextBox.Text;
RealmStatus realmStatus = WowRealms.GetRealmStatusByName(realmName);
if (realmStatus == null)
{
OutputLabel.Text =
string.Format("No realm with name {0} was found", realmName);
}
else
{
string serverStatus = string.Empty;
if (realmStatus.Up)
{
serverStatus = "Up";
}
else
{
serverStatus = "Down";
}
OutputLabel.Text = string.Format("{0} ({1}) has a population of {2} and is {3}",
realmStatus.Name, realmStatus.Type, realmStatus.Population, serverStatus);
}
}
这段代码非常简单。您会注意到包装它使我们能够轻松使用它,以及我们的表单代码多么整洁。现在,我们有了一个有效的数据层和表示层……少说废话,让我们试试吧。
太棒了,它奏效了!
好了,我希望这能帮助那些想学习新技术的人,并且也许能引起一些游戏玩家的兴趣,让他们尝试编程。正如您所见,这段代码非常灵活且易于使用,不仅在Windows Forms中,而且在WPF、WCF、ASP.NET,甚至Vista小工具中!
我展示了.NET 3.5的一些全新技术,这些技术使当今的开发人员更具生产力和效率。这些功能包括:Base Class Library中的XLINQ、自动属性和对象初始化。这仅仅是冰山一角,我鼓励所有阅读本文的读者去购买/下载Visual Studio 2008/.NET 3.5 Express版本,并且不要局限于自己。
玩得开心!
关注点
- 在撰写本文时,Visual Studio 2008和.NET Framework 3.5都处于Beta 2阶段,而暴雪的《魔兽世界》拥有超过900万订阅用户。
历史
- 2007年11月2日:初始帖子。
- 2008年9月19日:编辑以反映XML更新,感谢btlife!