ASP.NET建立在线RSS聚合阅读器详解

编程乐园

Web网站聚合是一种使用XML来共享数据的范例,在新闻站点和Blog中经常可以看到。采用Web网站聚合技术,网站能以XML格式的Web可访问的聚合文件来发布最新内容。网站使用的聚合格式有很多种,其中最流行的一种格式就是RSS。

在本文中,我们不仅讲到如何创建一个聚合引擎,还创建了一个在线新闻聚合器。在建立这两个应用程序时,我们都采用了在ASP.NET页面显示XML数据的技术。在聚合引擎里面,我们使用了Repeater控件以XML格式来显示数据库中的数据。而在新闻聚合器里面,我们使用了XML Web控件和 XSLT 样式表。

一、RSS新闻聚合器基础

1.什么是RSS

RSS,即Really Simple Syndication,是站点用来和其他站点之间共享内容的一种简易方式(也叫聚合内容),通常被用于新闻和其他按顺序排列的网站,例如,Blog。网络用户可以在客户端借助于支持RSS的新闻聚合工具软件,在不打开网站内容页面的情况下阅读支持RSS输出的网站内容。说得更简单一点,RSS就是一种用来分发和汇集网页内容的XML格式!

提示:Blog是Web Log的简称。在中国,人们通常称它为博客。它是一种作者与读者以日记风格进行交互的中介。例如,http://blogs.msdn.com就是MSDN上的一个blogging。XML即eXtensible Markup Language,一种扩展性标识语言。

2.RSS可以干什么

(1)订阅Blog。在Blog上,你可以订阅你工作中所需的技术文章;也可以订阅与你有共同爱好的作者的日志。

(2)订阅新闻。无论是奇闻怪事、明星消息、体坛风云,只要你想知道的,都可以订阅。

你再也不用一个网站一个网站,一个网页一个网页去逛了。只要将你需要的内容订阅在一个RSS阅读器中,这些内容就会自动出现你的阅读器里,你也不必为了一个急切想知道的消息而不断的刷新网页,因为一旦有了更新,RSS阅读器就会自动通知你!

目前,RSS阅读器基本可以分为两类。第一类阅读器大多是运行在计算机桌面上的单机应用程序,通过所订阅网站和Blog中的新闻供应,可自动、定时地更新新闻标题。在该类阅读器中。第二类大多是新闻阅读器,它通常将自己内嵌在运行的应用程序中。例如,NewsGator内嵌在微软的Outlook中,所订阅的新闻标题位于Outlook的收件箱文件夹中。

3.RSS的联合(Syndication)和聚合(Aggregation)

发布一个RSS文件(一般称为RSS Feed)后,这个RSS Feed中包含的信息就能直接被其他站点调用,而且由于这些数据都是标准的XML格式,所以也能在其他的终端和服务中使用,例如,PDA、手机、邮件列表等。而且一个网站联盟(例如,专门讨论旅游的网站系列)也能通过互相调用彼此的RSS Feed,自动显示网站联盟中其他站点上的最新信息,这就叫着RSS的联合。这种联合就导致一个站点的内容更新越及时、RSS Feed被调用得越多,该站点的知名度就会越高,从而形成一种良性循环。

而所谓RSS聚合,就是通过软件工具的方法从网络上搜集各种RSS Feed并在一个界面中提供给读者进行阅读。这些软件可以是在线的Web工具,例如,http://my.netscape.com、http://my.userland.com等,也可以是下载到客户端安装的工具。

二、使用RSS 2.0规范的聚合内容

下面,我们将创建一个聚合文件生成器。针对这个程序,假设你是一个大型新闻网站(例如,MSNBC.com)的Web开发者,所有的新闻内容都保存在Microsoft SQL Server 2000数据库中。具体地说,这些文章都保存在一个名为Articles的表中,表中以下字段与我们的程序密切相关:

029.jpg

请注意,Articles表中可能还有其他字段,上面所列的只是我们在创建聚合文件时所要用到的字段。而且,这只是一个非常简单的数据模型,在具体的数据库环境中,你可能会使用更加标准化的数据库模型,例如,具备一个单独的Authors(作者)表,有一个建立作者和文章之间多对多关系的表等。

