<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>W(wǎng)orking 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>W(wǎng)orking 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="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> 
在我們生成在線(xiàn)新聞聚合器之前,讓我談?wù)勥@個(gè)聚合引擎一些可能的增強功能。首先,每一次訪(fǎng)問(wèn) rss.Aspx 頁(yè)面的時(shí)候,都要訪(fǎng)問(wèn)一次數據庫。如果預期可能有大量的人頻繁地訪(fǎng)問(wèn) rss.Aspx 頁(yè)面,使用輸出緩存是很有價(jià)值的。其次,通常新聞網(wǎng)站會(huì )將聚合的內容分為不同的類(lèi)別。例如:News.com 有一些專(zhuān)門(mén)的聚合內容區, 比如針對企業(yè)計算、電子商務(wù)、通信的內容等等。在數據庫表 Articles 中加入表示類(lèi)別的 Category 字段就可以很容易地提供這種支持。這樣 一來(lái),在 rss.Aspx 頁(yè)面中,可以接收一個(gè)表示顯示分類(lèi)的查詢(xún)參數,然后只搜索指定的新聞項分類(lèi)即可。
[dvnews_page=用Asp.NET建立一個(gè)在線(xiàn)RSS新聞聚合器]
在 Asp.NET 頁(yè)面中使用聚合摘要
為了測試我們剛建立的聚合引擎,我們將創(chuàng )建一個(gè)在線(xiàn)新聞聚合器,允許采集任意數量的聚合內容摘要。聚合器的界面很簡(jiǎn)單,參見(jiàn)圖二。它包括三個(gè)框架頁(yè)面。左邊框架以列表形式列出了不同的聚合內容摘要。右上部框架顯示所選的聚合內容摘要包含的新聞項以及查看該新聞項的鏈接。最后,在右下部框架則顯示選中的新聞項標題和內容。順便提及一下,這樣的界面基本上是各種類(lèi)型的聚合器的一個(gè)事實(shí)上的標準界面,包括新聞聚合器、email客戶(hù)端軟件和新聞組閱讀器都是這樣的界面。

圖二 新聞聚合器用戶(hù)界面的截圖
第一步是創(chuàng )建一個(gè)html頁(yè)面來(lái)建立框架用戶(hù)界面。幸運的是,在Visual Studio.NET 2003 中,這一過(guò)程非常容易。只需要在Web應用程序解決方案中添加一個(gè)新 的項目,選擇新項目類(lèi)型為 Frameset。(我在我的工程中將這個(gè)新文件命名為 NewsAggregator.htm。我之所以將它設置為 html 文件而不是 Asp.net 頁(yè)面, 是因為這個(gè)頁(yè)面只包括建立框架的 html 代碼。每一個(gè)單獨的框架會(huì )顯示一個(gè) Asp.net 頁(yè)面)。下一步,參見(jiàn)圖三,Frameset 模版向導會(huì )啟動(dòng),簡(jiǎn)單地選擇選項“Nested Hierarchy”,然后按ok按鈕就可以了。

