简介
随着办公室和家庭上网在线时间的延长,以及 Web 站点和可访问的互联网应用程序呈持续爆炸性增长,应用程序之间能数据共享变得越来越重要。在 异构平台之间共享数据需要一种平台中立的数据格式,这种数据格式要求能易于通过标准的互联网协议来传输,而这正是XML的用武之地。因为XML文件本质上 只是一个文本文件,其编码格式众所周知,而且现有的XML解析器能为所有主流编程语言所用,所以XML数据能被任何平台轻松使用。
Web 网站聚合就是一种使用 XML 来共享数据的范例,在新闻站点和网志中经常可以看到。采用 Web 网站聚合技术,网站能以 XML 格式的 Web 可访问的聚合文件来发布最新内容。网站使用 的聚合格式有很多种,其中最流行的一种格式就是 RSS2.0。( RSS2.0 规范被发布在 Harvard Law 网站 的技术栏目上)。此外,MSDN 杂志有一个聚合文件:MSDN杂志:本期刊物, 其中列出了最新一期 MSDN 杂志上的文章,包括到在线版本文章的链接。
一旦 Web 站点有了公开发布聚合文件,那么不同的客户端就可以消费它。消费聚合文件的方式有很多种,比如,某个提供 .NET 技术资源的站点可能希望在网站中 添加最新的 MSDN 杂志文章标题。聚合文件还常常被新闻聚合器程序所用,这种程序被专门设计用来获取和显示不同来源的聚合文件。
随着人们越来越注重使用 XML 数据,在 ASP.NET 页面中处理 XML 数据的能力变得比以往更关键。既然 Web 站点聚合如此重要, 本文我们就来创建一个 Web 站点聚合文件生成程序和一个在线新闻聚合器。在建立这两个微型程序的过程中,我们将讲述如何访问和显示XML数据,不论这些数据是来自远端的Web服务器还是本地的文件系统。我们将演示如 何多种不同的方法显示XML数据,比如:用 Repeater 控件以及用 ASP.NET XML Web控件。
使用 RSS 2.0 规范的聚合内容
本文我们将要创建的第一个微型程序是一个聚合文件生成器。针对这个迷你程序,假设你是一个大型新闻网站(如 MSNBC.com)的 Web 开发者,所有的新闻内容都保存在 Microsoft SQL Server 2000 数据库中。具体地说,这些文章是 都保存在一个名为 Articles 的表中,表中以下字段与我们的程序密切相关:
・ArticleID―主键,自增长的整型字段,用来唯一标识每一篇文章;
・Title― 指定标题,字段数据类型: varchar(50);
・Author―指定作者,字段数据类型: varchar(50);
・Description―新闻内容描述,字段数据类型: varchar(2000);
・DatePublished―新闻发布日期,字段数据类型:datetime
请注意,Articles 表中可能还有其它字段,上面所列的只是我们在创建聚合文件的时候所要用到的字段。而且,这只是一个非常简单的数据模型,在 是应用的数据库环境中,你可能会使用更加标准化的数据库模型,比如具备一个单独的 authors (作者)表,有一个建立作者和文章之间多对多关系的表等等。
下一步,我们将创建一个ASP.NET页面,用格式化好的 RSS2.0 XML 文件显示一个最新的新闻列表。在讲述如何在 ASP.NET 页面 中完成这种转换之前,我们要先介绍一下 RSS2.0 规范的内容。我们应该记住,在整个规范中,RSS 是被设计用来为聚合内容提供一个数据模型。那么 毫无疑问,它会有一系列的 XML 元素,用来描述 Web 站点要聚合的内容信息,以及一系列用来描述某一特定新闻项的 XML 元素。最后,不要忘记 RSS 聚合文件是一个 XML 格式文件,必须符合 XML 格式化的准则, 也就是:
・所有 XML元素必须正确嵌套;
・所有的属性值要用引号包含起来;
・<, >, &, "和’’符号要相应地替换为 <,>, &, " 和 ';
而且,XML格式是大小写敏感的,这就意味着,XML元素的起始和终止标签必须匹配,拼写和大小写都必须一致。
RSS2.0 的根元素是<rss>元素,这个元素可以有一个版本号的属性,例如:
<rss version="2.0"> ... </rss> |
<rss version="2.0"> <channel> <title>Latest DataWebControls.com FAQs</title> <link>http://datawebcontrols.com</link> <description> This is the syndication feed for the FAQs at DataWebControls.com </description> <item> <title>Working with the DataGrid</title> <link>http://datawebcontrols.com/faqs/DataGrid.aspx</link> <pubDate>Mon, 07 Jul 2003 21:00:00 GMT</pubDate> </item> <item> <title>Working with the Repeater</title> <description> This article examines how to work with the Repeater control. </description> <link>http://datawebcontrols.com/faqs/Repeater.aspx</link> <pubDate>Tue 08 Jul 2003 12:00:00 GMT</pubDate> </item> </channel> </rss> |
SELECT TOP 5 ArticleID,Title,Author,Description,DatePublished FROM Articles ORDER BY DatePublished DESC |
<%@ Page language="c#" ContentType="text/xml" Codebehind="rss.aspx.cs" AutoEventWireup="false" Inherits="SyndicationDemo.rss" %> <asp:Repeater id="rptRSS" runat="server"> <HeaderTemplate> <rss version="2.0"> <channel> <title>ASP.NET News!</title> <link>http://www.ASPNETNews.com/Headlines/</link> <description> This is the syndication feed for ASPNETNews.com. </description> </HeaderTemplate> <ItemTemplate> <item> <title><%# FormatForXML(DataBinder.Eval(Container.DataItem, "Title")) %></title> <description> <%# FormatForXML(DataBinder.Eval(Container.DataItem, "Description")) %> </description> <link> http://www.ASPNETNews.com/Story.aspx?ID=<%# DataBinder.Eval(Container.DataItem, "ArticleID") %> </link> <author><%# FormatForXML(DataBinder.Eval(Container.DataItem, "Author")) %></author> <pubDate> <%# String.Format("{0:R}", DataBinder.Eval(Container.DataItem, "DatePublished")) %> </pubDate> </item> </ItemTemplate> <FooterTemplate> </channel> </rss> </FooterTemplate> </asp:Repeater> |
![]() 图一 通过浏览器访问 Rss.aspx 页面 |
在我们生成在线新闻聚合器之前,让我谈谈这个聚合引擎一些可能的增强功能。首先,每一次访问 rss.aspx 页面的时候,都要访问一次数据库。如果预期可能有大量的人频繁地访问 rss.aspx 页面,使用输出缓存是很有价值的。其次,通常新闻网站会将聚合的内容分为不同的类别。例如:News.com 有一些专门的聚合内容区, 比如针对企业计算、电子商务、通信的内容等等。在数据库表 Articles 中加入表示类别的 Category 字段就可以很容易地提供这种支持。这样 一来,在 rss.aspx 页面中,可以接收一个表示显示分类的查询参数,然后只搜索指定的新闻项分类即可。
在 ASP.NET 页面中使用聚合摘要
为了测试我们刚建立的聚合引擎,我们将创建一个在线新闻聚合器,允许采集任意数量的聚合内容摘要。聚合器的界面很简单,参见图二。它包括三个框架页面。左边框架以列表形式列出了不同的聚合内容摘要。右上部框架显示所选的聚合内容摘要包含的新闻项以及查看该新闻项的链接。最后,在右下部框架则显示选中的新闻项标题和内容。顺便提及一下,这样的界面基本上是各种类型的聚合器的一个事实上的标准界面,包括新闻聚合器、email客户端软件和新闻组阅读器都是这样的界面。
![]() 图二 新闻聚合器用户界面的截图 |
![]() 图三 VS2003 中 Frameset 模版向导画面 |
<asp:DataGrid id="dgFeeds" runat="server" AutoGenerateColumns="False" ...> ... <Columns> <asp:HyperLinkColumn Target="rtop" DataNavigateUrlField="FeedID" DataNavigateUrlFormatString="DisplayNewsItems.aspx?FeedID={0}" DataTextField="Title" HeaderText="RSS Feeds"> </asp:HyperLinkColumn> </Columns> </asp:DataGrid> |
显示特定聚合摘要的新闻项
我们面临的下一个任务是创建 DisplayNewsItems.aspx 页面。这个页面会以链接的形式显示所选聚合摘要的新闻项标题,当点击标题时,新闻的内容就会显示在右下部分的框架中。要完成这一任务,我们会面临以下两个主要的挑战:
・通过指定的 URL 访问 RSS 聚合摘要;
・将接收到的 XML 数据转换为相应的 HTML;
幸运的是,在.NET 框架中,要实现这两个任务都不是很难。对于第一个任务,只需要两行代码,我们就可以将远程的xml数据装载到一个XmlDocument对象中。而第二个任务呢, 借助 ASP.NET XML Web 控件在ASP.NET 页面中显示XML数据也比较容易。
XML Web 控件被设计用于在 Web 页面中显示原始或者转换过的 XML 数据。使用 XML Web 控件的第一步是定义XML数据源,通过 定义一系列的属性,用许多方法都可以完成这一工作。使用 Document属性,你可以指定一个 XmlDocument 实例作为 XML Web 控件的 XML 数据源。如果XML数据存在于 Web 服务器文件系统的一个文件中,可以用 DocumentSource 属性,只要提供该 XML 文件的相对或者绝对路径就可以了。最后,如果你 的 XML数据是一个字符串,那么你可以将这个字符串的内容赋给控件的 DocumentContent 属性。这三种办法都可以将 XML 数据与 XML 控件联系起来。
通常,在将 XML 数据显示到 Web 页面之前,我们会以某种方式转换 XML 数据。XML Web 控件允许我们指定一个 XSLT 样式表来做这个转换工作。与 XML 数据相似,XSLT 样式表可以通过 两个属性之一,以两种不同的方式中的一种来设置,一是 Transform 属性可被赋值给 XslTransform 实例,二是将本地 Web 服务器上 XSLT文件的 相对或绝对路径赋予 TransformSource 属性。
现在我们来创建 DisplayNewsItems.aspx 页面。在添加 XML Web 控件以及编写后台代码类之前,我们需要在 HTML 部分加入一小段客户端 JavaScript 代码。准确地说,是在 html 部分的 <head> 标签里面 添加如下的<script>代码块:
<script language="javascript"> // display a blank page in the bottom frame when the news items loads parent.rbottom.location.href = "about:blank"; </script> |
<?xml version="1.0" encoding="UTF-8" ?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="html" omit-xml-declaration="yes" /> <xsl:template match="/rss/channel"> <b><xsl:value-of select="title" disable-output-escaping="yes" /></b> <xsl:for-each select="item"> <li> <a> <xsl:attribute name="href"> DisplayItem.aspx?ID=<xsl:number value="position()" /> </xsl:attribute> <xsl:attribute name="target">rbottom</xsl:attribute> <xsl:value-of select="title" disable-output-escaping="yes" /> </a> (<xsl:value-of select="pubDate" />) </li> </xsl:for-each> </xsl:template> </xsl:stylesheet> |
<a href="DisplayItem.aspx?ID=position">news item title</a> |
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="/rss/channel"> <xsl:param name="FeedID" /> ... </xsl:template> </xsl:stylesheet> |
<xsl:value-of select="$parameterName" /> |
<a> <xsl:attribute name="href">DisplayItem.aspx?ID=<xsl:number value="position()" />&FeedID=<xsl:value-of select="$FeedID" /></xsl:attribute> |
<?xml version="1.0" encoding="UTF-8" ?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="html" omit-xml-declaration="yes" /> <xsl:param name="ID" /> <xsl:template match="/rss/channel"> <b><xsl:value-of select="item[$ID]/title" disable-output-escaping="yes" /></b> <p> <xsl:value-of select="item[$ID]/description" disable-output-escaping="yes" /> </p> <a> <xsl:attribute name="href"><xsl:value-of select="item[$ID]/link" /></xsl:attribute> <xsl:attribute name="target">_blank</xsl:attribute> Read More... </a> </xsl:template> </xsl:stylesheet> |
未来的扩展和当前程序的缺点
本文讲述的代码中有一个明显的缺点就是每次用户点击左边框架的某个聚合摘要或者在右上部框架点击某个新闻项时,远程聚合摘要都会被装载和解析。每次用户点击远程聚合 摘要时,所有的项都被加载,这样的效率无疑是很差的。每次用户点击一个新闻项标题就重新装载整个远程聚合摘要也是很浪费资源的。这样的方法不仅没有效率,对提供发布服务的个人或者公司也是不礼貌的,因为这些 连续的、不没必要的请求占用了他们的 Web 服务器的负载资源。
这个缺点在本文附带的源代码中已经得到解决。具体来说,.NET数据缓存可以用来存放不同摘要的 XmlDocument 对象。缓存间隔设置为数据表 Feeds 中 UpdateInterval 字段定义的值。(当然,由于某些原因,摘要的 XmlDocument 对象有可能会被提前清除出缓存)
这个系统的另外一个缺点是在右上部框架和右下部框架之间没有状态的保存。为了说明这样会引起什么问题,考虑以下的动作:
・用户点击左边框架的某个聚合摘要链接,在右上部框架中装载这个摘要的新闻项目。假设这个摘要的UpdateInterval 的值是30,则表示这些内容在30分钟之 后会过期;
・装载右上部框架的新闻项的同时,这些内容被缓存起来;
・用户离开去吃午饭;
・发布聚合内容的网站增加了一条新的新闻项;
・我们的用户一个小时午饭后回来了,这个 摘要的 XmlDocument 的缓存已经过期;
・用户点击右上部框架的第一条新闻项,将会在右下部分框架中装载 DisplayItem.aspx,传入 ID 参数值1;
・DisplayItem.aspx 页面在缓存中没找到 XmlDocument 对象,只好重新获取远程摘要。这样就会获得新的数据了(别忘了,步骤 4 已经加了一个新的新闻项),然后此页面会显示第一条新闻项目(因为ID参数的值为1) ;
・用户看到了新的新闻项,但是内容会令他感到有点困惑,因为已经不是他所点击的那一条新闻了,而且右上部也没有显示那条新的新闻。
之所以出现这样的问题,是因为 ID 参数没有唯一地标识一个新闻项,它只是一个特定时间点上新闻项列表中的一个偏移量。解决这个问题的一个好的方法是不要用数据缓存来保存聚合 摘要,而是使用数据库或者持久介质的其它方式(比如 Web 服务器本地文件系统的 XML 文件)。如果使用数据库,每一个新闻项都可以拥有一个唯一的标识号,可以用来传递到右下角的框架中。这种方法可以保证解决上面提到的问题。当然也会增加系统的复杂性,比如需要决定何时从数据库中清除掉旧的新闻项 。
本文现有的应用程序还缺少异常处理,而这肯定是应该加上的。尤其是当从远程 RSS 聚合摘要文件获取数据并加载到 XmlDocument 对象时,应该加上异常处理。因为远程的文件可能不存在或者格式不正确。
还有很多增强功能可以轻松地加入到这个在线新闻聚合器。一个明显的功能是需要一个管理页面来允许用户添加,删除和编辑他们现在的聚合摘要。还有,如果能允许用户自定义分类 ,将他们的聚合摘要按类别放在一起就更好了。另外,现在的用户界面还是比较粗糙的,但是通过增加一些 XSLT 样式表生成的 HTML 代码或者在几个框架里面增加一些样式表就可以很容易地美化一下界面。最后,在html标签里面加一些<meta>元素,可以让右上部框架定时地去刷新,使得用户不用自己手工去刷新页面就可以看到最新的新闻项目。
注解 (2003年8月4日): 在这篇文章发布以后,一些读者用 Email 告诉通知我在显示特定 RSS 聚合项的 <description> 元素时,有两个潜在的问题:
1、Disable-output-encoding 属性,这个属性用在 <xsl:value-of> 元素中,但是并不是所有的 XSLT解析器都实现了这个功能。.NET XSLT 解析器支持 disable-output-encoding,但是还是要 注意一下,因为读者可能试图将这个应用程序移植到其它平台。
2、<description> 元素的 HTML 内容是被原封不动地输出的。但是,这些 HTML 内容可能包含恶意代码,比如 <script> 或者 <embed> 代码块。理想情况下,这些代码应该被剔除掉。为了清除掉这些有潜在危险的代码,可能需要用到一些扩展函数(参见 Extending XSLT with JScript, C#, and Visual Basic .NET)。想查看从 RSS 聚合 摘要剔除 HTML 内容的更多信息,可以参见’’Dive Into Mark’’ 日志.
总结
在本文中,我们不仅讲到如何创建一个聚合引擎,还创建了一个在线新闻聚合器。在建立这两个应用程序时,我们都采用了在 ASP.NET 页面显示 XML 数据的技术。在聚合引擎里面,我们使用了 Repeater 控件以 XML格式来显示数据库中的数据。而在新闻聚合器里面,我们使用了 XML Web 控件和 XSLT 样式表。
我邀请你下载本文的在线新闻聚合器,然后根据你的需要来增强它。如果有任何关于这个应用程序或者这篇文章讨论的概念方面的问题,随时恭候你的 EMail。我的 EMail mitchell@4guysfromrolla.com.