下一步,我们将创建一个ASP.NET页面,用格式化好的RSS 2.0 XML文件显示一个最新的新闻列表。在介绍如何在ASP.NET页面中完成这种转换之前,我们要先介绍一下RSS 2.0规范的内容。我们应该记住,在整个规范中,RSS是被设计用来为聚合内容提供一个数据模型。那么毫无疑问,它会有一系列的XML元素,用来描述Web站点要聚合的内容信息,以及一系列用来描述某一特定新闻项的XML元素。最后,不要忘记RSS聚合文件是一个XML格式文件,必须符合XML格式化的准则,即是:

(1)所有XML元素必须正确嵌套;

(2)所有的属性值要用引号包含起来;

(3)<、>、&、"和''符号要相应地替换为&lt;、&gt;、&amp;、&quot;和&apos;);

(4)XML格式是大小写敏感的,这就意味着,XML元素的起始和终止标签必须匹配,拼写和大小写都必须一致。

RSS 2.0的根元素是元素,这个元素可以有一个版本号的属性,例如:

<rss version="2.0">

...

</rss>

<rss>元素只有一个子元素,用来描述聚合的内容。在元素里面有三个必需的子元素,用来描述Web站点的信息。这3个元素是:

(1)Title:定义聚合文件的名称,一般来说,还会包括Web站点的名称;

(2)Link:Web站点的URL;

(3)Description:Web站点的一段简短的描述。

除此之外,还有一些可选元素来描述站点信息。这些元素的更多信息请参见RSS 2.0规范。

每一个新闻项目放在一个单独的<item>元素中。<channel>元素可以有任意数量的<item>元素。每个<item>元素可以有多种的子元素,唯一的要求是最少必须包含<title>元素和<description>元素其中一个作为子元素。以下列出了一些相关的<item>子元素:

(1)Title:新闻项目的标题;

(2)Link:新闻项目的URL;

(3)Description:新闻项目的大纲;

(4)Author:新闻项目的作者;

(5)pubDate:新闻项目的发布日期。

下面是一个非常简单的RSS 2.0聚合文件:

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

关于<pubDate>元素的格式有一点特别重要,RSS要求日期必须按照RFC822日期和时间规范进行格式化,此格式要求开头是一个可选的3字母星期缩写加一个逗号,接着必须是日加上3字母缩写的月份和年份,最后是一个带时区名的时间。另外,要注意<description>子元素是可选的,上述文件第一个新闻没有元素,而第二个新闻就有一个。

三、通过ASP.NET页面输出聚合内容

现在,我们已经知道了如何按照RSS 2.0规范存储新闻项,我们已经创建了一个ASP.NET页面,当用户发出请求时,就会返回网站聚合的内容。更确切地说,我们将建立一个名字叫rss.aspx的ASP.NET页面,这个页面会按照RSS 2.0规范的格式返回Articles数据库表中的最新的5个新闻项。

可以有几种方法来完成这件事,稍后将会讲到。但是现在,我们首先要完成一件事,那就是先要从数据库中获得最新的5个新闻项。这可以用下面的 SQL 查询语句获得:

SELECT TOP 5 ArticleID,Title,Author,Description,DatePublished FROM Articles ORDER BY DatePublished DESC

获得了这些信息以后,我们需要把这些信息转换成相应的RSS 2.0格式聚合文件。要把数据库的数据显示为XML数据最简单、快速的方法就是使用Repeater控件。准确地说,Repeater控件将在HeaderTemplate和FooterTemplate模版里显示<rss>元素、<channel>元素以及站点相关的元素标签,在ItemTemplate模版里面显示<item>元素。下面是我们这个ASP.NET页面(.aspx文件)的HTML部分:

<%@ 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>

首先要注意的是,上面这段代码只包括Repeater控件,没有其他HTML标记或Web控件。这是因为我们只希望页面输出XML格式的数据。实际上,观察一下@Page指令,你就会发现ContentType被设置为XML MIME类型(text/xml)。

