一、引言 近几年,RSS的应用越来越广泛。新浪、新华网、网易等新闻门户网站及各个博客运营网站都推出了RSS订阅服务,Google也推出了Google Reader服务用来让用户定制自己所需的RSS源内容。RSS也被用于检索电子邮件,或通过复杂的工作流程传递交易信息等。RSS大有可能演变为所有信息(不论结构严谨或松散)的主要采集方式。 接下来我们将提供一套基于asp.net 2.0站点对RSS文件生成与调用的程序。 二、什么是RSS RSS是一种XML表示数据的格式。通常用于共享标题和新闻文章的链接。这种格式可以包括标题、Url和描述等信息。 RSS起源于“网景通讯公司”的新闻频道语言,其目的是利用推(Push)的技术把用户订阅的新闻传送到订户。由于该技术没有找到合适商业模型和规范过于复杂,所以日渐衰落。但到了近些年由于网络日志(Web Blog)的流行,RSS也逐渐成为描述Blog主题和更新信息的最基本方法。为了适应新的网络应用需要,UserLand公司把RSS从网景公司原来的0.9版升级到了0.91版、0.92版,随后在各种Blog工具中得到了应用,并被众多的专业新闻站点所支持。在广泛的应用过程中,一个联合小组根据W3C新一代的语义网技术RDF对RSS进行了重新标准化定义,发布RSS 1.0,并希望能把RSS发展成为一个通用的规范。但直到今天,RSS 1.0也没有成为标准化组织的真正标准。2002年9月UserLand公司(一家著名的博客公司)独自把RSS由原来的0.94版升级到了全新模式的2.0版本,该版本与0.9x版本是兼容的,但不兼容于RSS 1.0版。RSS也分化形成了RSS 0.9x/2.0和RSS 1.0两个阵营。由于RSS 0.9x/2.0应用起来更加简单实用,所以该规范得到广泛的使用。本文也主要基于RSS2.0规范上使用的。 为网站创建RSS,必须对RSS了解。RSS文件主要是由一个<channel>元素及其子元素组成的。RSS2.0 的根元素是<rss>元素,这个元素可以有一个版本号的属性。<rss>元素只有一个子元素<channel>用来描述频道聚合的内容。<channel>还包含表示对频道本身的描述,如<title>表示频道或提要的名称、<link>表示与频道关联的Web站点或站点区域、<description>表示该频道的描述信息等。项<item>通常是频道的主要部分。项通常包含下列元素: <title>:项的名称。在应用中被转换成Html的标题。 <link>:项的URL。指向title的链接。 <description>:内容描述。指向URL的摘要或补充。 <author>:作者的信息。 <category>:组织分类。 <comments>:关于项注释页的URL。 <enclosure>:支持与该项有关的媒体对象。 <guid>:唯一与该项有关的永久链接。 <pudDate>:该项的发布时间。该日期必须按照 RFC822 日期和时间规范显示。 <source>:该项来自哪个频道。 所有的项元素都是可选的,但是一个项中必须包含一个<title>元素或<description>元素。 三、在asp.net 2.0中生成RSS规范的页面 因为在RSS文件中包含若干个项(RSS 0.9x中最多为15个),所以采用数据绑定的方法来实现来会更为方便。由于Repeater控件自定义性非常强,并且该控件对服务器的性能消耗也很小,在页面上显示也不会产生冗余的Html代码,所以我们使用Repeater控件来进行数据绑定。 RSS.aspx页面代码: <%@ Page Language="C#" AutoEventWireup="true" ContentType="text/xml" CodeFile="Rss.aspx.cs" Inherits="NewsRss" ValidateRequest="false"EnableViewState="false" ResponseEncoding="UTF-8" StylesheetTheme=""Theme=""%> <asp:Repeater ID="RptRSS" runat="server"> <HeaderTemplate> <rss version="2.0"> <channel> <title>新闻</title> <link>http://localhost/news/</link> <description>信息网新闻排行</description> </HeaderTemplate> <ItemTemplate> <item> <title><%#Output(Eval("Title")) %></title> <link><%#Eval("NewsUrl") %></link> <pubDate><%#Convert.ToDateTime(Eval("ApprovedDate").ToString()).ToString("r") %></pubDate> <description><%#Output(Eval("Abstract"))%></description> <author><%#Output(Eval("PostUser"))%></author> </item> </ItemTemplate> <FooterTemplate> </channel> </rss> </FooterTemplate> </asp:Repeater> 为了满足RSS2.0规范,所以必须要在页面上去掉了一些诸如<html>、<head>、<body>、<form>等Html代码。以下是部分代码的说明: ContentType="text/xml" 指输出的是XML格式。 ResponseEncoding="UTF-8" 参数设置能使XML支持中文显示。 如果站点使用了主题和皮肤,则需要设置StylesheetTheme=""Theme=""。 Output函数是将非法的 xml 字符替换为它们对应的合法的转义字符,函数是在后台代码中声明的,后面再详细说明。 <pubDate>元素中的ToString("r")是把ApprovedDate格式化为RFC822 日期和时间规范(例如:Thu, 01 Dec 2005 21:35:55 GMT)。 注意:XML格式是大小写敏感的,这就意味着,XML元素的起始和终止标签必须匹配,拼写和大小写都必须一致。 后台代码Rss.aspx.cs如下: private const string connectionString = @"数据库链接字符串"; public partial class NewsRss : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { if (!Page.IsPostBack) { DataTable dt = GetNewsRss(); DataView myDV = dt.DefaultView; RptRSS.DataSource = myDV; RptRSS.DataBind(); } } private DataTable GetNewsRss() { DataTable dt = (DataTable)Cache["NewsRssCache"]; if (dt == null) { SqlConnection connection = new SqlConnection(connectionString); SqlCommand command = new SqlCommand("SELECT TOP 15 Title, PostUser, LEFT(CAST(Body AS varchar(1000)), 350)+ '...' AS Abstract, 'http://localhost/news/VIEW.aspx?newsID=' +CAST(NewsID AS varchar(6)) AS NewsUrl FROM News_News WHERE (Approved = 1) ORDER BY ApprovedDate DESC ", connection); command.CommandType = CommandType.Text; SqlDataAdapter da = new SqlDataAdapter(command); dt = new DataTable(); try { connection.Open(); da.Fill(dt); Cache.Insert("NewsRssCache", dt, null, DateTime.Now.AddMinutes(10), TimeSpan.Zero); } finally { connection.Dispose(); command.Dispose(); da.Dispose(); } } return dt; } protected string Output(object inputString) { if (inputString == null) return string.Empty; StringBuilder strBuilder = new StringBuilder(); string strTemp = HttpContext.Current.Server.HtmlEncode(inputString.ToString()); strBuilder.Insert(0, strTemp); strBuilder.Replace(((char)32).ToString(), " "); strBuilder.Replace(((char)9).ToString(), " "); strBuilder.Replace(((char)34).ToString(), """); strBuilder.Replace(((char)38).ToString(), "&"); strBuilder.Replace(((char)39).ToString(), "'"); strBuilder.Replace(((char)60).ToString(), "<"); strBuilder.Replace(((char)62).ToString(), ">"); strBuilder.Replace(((char)13).ToString(), " "); return strBuilder.ToString(); } 为了加快页面浏览速度和提高服务器性能,我们采用了缓存机制。GetNewsRss()是返回缓存中NewsRssCache的表数据。如果缓存中没有该表则创建它,该表在服务器缓存中的生存期设置为10分钟。 Output函数是把数据字符格式化为XML数据显示。也就是把<,>,&,", (空格)和'转换成<,>,& ,", 和'。该函数用于在页面上XML数据格式的输出。 这样生成的Rrs.aspx页面就可以用SharpReader、SSReader、NewzCrawler等RSS阅览器订阅浏览,也可以被其他站点调用了。 四、在asp.net 2.0站点中调用RSS 2.0规范的数据 当然也可以让我们的asp.net站点来调用其它站点的RSS数据,我们可以这样做。 在项目管理器中添加名称为App_Code的Asp.net文件夹,并在文件夹中分别添加RssFeed.cs,RssChannel.cs,RssItem.cs,IItem.cs,ItemListView.cs类文件。新建UserControls文件夹,在文件夹中添加名为RssItemsList的用户控件。(如图所示) C#2.0中增加了一个激动人心的特征是泛型的使用。泛型提供了类型安全,并且能对参数的类型施加约束。所以我们采用泛型的方法来撷取调用调用RSS文件中的项元素内容。 由于.NET 2.0的System.Collections.Generics 命名空间包含了泛型集合定义,各种不同的集合/容器类都已经被"参数化"了,所以在我们的程序中需要引用(using)该命名空间。 RssItem.cs是从RSS 2.0的Xml文档中提取一个元素构建成为的类。文件代码: namespace NewsRss.WebModules.Business.Rss { public class RssItem : IItem { private readonly string title; private readonly string description; private readonly string link; public string Title { get { return title; } } public string Description { get { return description; } } public string Link { get { return link; } } internal RssItem(XmlNode itemNode) { XmlNode selected; selected = itemNode.SelectSingleNode("title"); if (selected != null) title = selected.InnerText; selected = itemNode.SelectSingleNode("description"); if (selected != null) description = selected.InnerText; selected = itemNode.SelectSingleNode("link"); if (selected != null) link = selected.InnerText; } RssChannel.cs是从RSS 2.0的Xml文档中提取<channel>元素构建成为的类。文件代码: namespace NewsRss.WebModules.Rss { public class RssChannel { private readonly string title; private readonly string link; private List<RssItem> items; public string Title { get { return title; } } public string Link { get { return link; } } public IList<RssItem> Items { get { return items.AsReadOnly(); } } internal RssChannel(XmlNode channelNode) { items = new List<RssItem>(); title = channelNode.SelectSingleNode("title").InnerText; link = channelNode.SelectSingleNode("link").InnerText; XmlNodeList itemNodes = channelNode.SelectNodes("item"); foreach (XmlNode itemNode in itemNodes) { items.Add(new RssItem(itemNode)); } } RssFeed.cs用来读取和格式化Rss 2.0的XML文件,采用工厂模式来构建。文件代码: namespace NewsRss.WebModules.Rss { public class RssFeed { private List<RssChannel> channels; public IList<RssChannel> Channels { get { return channels.AsReadOnly(); } } public RssChannel MainChannel { get { return Channels[0]; } } private RssFeed(XmlNode xmlNode) { channels = new List<RssChannel>(); // 读取<rss>标记 XmlNode rssNode = xmlNode.SelectSingleNode("rss"); // 在<rss>中遍历 <channel>节 XmlNodeList channelNodes = rssNode.ChildNodes; foreach (XmlNode channelNode in channelNodes) { RssChannel newChannel = new RssChannel(channelNode); channels.Add(newChannel); } } public static RssFeed FromUrl(string url) { XmlTextReader reader = new XmlTextReader(url); XmlDocument xmlDoc = new XmlDocument(); xmlDoc.Load(reader); return new RssFeed(xmlDoc); } } } IItem.cs在此接口类中,声明了两个只读属性Description和Title。文件代码: namespace NewsRss.WebModules.Rss { public interface IItem { string Description { get; } string Title { get; } } } ItemListView.cs取得项列表。文件代码: namespace NewsRss.WebModules.Rss { public class ItemListView<T> : IDisposable where T : IItem { private string title; private int selectedIndex = 0; private IList<T> items; private int maxItemsToShow; public int NumItemToShow { get { return Math.Min(items.Count, maxItemsToShow); } } public int MaxItemsToShow { get { return maxItemsToShow; } set { maxItemsToShow = value; } } public int SelectedIndex { get { return selectedIndex; } } public T SelectedItem { get { return items[selectedIndex]; } } public void NextArticle() { if (selectedIndex < NumItemToShow - 1) selectedIndex++; else selectedIndex = 0; } public ItemListView(string title, IList<T> items) { if (items == null) throw new ArgumentException("项不能为空", "items"); this.items = items; this.title = title; } public void Dispose() {} } } RssItemsList.ascx用户控件代码如下: <%@ OutputCache Duration="600" VaryByParam="none" %> <table border="1"cellpadding="0" cellspacing="0" style=" border-color:#3366cc" width="100%"> <tr> <tdstyle="background-color:#3366cc; border-color:#3366cc;" > <asp:Label ID="RssTitle" runat="server" ForeColor="White"></asp:Label></td> </tr> <tr> <td> <asp:Literal ID="RssItems" runat="server"></asp:Literal></td> </tr> </table> 为了提高浏览速度,所以把控件的缓存设置为600秒。 RssItemsList.ascx用户控件的后台代码: private RssFeed rssFeed; private ItemListView<RssItem> rssView; private int itemsCount=15; private string rssUrl=""; public int ItemsCount{get{ return itemsCount; } set{ itemsCount = value;}} public string RssUrl{get{return rssUrl;} set{rssUrl=value;}} protected void Page_Load(object sender, EventArgs e) { if (!Page.IsPostBack) { if (LoadRssFeed(RssUrl)) { rssView = new ItemListView<RssItem>(rssFeed.MainChannel.Title, rssFeed.MainChannel.Items); RssTitle.Text = rssFeed.MainChannel.Title; rssView.MaxItemsToShow = ItemsCount; RssItems.Text = ""; for (int i = 0; i < rssView.NumItemToShow; i++) { RssItems.Text += "<a href='" + rssView.SelectedItem.Link +"' target='_blank'>" + rssView.SelectedItem.Title + "</a><br>"; rssView.NextArticle(); } else { RssItems.Text = "RSS Feed 源数据出错!"; }} private bool LoadRssFeed(string url) { try { rssFeed = RssFeed.FromUrl(url); return true; } catch { return false; } } LoadRssFeed函数是调用RssFeed类的FromUrl方法,用来判断RSS Feed的格式是否正确。如果正确,就把文件格式化为我们所需的格式。 在RssItemsList用户控件中,定义了两个属性,其中RssUrl属性用来设置RSS文件的Url地址,ItemsCount属性用来设置显示RSS项的个数。 在页面上调用RssItemsList控件的代码: <uc1:RssItemsList id="RssItemsList1" ItemsCount=10 RssUrl="http://127.0.0.1/news/rss.aspx" runat="server"> </uc1:RssItemsList> 这样,一个完整的RSS Feed调用实例就完成了。把本控件与用户的cookies联系起来可以实现用户自定义定制RSS源,类似于一些网站提供的用户自定义RSS博览服务。 五、总结 RSS提供了一种网站与其他站点之间共享内容的一种简易方式(也叫聚合内容)。本文通过对RSS规范的简单分析,并在此基础上实现asp.net对RSS文件的生成和调用。这样我们可以把自己站点内容更为广泛的共享,又可以对支持RSS的站点进行调用显示在自己的站点上。 但是由于RSS feed规范标准不是统一的,会为我们的调用带来了相当大的麻烦,本文中只支持RSS 0.9x/2.0规范。为了提高响应速度,本文的示例中应用了缓存机制,但这样做会存在聚合列表更新比新闻发布时间稍迟的问题。