圖三 VS2003 中 Frameset 模版向導畫(huà)面
然后 Frameset 模版向導會(huì )創(chuàng )建一個(gè)HTML頁(yè)面,里面已經(jīng)加入了框架的源代碼。 只要將左邊框架的src屬性設置為 DisplayFeeds.Aspx,它是列表顯示聚合摘要 Asp.net 頁(yè)面的 url。至此 NewsAggreator.htm 頁(yè)面就完成了。
以下三個(gè)部分,我們將講述如何創(chuàng )建在線(xiàn)新聞聚合器的三個(gè)組件,它們分別是顯示聚合摘要列表的 DisplayFeeds.Aspx;顯示特定聚合摘要新聞項 的 DisplayNewsItems.Aspx;以及顯示指定聚合摘要特定新聞項具體內容的 DisplayItem.Aspx。
顯示聚合摘要列表
現在我們需要創(chuàng )建 DisplayFeeds.Aspx 頁(yè)面。該頁(yè)面要顯示訂閱的聚合摘要列表。作為示范,我將這些聚合摘要放在一個(gè)叫 Feeds 的數據庫表中。當然你也可以將它們放在一個(gè)XML文件中。表 Feeds 有如下四個(gè)字段:
·FeedID—主鍵,自增長(cháng)整數類(lèi)型,唯一標示一個(gè)摘要
·Title—摘要名稱(chēng),數據庫字段類(lèi)型:varchar(50)
·URL—RSS 摘要的 URL,數據庫字段類(lèi)型:varchar(150)
·UpdateInterval—摘要更新頻率(分鐘),數據庫字段類(lèi)型:int
DisplayFeeds.Aspx 頁(yè)面使用一個(gè) DataGrid 控件顯示聚合摘要的列表。這個(gè) DataGrid 只有一個(gè) HyPerlinkColumn 列,顯示 Title 字段的內容并且鏈接到 DisplayNewsItems.Aspx 頁(yè)面, 在查詢(xún)字符串中 要傳遞 FeedID 字段的值。以下是 DataGrid 控件的聲明,為簡(jiǎn)單起見(jiàn),省略了一些無(wú)關(guān)的部分): <Asp:DataGrid id="dgFeeds" runat="server"
AutoGenerateColumns="False" ...>
...
<Columns>
<Asp:HyPerlinkColumn Target="rtop"
DataNavigateUrlField="FeedID"
DataNavigateUrlFormatString="DisplayNewsItems.DataTextField="Title" HeaderText="RSS Feeds">
</Asp:HyPerlinkColumn>
</Columns>
</Asp:DataGrid>
這里要注意的關(guān)鍵是 HyPerlinkColumn 列的定義。它的 Target 屬性設置為右上部分框架的名稱(chēng),這樣當用戶(hù)點(diǎn)擊的時(shí)候,DisplayNewsItems.Aspx 頁(yè)面就會(huì )顯示在右上部分的框架中。另外, 屬性 DataNavigateUrlField、DataNavigateUrlFormatString 和 DataTextField 也做了相應的設置, 以便超鏈接顯示摘要的標題,并且當點(diǎn)擊它時(shí),就會(huì )將用戶(hù)帶到 DisplayNewsItems.Aspx 頁(yè)面,并在查詢(xún)串中將 FeedID 字段的內容傳 過(guò)來(lái)。(該頁(yè)面的后臺代碼類(lèi)只訪(fǎng)問(wèn)來(lái)自 Feeds 表的摘要清單,按照 Title 字段的字母順序返回,接著(zhù)將查詢(xún)結果綁定到 DataGrid 控件。 由于篇幅所限,本文在此不列出代碼。)
[dvnews_page=用Asp.NET建立一個(gè)在線(xiàn)RSS新聞聚合器]
顯示特定聚合摘要的新聞項<script language="// 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"
<xsl:output method="html" omit-
<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.
之所以要使用這種語(yǔ)法,是因為要給 XSLT 樣式表中某個(gè)你要創(chuàng )建的元素添加一個(gè)屬性,然后在該元素的標簽里使用 <xsl:attribute> 語(yǔ)法 。有關(guān)該語(yǔ)法的一些例子可在 W3Schools 網(wǎng)站上找到:The <xsl:attribute> Element。
最后要注意的是,超鏈接的ID查詢(xún)字符串的值是來(lái)自于 <xsl:number> 元素,從 position() 函數中返回的值。<xsl:number> 元素僅僅是輸出一個(gè)數值。position()函數是一個(gè) XPath 函數,用來(lái)返回 XML 文檔中當前節點(diǎn)的順序位置。這意味著(zhù)對于第一個(gè)新聞項,position() 函數返回 1,第二個(gè) 新聞項,position函數返回 2,以此類(lèi)推。我們需要記錄這個(gè)值并將它通過(guò)查詢(xún)字符串傳遞出去。這樣當 DisplayItem.Asp 頁(yè)面被訪(fǎng)問(wèn)時(shí),就可以知道顯示 RSS 聚合摘要的什么項目了。
聰明的讀者可能已經(jīng)注意到,我們的 XSLT 樣式表沒(méi)有全部完成,因為 FeedID 參數沒(méi)有通過(guò)查詢(xún)字符串傳遞到 DisplayItem.Aspx 頁(yè)面。要明白 這是為什么,我們回顧一下在 ID 查詢(xún)串參數中所傳遞的是用戶(hù)擬察看詳細信息的<item>元素順序號。也就是說(shuō),如果用戶(hù)點(diǎn)擊第四條新聞項,頁(yè)面 DisplayItem.Aspx?ID=4 就會(huì )被 加載到右下部分的框架中。問(wèn)題在于 DisplayItem.Aspx 頁(yè)面無(wú)法確定用戶(hù)希望查看哪一個(gè)摘要。有兩個(gè)不同的方法可以解決這個(gè)問(wèn)題,比如可以在右下部框架中用客戶(hù)端 JAVAscript 代碼讀取右上部框架的 URL,然后確定FeedID 的值。在我看來(lái),更簡(jiǎn)單的辦法是和 ID 參數一起將 FeedID 的值通過(guò)查詢(xún)字符串傳遞 。
這樣的話(huà),有一個(gè)難題是 XSLT 樣式表操縱的 RSS XML 數據中并沒(méi)有 FeedID 值。但是 DisplayNewsItems.Aspx 頁(yè)面知道 FeedID 值,需要一種方法讓 XSLT 樣式表也知道這個(gè)值。通過(guò)使用 XSLT參數可以 實(shí)現完成。
XSLT 參數的使用是非常簡(jiǎn)單。在 XSLT 樣式表中,你需要在 <xsl:template> 元素中加入一個(gè)<xsl:param> 元素, 該元素提供參數的名稱(chēng)。下面的代碼將這個(gè)參數命名為 FeedID:
<xsl:stylesheet version="1.0"
<xsl:template match="/rss/channel">
<xsl:param name="FeedID" />
...
</xsl:template>
</xsl:stylesheet>
現在,就可以用下面的語(yǔ)法在<xsl:value-of>元素中使用這個(gè)參數了:
<xsl:value-of select="$parameterName" />
最后,在我們的 XSLT 樣式表中加入下面的代碼,我們就可以把 FeedID 查詢(xún)字符串參數加到超鏈接中了:
<a>
<xsl:attribute name="href">DisplayItem.Aspx?ID=<xsl:number value="position()" />&FeedID=<xsl:value-of select="$FeedID"
/></xsl:attribute>
注意在ID查詢(xún)字符串參數后面我們加了一個(gè)&字符(轉義&),這樣我們就可以傳遞 FeedID 參數的值到查詢(xún)字符串的 FeedID 參數中了。 這就是我們要在 XSLT 樣式表中添加的內容。
剩下的工作是在 DisplayNewsItems.Aspx 頁(yè)面的 Page_Load 事件處理函數中設置這個(gè)參數的值。通過(guò)使用 XsltArgumentList 類(lèi)可以完成這一工作。這個(gè)類(lèi)有一個(gè) AddParameter() 方法。一旦我們創(chuàng )建了這個(gè)類(lèi)的一個(gè)實(shí)例,加入了相應的參數,就可以將這個(gè) 實(shí)例賦給 XML Web 控件的 TransformArgumentList 參數了。下面的代碼顯示了更新后的 DisplayNewsItems.Aspx 頁(yè)面 Page_Load 事件處理函數:
顯示特定新聞項的詳細內容
還剩下最后一件需要做的事情是顯示用戶(hù)選擇的特定新聞項的詳細內容。這些詳細內容將顯示在右下部的框架中,而且將會(huì )顯示新聞項的標題,描述和新聞項的鏈接等信息。和 DisplayNewsItem.Aspx 頁(yè)面 類(lèi)似,DisplayItem.Aspx 頁(yè)面首先將根據傳入的 FeedID 查詢(xún)字符串參數獲取遠程的 RSS 聚合摘要,然后它會(huì )用 XML Web 控件顯示這些詳細內容。實(shí)際上,DisplayItem.Aspx 頁(yè)面的 Page_Load 事件處理函數和DisplayNewsItem.Aspx 頁(yè)面的 該函數幾乎一樣,只有以下兩個(gè)小小的區別:
·DisplayItem.Aspx 頁(yè)面需要讀取ID查詢(xún)字符串參數的值;
·DisplayItem.Aspx 頁(yè)面使用一個(gè) XSLT 參數,但是這個(gè)參數與 DisplayNewsItem.Aspx 頁(yè)面用的參數是不一樣的;
DisplayNewsItem.Aspx 和 DisplayItem.Aspx 頁(yè)面一樣都需要在參數中傳遞一個(gè) XSLT 樣式表。DisplayNewsItem.Aspx 頁(yè)面傳遞的是 參數 FeedID,而 DisplayItem.Aspx 還需要傳入 ID 參數,它表示 XSLT 樣式表應該顯示那個(gè)新聞項。這個(gè)細小的差別在以下代碼中以粗體顯示,以下 代碼省略了與 DisplayNewsItems.Aspx 頁(yè)面相同的部分:
以下是轉換 XML 數據的 XSLT 樣式表:
<?XML version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0"
<xsl:output method="html" omit-<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 參數。然后,在幾個(gè)不同的 <xsl:value-of> 元素中,ID 參數 被用來(lái)從 <item> 元素列表中抓取特定的 <item> 元素。在 XPath 的語(yǔ)法中,elementName[i]意思是根據相應元素名 存取第i個(gè)元素。例如,item[1]將只獲取第一個(gè)<item>元素,item[2]則獲取第二個(gè)元素。所以 item[$ID]是獲取由 XSLT 參數 ID 定義的 特定 <item> 元素。
最后,值得注意的還有在樣式表靠近末尾部分的超鏈接 Read More…,它的target屬性設為空,這樣的話(huà)當用戶(hù)點(diǎn)擊 Read More… 鏈接的時(shí)候,瀏覽器會(huì )打開(kāi)一個(gè)新的窗口。
[dvnews_page=用Asp.NET建立一個(gè)在線(xiàn)RSS新聞聚合器]
未來(lái)的擴展和當前程序的缺點(diǎn)
本文講述的代碼中有一個(gè)明顯的缺點(diǎn)就是每次用戶(hù)點(diǎn)擊左邊框架的某個(gè)聚合摘要或者在右上部框架點(diǎn)擊某個(gè)新聞項時(shí),遠程聚合摘要都會(huì )被裝載和解析。每次用戶(hù)點(diǎn)擊遠程聚合 摘要時(shí),所有的項都被加載,這樣的效率無(wú)疑是很差的。每次用戶(hù)點(diǎn)擊一個(gè)新聞項標題就重新裝載整個(gè)遠程聚合摘要也是很浪費資源的。這樣的方法不僅沒(méi)有效率,對提供發(fā)布服務(wù)的個(gè)人或者公司也是不禮貌的,因為這些 連續的、不沒(méi)必要的請求占用了他們的 Web 服務(wù)器的負載資源。
這個(gè)缺點(diǎn)在本文附帶的源代碼中已經(jīng)得到解決。具體來(lái)說(shuō),.NET數據緩存可以用來(lái)存放不同摘要的 XMLDocument 對象。緩存間隔設置為數據表 Feeds 中 UpdateInterval 字段定義的值。(當然,由于某些原因,摘要的 XMLDocument 對象有可能會(huì )被提前清除出緩存)
這個(gè)系統的另外一個(gè)缺點(diǎn)是在右上部框架和右下部框架之間沒(méi)有狀態(tài)的保存。為了說(shuō)明這樣會(huì )引起什么問(wèn)題,考慮以下的動(dòng)作:
·用戶(hù)點(diǎn)擊左邊框架的某個(gè)聚合摘要鏈接,在右上部框架中裝載這個(gè)摘要的新聞項目。假設這個(gè)摘要的UpdateInterval 的值是30,則表示這些內容在30分鐘之 后會(huì )過(guò)期;
·裝載右上部框架的新聞項的同時(shí),這些內容被緩存起來(lái);
·用戶(hù)離開(kāi)去吃午飯;
·發(fā)布聚合內容的網(wǎng)站增加了一條新的新聞項;
·我們的用戶(hù)一個(gè)小時(shí)午飯后回來(lái)了,這個(gè) 摘要的 XMLDocument 的緩存已經(jīng)過(guò)期;
·用戶(hù)點(diǎn)擊右上部框架的第一條新聞項,將會(huì )在右下部分框架中裝載 DisplayItem.Aspx,傳入 ID 參數值1;
·DisplayItem.Aspx 頁(yè)面在緩存中沒(méi)找到 XMLDocument 對象,只好重新獲取遠程摘要。這樣就會(huì )獲得新的數據了(別忘了,步驟 4 已經(jīng)加了一個(gè)新的新聞項),然后此頁(yè)面會(huì )顯示第一條新聞項目(因為ID參數的值為1) ;
·用戶(hù)看到了新的新聞項,但是內容會(huì )令他感到有點(diǎn)困惑,因為已經(jīng)不是他所點(diǎn)擊的那一條新聞了,而且右上部也沒(méi)有顯示那條新的新聞。
之所以出現這樣的問(wèn)題,是因為 ID 參數沒(méi)有唯一地標識一個(gè)新聞項,它只是一個(gè)特定時(shí)間點(diǎn)上新聞項列表中的一個(gè)偏移量。解決這個(gè)問(wèn)題的一個(gè)好的方法是不要用數據緩存來(lái)保存聚合 摘要,而是使用數據庫或者持久介質(zhì)的其它方式(比如 Web 服務(wù)器本地文件系統的 XML 文件)。如果使用數據庫,每一個(gè)新聞項都可以擁有一個(gè)唯一的標識號,可以用來(lái)傳遞到右下角的框架中。這種方法可以保證解決上面提到的問(wèn)題。當然也會(huì )增加系統的復雜性,比如需要決定何時(shí)從數據庫中清除掉舊的新聞項 。
本文現有的應用程序還缺少異常處理,而這肯定是應該加上的。尤其是當從遠程 RSS 聚合摘要文件獲取數據并加載到 XMLDocument 對象時(shí),應該加上異常處理。因為遠程的文件可能不存在或者格式不正確。
還有很多增強功能可以輕松地加入到這個(gè)在線(xiàn)新聞聚合器。一個(gè)明顯的功能是需要一個(gè)管理頁(yè)面來(lái)允許用戶(hù)添加,刪除和編輯他們現在的聚合摘要。還有,如果能允許用戶(hù)自定義分類(lèi) ,將他們的聚合摘要按類(lèi)別放在一起就更好了。另外,現在的用戶(hù)界面還是比較粗糙的,但是通過(guò)增加一些 XSLT 樣式表生成的 HTML 代碼或者在幾個(gè)框架里面增加一些樣式表就可以很容易地美化一下界面。最后,在html標簽里面加一些<meta>元素,可以讓右上部框架定時(shí)地去刷新,使得用戶(hù)不用自己手工去刷新頁(yè)面就可以看到最新的新聞項目。
注解 (2003年8月4日): 在這篇文章發(fā)布以后,一些讀者用 Email 告訴通知我在顯示特定 RSS 聚合項的 <description> 元素時(shí),有兩個(gè)潛在的問(wèn)題:
1、Disable-output-encoding 屬性,這個(gè)屬性用在 <xsl:value-of> 元素中,但是并不是所有的 XSLT解析器都實(shí)現了這個(gè)功能。.NET XSLT 解析器支持 disable-output-encoding,但是還是要 注意一下,因為讀者可能試圖將這個(gè)應用程序移植到其它平臺。
2、<description> 元素的 HTML 內容是被原封不動(dòng)地輸出的。但是,這些 HTML 內容可能包含惡意代碼,比如 <script> 或者 <embed> 代碼塊。理想情況下,這些代碼應該被剔除掉。為了清除掉這些有潛在危險的代碼,可能需要用到一些擴展函數(參見(jiàn) Extending XSLT with JScript, C#, and Visual Basic .NET)。想查看從 RSS 聚合 摘要剔除 HTML 內容的更多信息,可以參見(jiàn)’’Dive Into Mark’’ 日志.
總結
在本文中,我們不僅講到如何創(chuàng )建一個(gè)聚合引擎,還創(chuàng )建了一個(gè)在線(xiàn)新聞聚合器。在建立這兩個(gè)應用程序時(shí),我們都采用了在 Asp.NET 頁(yè)面顯示 XML 數據的技術(shù)。在聚合引擎里面,我們使用了 Repeater 控件以 XML格式來(lái)顯示數據庫中的數據。而在新聞聚合器里面,我們使用了 XML Web 控件和 XSLT 樣式表。
我邀請你下載本文的在線(xiàn)新聞聚合器,然后根據你的需要來(lái)增強它。如果有任何關(guān)于這個(gè)應用程序或者這篇文章討論的概念方面的問(wèn)題,隨時(shí)恭候你的 EMail。我的 EMail mitchell@4guysfromrolla.com.
聯(lián)系客服