其次要注意的是,在ItemTemplate模版里,当在XML输出中添加数据库字段Title、Description和Author时,我们调用了辅助函数FormatForXML()。我们很快就会看到,该函数被定义在后台编码的类中,其作用只是将非法的XML字符替换为它们对应的合法的转义字符。

最后我们应该注意,在元素里面的数据库字段DatePublished是用String.Format来格式化的。标准的格式描述符“R”对DatePublished的值进行相应的格式化。

此Web页面的后台编码类代码并不复杂。Page_Load事件处理函数只是将数据库查询结果绑定到Repeater控件,FormatForXML()函数根据需要做一些简单的字符串替换。为简单起见,下面的例子只列出了这两个函数的代码:

private void page_load (object sender,System.EventArgs e)

{

//连接到数据库

SqlConnection myConnection = new SqlConnection (connection string);

//接受SQL查询结果并绑定到Repeater

string SQL_QUERY = "SELECT TOP 5 ArticleID,Title,Author," +"Description,DatePublisher" +"FROM Articles ORDER BY DatePublis hed DESC";

SqlCommand myCommand = new SqlCommand (SQL_QUERY,myConnection);

//绑定搜索结果到Repeater

myConnection.open ();

rptRSS.DataSource = myCommand.ExecuteReader ();

rptRss.DataBind ();

myConnection.Close ();

}

protected string FormatForXML (object input)

{

String data = input.ToString (); //转换为String类型

//将下面这些在XML中不接受的字符替换掉

Data = data.Replace ("&", "&");

Data = data.Replace ("\", """);

Data = data.Replace ("'", "'");

Data = data.Replace ("<", "<");

Data = data.Replace (">", ">");

return data;

}

在生成在线新闻聚合器之前,让我们来谈谈这个聚合引擎一些可能的增强功能。首先,每一次访问rss.aspx页面时,都要访问一次数据库。如果预期可能有大量的人频繁地访问rss.aspx页面,使用输出缓存是很有价值的。其次,通常新闻网站会将聚合的内容分为不同的类别。例如,News.com有一些专门的聚合内容区,针对企业计算、电子商务、通信的内容等。在数据库表Articles中加入表示类别的Category字段就可以很容易地提供这种支持。这样一来,在rss.aspx页面中,可以接收一个表示显示分类的查询参数,然后只搜索指定的新闻项分类即可。

四、ASP.NET页面中使用聚合摘要

为了测试我们刚建立的聚合引擎,我们将创建一个在线新闻聚合器,允许采集任意数量的聚合内容摘要。

1.聚合器界面设计

新闻聚合器界面包括3个框架页面。左边框架以列表形式列出了不同的聚合内容摘要。右上部框架显示所选的聚合内容摘要包含的新闻项以及查看该新闻项的链接。最后,在右下部框架则显示选中的新闻项标题和内容。

rss1.jpg
RSS阅读器——新浪点点通

第一步,我们需要创建一个HTML页面来建立用户界面。在Visual Studio.NET中你只需要在Web应用程序解决方案中添加一个新的项目,选择新项目类型为Frameset。在工程中将这个新文件命名为NewsAggregator.htm,之所以将它设置为HTML文件而不是ASP.NET页面,是因为这个页面只包括建立框架的HTML代码。每一个单独的框架会显示一个ASP.NET页面。

下一步,启动Frameset模版向导,从中选择“Nested Hierarchy”选项,然后单击“OK”按钮即可。然后,Frameset模版向导会创建一个HTML页面,里面已经加入了框架的源代码。你只要将左边框架的src属性设置为DisplayFeeds.aspx,它是列表显示聚合摘要ASP.NET页面的URL。至此完成NewsAggreator.htm页面。

rss2 拷贝.jpg
Frameset模版向导

以下3个部分,我们将讲述如何创建在线新闻聚合器的3个组件,它们分别是显示聚合摘要列表的DisplayFeeds.aspx;显示特定聚合摘要新闻项的DisplayNewsItems.aspx;以及显示指定聚合摘要特定新闻项具体内容的DisplayItem.aspx。

2.显示聚合摘要列表

现在我们需要创建DisplayFeeds.aspx页面。该页面要显示订阅的聚合摘要列表。作为示范,我们将这些聚合摘要放在一个叫Feeds的数据库表中。当然你也可以将它们放在一个XML文件中。表Feeds有如下4个字段:

030.jpg

DisplayFeeds.aspx页面使用一个DataGrid控件显示聚合摘要的列表。这个DataGrid只有一个HyperLinkColumn列,显示Title字段的内容并且链接到DisplayNewsItems.aspx页面,在查询字符串中要传递FeedID字段的值。以下是DataGrid控件的声明,为简单起见,省略了一些无关的代码):

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

这里要注意的是,HyperLinkColumn列的定义。它的Target属性设置为右上部分框架的名称,这样当用户点击时,DisplayNewsItems.aspx页面就会显示在右上部分的框架中。另外,属性DataNavigateUrlField、DataNavigateUrlFormatString和DataTextField也做了相应的设置,以便超链接显示摘要的标题,并且当点击它时,就会将用户带到DisplayNewsItems.aspx页面,并在查询串中将FeedID字段的内容传过来。

该页面的后台代码类只访问来自Feeds表的摘要清单,按照Title字段的字母顺序返回,接着将查询结果绑定到DataGrid控件。由于篇幅所限,本文在此不列出代码。

3.显示特定聚合摘要的新闻项

我们面临的下一个任务是创建DisplayNewsItems.aspx页面。这个页面会以链接的形式显示所选聚合摘要的新闻项标题,当点击标题时,新闻的内容就会显示在右下部分的框架中。要完成这一任务,我们会面临以下两个主要的挑战:

(1)通过指定的URL访问RSS聚合摘要;

(2)将接收到的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">

// 当新闻被加载时,在底部显示一个空白的页面

parent.rbottom.location.href = "about:blank";

</script>

每当DisplayNewsItems.aspx页面装载时,这段客户端JavaScript代码会在右下角的框架中显示一个空白页。为了理解为什么要加入这段代码,我们来看看省略这段代码,我们会碰到什么情况:

(1)用户在左边的框架中点击聚合摘要,浏览器会在右上部的框架中装载摘要新闻项;

(2)用户在右上部框架中点击某个新闻项,浏览器会在右下部框架中装载这个新闻项 的详细内容;

(3)用户在左边的框架中点击其他的聚合摘要,浏览器会在右上部分的框架中装载新的摘要新闻项。

现在,前一个新闻项的详细内容还显示在右下部的框架中!通过上面的客户端Javascript代码,每次点击左面框架的摘要便可以清除右下部框架 的内容,以消除这一瑕疵。

当处理了客户端代码的问题之后,让我们把注意力转到添加XML Web控件。一旦加入XML Web控件,将其ID属性设置为xsltNewsItems,TransformSourc属性设置为NewsItems.xslt(我们将要创建的XSLT样式表文件的名称)。现在,在Page_Load事件处理函数中,我们需要在某个 XmlDocument实例中获取远程RSS聚合文件,然后将该XML Web控件的Document属性赋给该XmlDocument实例。

private void Page_Load (object sender,System.EventArgs e)

{

//查看新闻种子是否在Data缓存中

int feedID = Int32.Parse (Request.QueryString ["FeedID"]);

//连接到数据库,查找到RSS的URL连接

SqlConnection myConnection = new Sqlconnection (connection string);

//接受远程RSS文件的SQL URL

string SQL_QUERY = "SELECT URL,UpdateIntervql FROM

Feeds" +"WHERE FeedID = @Feed

ID";

SqlCommand myCommand = new SqlCommand(SQL QUERY,)

myConnection);

SqlParameter feedParam = new SqlParameter ("@FeedID",

SqlDbtype.Int,4);

feedParem.Value = feedID;

myCommand.Parameters.Add (feedParam);

myConnection.Open ();

string feedURL = myCommand.ExecuteScalar ().ToString ()

myConnection.Close ();

// 现在我们获得了种子的URL地址并载入到一个XML文档中

XmlDocument feedXML = new XmlDocument ();

feedXML.Load (feedURL);

xmlNewsItems.Document = feedXML;

}

在Page_Load事件处理函数中,与我们要实现的任务有密切关系的代码是最后三行代码。这三行代码创建一个新的XmlDocument对象,加载远程RSS摘要内容,然后将这个XmlDocument对象赋给XML Web控件的Document属性。访问远程XML数据并将它们显示在ASP.NET页面中就是这么简单,难道给你留下的印象不深吗?

最后要做的就是创建XSLT样式表NewsItems.aspx。下面是样式表的第一版的草稿:

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

这个XSLT样式表只有一个模版,用于匹配“/rss/channel”XPath表达式。这个模版先是以粗体显示<title>元素的内容。然后,循环获取每一个<item>元素,对于每一个元素,显示一个到DisplayItem.aspx页面的超链接,在查询字符串中传递<item>元素的位置属性。要留意超链接的target属性设置为rbottom,右下部框架的名称。最后,显示新闻项的标题和<pubDate>元素。

该XSLT样式表中有两个项目,并不是每个人都熟悉。首先是<xsl:value-of>元素中的disable-output-escaping="yes"属性。从本质上讲,这个属性的设置通知XSLT引擎不要转义那些非法的XML字符,例如,&、&lt;、&gt;、"和"。为了理解这个设置的意义,就要知道,如果不设置该属性(也就是设置为默认值“no”),那么如果标题包含一个转义的&字符&amp;,那么输出的HTML文件中也会有一个&amp;,而不只是一个字符&。如果你再仔细想一想,你会发现这种情况会导致很多问题。例如,假设一个聚合文件的标题是“Matt''s &lt;i&gt;Cool&lt;/i&gt; Blog”,如果输出转义没有被禁止,那么输出就会保留“Matt''s &lt;i&gt;Cool&lt;/i&gt; Blog”,在Web页面就会显示为 "Matt''s <i>Cool</i> Blog"。当用disable-output-escaping="yes"设置禁止输出转义时,输出就不会被转义,上面的内容就会被当作“Matt''s <i>Cool</i> Blog”,显示在页面上就是我们想要的“Matt''s Cool Blog”。

另一个要注意的是元素<a>。这个奇怪的语法会生成下面的输出内容:

<a href="DisplayItem.aspx?ID=position">news item title</a>

之所以要使用这种语法,是因为要给XSLT样式表中某个你要创建的元素添加一个属性,然后在该元素的标签里使用<xsl:attribute>语法。有关该语法的一些例子可在W3Schools网站上找到:The <xsl:attribute> Element。

最后要注意的是,超链接的ID查询字符串的值是来自于<xsl:number>元素,从position()函数中返回的值。<xsl:number>元素仅仅是输出一个数值。position()函数是一个XPath函数,用来返回XML文档中当前节点的顺序位置。这意味着对于第一个新闻项,position()函数返回1,第二个新闻项,position函数返回2,以此类推。我们需要记录这个值并将它通过查询字符串传递出去。这样当DisplayItem.asp页面被访问时,就可以知道显示RSS聚合摘要的什么项目了。

可能你已经注意到,我们的XSLT样式表没有全部完成,因为FeedID参数没有通过查询字符串传递到DisplayItem.aspx页面。要明白这是为什么,我们回顾一下在ID查询串参数中所传递的是用户拟查看详细信息的<item>元素顺序号。也就是说,如果用户点击第四条新闻项,页面DisplayItem.aspx?ID=4就会被加载到右下部分的框架中。问题在于DisplayItem.aspx页面无法确定用户希望查看哪一个摘要。有两个不同的方法可以解决这个问题,例如,可以在右下部框架中用客户端Javascript代码读取右上部框架的URL,然后确定FeedID的值。其实,更简单的办法是和ID参数一起将FeedID的值通过查询字符串传递。

这样的话,有一个难题是XSLT样式表操纵的RSS XML数据中并没有FeedID值。但是DisplayNewsItems.aspx页面知道FeedID值,需要一种方法让XSLT样式表也知道这个值。通过使用XSLT参数可以实现完成。

XSLT参数的使用是非常简单。在XSLT样式表中,你需要在<xsl:template>元素中加入一个<xsl:param>元素,该元素提供参数的名称。下面的代码将这个参数命名为FeedID:

<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>元素中使用这个参数了:

<xsl:value-of select="$parameterName" />

最后,在我们的XSLT样式表中加入下面的代码,就可以把FeedID查询字符串参数加到超链接中了:

<a>

<xsl:attribute name="href">DisplayItem.aspx?ID=<xsl:number value="position()" />&FeedID=<xsl:value-of select="$FeedID" /></xsl:attribute>

需要注意的是,在ID查询字符串参数后面加了一个&字符(转义&amp;),我们就可以传递FeedID参数的值到查询字符串的FeedID参数中。这就是我们要在XSLT样式表中添加的内容。

剩下的工作是在DisplayNewsItems.aspx页面的Page_Load事件处理函数中设置这个参数的值。通过使用XsltArgumentList类可以完成这一工作。这个类有一个AddParameter( )方法。一旦我们创建了这个类的一个实例,加入了相应的参数,就可以将这个实例赋给XML Web控件的TransformArgumentList参数了。下面的代码显示了更新后的DisplayNewsItems.aspx页面Page_Load事件处理函数:

private void Page_Load (object sender,System.EventArgs e)

{

...

//获取了种子的URL地址并载入到XML文档中

XmlDocument feedXML = new XmlDocument ();

feedXML.Load (feedURL);

xmlNewsItems.Document = feedXML;

//添加FeedID 参数到XSLT样式表中

XsltArgumentList xsltArgList = new XsltArgumentList ();

xsltArgList.AddParam<"FeedID","",feedID>;

xmlNewsItems.TransformArgumentList = xsltArgList;

}

4.显示特定新闻项的详细内容

还剩下最后一件需要做的事情是显示用户选择的特定新闻项的详细内容。这些详细内容将显示在右下部的框架中,而且将会显示新闻项的标题、描述和新闻项的链接等信息。和DisplayNewsItem.aspx页面类似,DisplayItem.aspx页面首先将根据传入的FeedID查询字符串参数获取远程的RSS聚合摘要,然后它会用XML Web控件显示这些详细内容。实际上,DisplayItem.aspx页面的Page_Load事件处理函数和DisplayNewsItem.aspx页面的该函数几乎一样,只有以下两个小小的区别:

(1)DisplayItem.aspx页面需要读取ID查询字符串参数的值;

(2)DisplayItem.aspx页面使用一个XSLT参数,但是这个参数与DisplayNewsItem.aspx页面用的参数是不一样的。

DisplayNewsItem.aspx和DisplayItem.aspx页面一样都需要在参数中传递一个XSLT样式表。DisplayNewsItem.aspx页面传递的是参数FeedID,而DisplayItem.aspx还需要传入ID参数,它表示XSLT样式表应该显示那个新闻项。这个细小的差别在以下代码中以粗体显示,以下代码省略了与DisplayNewsItems.aspx页面相同的部分:

private void Page_Load (object sender,System.EventArgs e)

{

// 查看新闻种子是否在Data缓存中

int feedID = Int32.Parse (Request.QueryString ["FeedID"]);

int ID = Int32.Parse (Request.QueryString ["ID"]);

...

//获取了种子的URL地址并载入到XML文档中

XmlDocument feedXML = new XmlDocutment ();

feedXML.Load (feedURL);

xmlNewsItems.Document = feedXML;

this.xmlItem.Document = feedXML'

//添加ID参数到XSLT样式表中

XsltArgumrntList xsltArgList = new XsltArgumentList ();

xsltArgList.AddParam ("ID", "", ID);

xmlItem.TransformArgumentList = xsltArgList;

}

以下是转换XML数据的XSLT样式表:

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

需要注意的是,<xsl:param>元素被用于声明ID XSLT参数。然后,在几个不同的<xsl:value-of>元素中,ID参数被用来从<item>元素列表中抓取特定的<item>元素。在XPath的语法中,elementName[i]意思是根据相应元素名存取第i个元素。例如,item[1]将只获取第一个<item>元素,item[2]则获取第二个元素。所以,item[$ID]是获取由XSLT参数ID定义的特定<item>元素。

最后,值得注意的还有在样式表靠近末尾部分的超链接Read More…,它的target属性设为空,当用户点击Read More…链接时,浏览器会打开一个新的窗口。

五、未来的扩展和当前程序的缺点

本文讲述的代码中有一个明显的缺点就是每次用户点击左边框架的某个聚合摘要或者在右上部框架点击某个新闻项时,远程聚合摘要都会被装载和解析。每次用户点击远程聚合摘要时,所有的项都被加载,这样的效率无疑是很差的。每次用户点击一个新闻项标题就重新装载整个远程聚合摘要也是很浪费资源的。这样的方法不仅没有效率,对提供发布服务的个人或者公司也是不礼貌的,因为这些连续的、不没必要的请求占用了他们的Web服务器的负载资源。

要解决这一缺点,你可以这样处理:.NET数据缓存可以用来存放不同摘要的XmlDocument对象。缓存间隔设置为数据表Feeds中UpdateInterval字段定义的值。(当然,由于某些原因,摘要的XmlDocument对象有可能会被提前清除出缓存)

这个系统的另外一个缺点是在右上部框架和右下部框架之间没有状态的保存。为了说明这样会引起什么问题,考虑以下的动作:

(1)用户点击左边框架的某个聚合摘要链接,在右上部框架中装载这个摘要的新闻项目。假设这个摘要的UpdateInterval的值是30,则表示这些内容在30分钟之后会过期;

(2)装载右上部框架新闻项的同时,这些内容被缓存起来;

(3)用户离开去吃午饭;

(4)发布聚合内容的网站增加了一条新的新闻项;

(5)我们的用户一个小时午饭后回来了,这个摘要的XmlDocument的缓存已经过期;

(6)用户点击右上部框架的第一条新闻项,将会在右下部分框架中装载DisplayItem.aspx,传入ID参数值1;

(7)DisplayItem.aspx页面在缓存中没找到XmlDocument对象,只好重新获取远程摘要。这样就会获得新的数据(别忘了,上面我们已经加了一个新的新闻项),然后此页面会显示第一条新闻项目(因为ID参数的值为1);

(8)用户看到了新的新闻项,但是内容会令他感到有点困惑,因为已经不是他所点击的那一条新闻了,而且右上部也没有显示那条新的新闻。

之所以出现这样的问题,是因为ID参数没有唯一地标识一个新闻项,它只是一个特定时间点上新闻项列表中的一个偏移量。解决这个问题的最好的方法是不要用数据缓存来保存聚合摘要,而是使用数据库或者持久介质的其他方式(例如,Web服务器本地文件系统的XML文件)。如果使用数据库,每一个新闻项都可以拥有一个唯一的标识号,可以用来传递到右下角的框架中。这种方法可以保证解决上面提到的问题。当然也会增加系统的复杂性,例如,需要决定何时从数据库中清除掉旧的新闻项 。

本文现有的应用程序还缺少异常处理,而这肯定是应该加上的。尤其是当从远程RSS聚合摘要文件获取数据并加载到XmlDocument对象时,应该加上异常处理。因为远程的文件可能不存在或者格式不正确。

还有很多增强功能可以轻松地加入到这个在线新闻聚合器。一个明显的功能是需要一个管理页面来允许用户添加,删除和编辑他们现在的聚合摘要。还有,如果能允许用户自定义分类 ,将他们的聚合摘要按类别放在一起就更好了。另外,现在的用户界面还是比较粗糙的,但是通过增加一些XSLT样式表生成的HTML代码或者在几个框架里面增加一些样式表就可以很容易地美化一下界面。最后,在HTML标签里面加一些<meta>元素,可以让右上部框架定时地去刷新,使得用户不用自己手工去刷新页面就可以看到最新的新闻项目。