基于 XML 的 (we)blog,带 RSS Feed






4.59/5 (17投票s)
2003 年 1 月 11 日
8分钟阅读

407519

2300
一个即用型博客工具。通过 Web 服务从 Windows 应用程序发布博客到 XML 文件。使用 SOAP 标头进行身份验证。使用简单的 XSL 转换生成 RSS Feed。
引言
由于博客(也称为 weblog)在过去一年中变得非常流行,我构思了制作自己的博客工具。Blog 是 web log 的简称,是一种在线(通常是公开的)日记,作者在此记录自己的想法,有时围绕特定主题。本文介绍如何编写一个相当简单的 weblog 应用程序以及一个用于在系统托盘中撰写条目的 Windows 程序。
此应用程序中使用的一些技术包括 XML 和 XML Schema、Web Services、DataSets、Cache 和 Calendar Web 控件。哦,还有 XML 控件,用于将 XML 博客转换为 RSS。
Web 应用程序
Web 应用程序实际上由三个部分组成:显示日志和日历的网页,用于接收条目的密码保护的 Web 服务,以及第二个网页,通过 XSL 转换将内部 XML 文件转换为 RSS 2.0 Feed。
Windows 应用程序
Windows 应用程序(下称客户端)功能相当简单,包含一个对话框,用户可以在其中键入消息并通过 Web 服务调用将其发送到网站。
客户端一直驻留在系统托盘中,当用户想在自己的博客中写消息时,单击鼠标即可弹出对话框,准备就绪。
Using the Code
让我们回顾一下代码中一些更有趣的部分,从博客数据的 XML 格式开始。
博客 XML 和 Schema
<?xml version="1.0" standalone="yes"?>
<weblog>
<logentry>
<id>0a8d4ec3-eec1-4b07-b26f-98bb5561f43c</id>
<logtitle>A
title</logtitle>
<logtime>2003-01-10T13:28:14.2031250+01:00</logtime>
<logtimeGMT>Fri, 10 Jan 2003 13:28:14 GMT</logtimeGMT>
<logtext>This is an entry in the weblog.</logtext>
</logentry>
</weblog>
以及博客的 XML Schema
<?xml version="1.0" encoding="utf-8" ?>
<xs:schema id="weblog" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xs:element name="weblog" msdata:IsDataSet="true" msdata:Locale="sv-SE">
<xs:complexType>
<xs:choice maxOccurs="unbounded">
<xs:element name="logentry">
<xs:complexType>
<xs:sequence>
<xs:element name="id" type="xs:string" minOccurs="0" />
<xs:element name="logtitle" type="xs:string" minOccurs="0" />
<xs:element name="logtime" type="xs:date" minOccurs="0" />
<xs:element name="logtimeGMT" type="xs:string" minOccurs="0" />
<xs:element name="logtext" type="xs:string" minOccurs="0" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>
如 XML 和 Schema 所示,博客由多个日志条目组成,包含 id
、logtitle
、logtext
、logtime
和 logtimeGMT
的数据。logtimeGMT
用于 RSS Feed,因为它需要是 RFC 822 格式。我找不到用 XSLT 将 logtime
转换为 GMT 的简单方法,所以我采取了捷径,将两者都存储在 XML 文件中。id
标签是为每个新博客条目分配的唯一 ID。
博客网页
博客通过将 XML 文件读取到 DataSet
并将其绑定到 Repeater 来在网页上显示。我喜欢 Repeater 进行此类简单循环,为什么要在不需要时使用更复杂的 DataGrid
或 DataList
呢?
请记住关闭 Repeater
的 ViewState
,它不需要而且会加速页面加载。
每次调用页面时,首先从 XML 文件中获取缓存的 DataSet
。这在 Page_Load
事件中完成。
Private Sub Page_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles MyBase.Load
'always start with getting our cached dataset
dsWebLog = XmlHelper.GetDS()
If Not IsPostBack Then
SetDates()
BindList()
End If
End Sub
XmlHelper
类有几个用于读取和写入 XML DataSet
的 static
方法。
XML 文件的位置存储在 ASP.NET 配置文件 web.config 中。
Public Shared Function GetDS() As DataSet
'get DS from cache
Dim ds As DataSet = CType(HttpContext.Current.Cache("dsWebLog"), DataSet)
If ds Is Nothing Then
ds = New DataSet("weblog")
ds.ReadXmlSchema(ConfigurationSettings.AppSettings("xmlSchema"))
Try
ds.ReadXml(ConfigurationSettings.AppSettings("xmlFile"))
Catch ex As Exception
'missing an xml file is perfectly fine, this might be the
'first time the app is used
End Try
'store in cache with dependency to the xml file
HttpContext.Current.Cache.Insert("dsWebLog", ds, _
New Caching.CacheDependency(ConfigurationSettings.AppSettings("xmlFile")))
End If
Return ds
End Function
缓存对 XML 文件具有依赖性,因此如果向 XML 文件添加新消息,.NET Cache 会自动刷新缓存的 DataSet
。
为了能够选择特定日期,我还向页面添加了 ASP.NET Calendar
控件。当页面加载时,我遍历博客 XML DataSet
中的所有日期,并选择日历中包含博客条目的所有日期。当有人单击日历中的某个日期时,会在绑定到 Repeater
之前过滤 DataSet
。
Private Sub Calendar1_SelectionChanged(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Calendar1.SelectionChanged
dateFilter = Calendar1.SelectedDate.AddDays(1).ToString
SetDates()
BindList()
End Sub
在将 DataSet
绑定到 Repeater
之前,日志条目会被排序,并且只显示前 50 条条目。这(和样本应用程序中的许多其他内容一样)可以在 web.config 文件中设置。
Private Sub BindList()
'get a dataview from a copy of the cached dataset
Dim dvWeblog As DataView = dsWebLog.Copy.Tables(0).DefaultView
'filter on date?
If dateFilter <> "" Then
dvWeblog.RowFilter = "logtime < '" & dateFilter & "'"
End If
'sort it by date and time
dvWeblog.Sort = "logtime desc"
'copy maximum nr of rows to show
Dim dtWeblog As DataTable = XmlHelper.GetTopRows(dvWeblog,
ConfigurationSettings.AppSettings("maxrows"))
'bind the sorted and stripped log to the repeater
weblogList.DataSource = dtWeblog
weblogList.DataBind()
End Sub
通过设置 DataView
的 RowFilter
属性来过滤 DataSet
。.NET Cache 指向我们的缓存 DataSet
,缓存的 DataSet
指向 DataView
,因此如果我们不复制 DataSet
,那么缓存 DataSet
的其他用户将具有相同的 RowFilter
属性。这是我吸取教训才发现的...
'get a dataview from a copy of the cached dataset
Dim dvWeblog As DataView = dsWebLog.Copy.Tables(0).DefaultView
名为 GetTopRows
的方法也位于 XmlHelper
类中,它从日志中复制特定数量的行以在页面上显示。
Public Shared Function GetTopRows(ByVal dv As DataView, _
ByVal Rows As Integer) As DataTable
Dim dtReturn As DataTable
Dim cRow As Integer
Dim maxRows As Integer
maxRows = dv.Count
dtReturn = dv.Table.Clone()
For cRow = 0 To (Rows - 1)
If cRow = maxRows Then Exit For
dtReturn.ImportRow(dv(cRow).Row)
Next
Return dtReturn
End Function
博客客户端
客户端由一个对话框组成,该对话框启动时最小化到系统托盘,即桌面状态区域的一个图标。该对话框包含一个用于标题的 TextBox
,一个用于正文的 RichTextBox
,以及几个用于将日志条目发送到 Web 服务以及隐藏或关闭程序的按钮。
因此,要将一些文本发布到博客 Web 服务,用户在标题 textbox
和正文 textbox
中键入一些文本,然后按“发送”按钮。我认为 Web 服务应该有一定的保护措施,因此调用是通过发送在 SOAP 标头中的密码进行身份验证的。密码存储在一个 config 文件中,我使用内置的 .NET ConfigurationSettings 文件(WeblogClient.exe.config)来实现这一点。
更新:为了能够输入带有不同颜色和字体的格式化文本,并且能够输入 HTML 或 XML 标签,RichTextBox
中的文本首先被转换为 HTML(RichTextBoxUtil.ConvertToHTML()
)。您可以查看名为 RichTextBoxUtil.vb 的实用工具类,了解其实现方式。请注意,该实用工具目前尚不支持链接。
Private Sub Send_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles SendButton.Click
'Send message to the Weblog via SOAP/Webservice
Dim wsWeblog As New Weblog.Weblog()
'get password from the config file (WeblogClient.exe.config)
Dim password As String = ConfigurationSettings.AppSettings("password")
If password Is Nothing Then
ConfigError("password")
End If
'this is our SOAP Header class
Dim authentication As New Weblog.AuthHeader()
authentication.password = password
wsWeblog.AuthHeaderValue = authentication
'get the Web Service URL from the config file
Dim URL As String = ConfigurationSettings.AppSettings("wsPostURL")
If URL Is Nothing Then
ConfigError("URL")
End If
'set the correct URL for the Web Service
wsWeblog.Url = URL
'send HTML converted text to the weblog webservice
wsWeblog.PostMessage(TextBox1.Text, _
RichTextBoxUtil.ConvertToHTML(RichTextBox1))
WindowState = FormWindowState.Minimized
HideMe()
'clear out the textbox
Me.RichTextBox1.Clear()
Me.TextBox1.Clear()
End Sub
Web 服务的 URL 也存储在 config 文件(WebLogClient.exe.config)中,该文件必须位于 weblog 客户端的同一目录下。
Web 服务
用于接收和存储已发布消息的 Web 服务方法非常小。它是一个简单的单一方法,首先检查 SOAP 标头并比较密码,然后将已发布的消息存储到博客中。
<WebMethod(Description:="Post a message to the weblog. An authentication
SOAP header is mandatory."), SoapHeader("authentication")> _
Public Function
PostMessage(ByVal title As String, ByVal message As String) As Integer
If authentication.password = ConfigurationSettings.AppSettings("password")_
Then
'password
is ok, stor message in the XML file XmlHelper.AddMessage(title,
message)
Else
Throw New Exception("Invalid password")
End If
End Function
密码(像许多其他东西一样)存储在 web.config 文件中。
AddMessage()
方法只是在博客 DataSet
中添加一个新的 DataRow
并将其保存回 XML。该方法还为此帖子创建一个唯一的 ID。新的 DataRow
添加到 DataSet
的顶部。XML 文件存储在 web.config 文件指定的(默认为 c:\weblog.xml)位置。
Public Shared Sub AddMessage(ByVal title As String, _
ByVal message As String)
Dim dsWebLog As DataSet = XmlHelper.GetDS
Dim drNew As DataRow
drNew = dsWebLog.Tables(0).NewRow
drNew.Item("id") = Guid.NewGuid.ToString
drNew.Item("logtitle") = title
drNew.Item("logtime") = Now
drNew.Item("logtimeGMT") = Format(Now, "r") 'RFC 822 format
drNew.Item("logtext") = message
dsWebLog.Tables(0).Rows.InsertAt(drNew, 0) 'insert it at beginning
'store xml again
dsWebLog.WriteXml(ConfigurationSettings.AppSettings("xmlFile"))
End Sub
博客 RSS 2.0 Feed
互联网上越来越多的博客提供其内容的 RSS Feed。我见过关于 RSS 代表什么的各种解释。这是摘自 RSS 规范的
“RSS 是一种 Web 内容聚合格式。它的名称是 Really Simple Syndication(真正简单的聚合)的首字母缩写。RSS 是 XML 的一种方言。”
但有些人说“RDF Site Summary”(RDF 站点摘要),RDF 代表 Resource Description Framework(资源描述框架),它是处理元数据的基础。这并不重要,它是一种以简单的 XML 方式发布内容的绝佳方式。
RSS 自 1999 年以来就已存在,我尝试通过阅读位于 http://backend.userland.com/rss 的 RSS 2.0 规范来创建一个非常简单的 RSS Feed。
仅仅为了“好玩”,我尝试使用 XSL Transformation 将博客 XML 文件转换为正确的 RSS 格式。因此,我创建了一个新的 WebForm ASPX 页面,并删除了除页面标头之外的所有内容,并为其添加了一个 ContentType
属性,值为 text/xml。
<%@ Page contenttype="text/xml" Language="vb" AutoEventWireup="false"
Codebehind="rss20.aspx.vb" Inherits="Weblog.rss20"%>
然后我将一个 ASP.NET XML 控件拖放到页面上,并在代码隐藏中添加了一些代码来指向 XML 文件和 XSL 文件。
Private Sub Page_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
Dim doc As XmlDocument = New XmlDocument()
doc.Load(ConfigurationSettings.AppSettings("xmlFile"))
Dim trans As XslTransform = New XslTransform()
trans.Load(ConfigurationSettings.AppSettings("RSSxslFile"))
Xml1.Document = doc
Xml1.Transform = trans
End Sub
这是用于转换 XML 文件的 XSL 文件
<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
<xsl:template match="/">
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel>
<title>My weblog</title>
<link>https:///weblog/</link>
<description>Just another weblog...</description>
<managingEditor>someone@somewhere.com (My Name)</managingEditor>
<language>en-us</language>
<xsl:for-each select='weblog/logentry'>
<item>
<link>https:///weblog/Item.aspx?id=<xsl:value-of
select='id'/></link>
<guid isPermaLink="false"><xsl:value-of select='id'/></guid>
<title><xsl:value-of select='logtitle'/></title>
<description><xsl:value-of select='logtext'/></description>
<pubDate><xsl:value-of select='logtimeGMT'/></pubDate>
</item>
</xsl:for-each>
</channel>
</rss>
</xsl:template>
</xsl:stylesheet>
XSL 文件遍历每个日志条目,并将它们写在 description
和 pubDate
标签中。根据 RSS 规范,发布日期需要是 RFC 822 格式(GMT 格式),这就是为什么我在 XML 文件中使用该字段。
更新:XSL 文件已更新,现在它还输出了标题、guid 和博客条目的链接。
此页面的一大缺点是它会输出博客中的每个记录,而我在网页中已经处理了这个问题。用网页中的方式对前 50 条记录进行排序和过滤应该不难,但我留待以后更新。
关注点
我可以通过多种方式创建 RSS Feed,但我一直想尝试 ASP.NET XML 控件,所以选择了这个方法。我发现可以使用 XSL Transformation 做很多事情,但是,哇,它相当复杂。
正如我在文章前面所写,很容易忘记 .NET Cache
保留对引用类型对象的指针,如果您更改了从 Cache
获取的对象中的数据,实际上就会更改存储在 .NET Cache 中的对象。请记住,在使用 Cache
对象时,您可能会弄乱其他访问者的网络内容。只要您将值类型存储在 Cache
中,就无需担心。
更新
更新 1 - 我为博客条目添加了标题,主要是因为对于不同的 RSS 阅读器来说,每个博客条目都有标题看起来更好。出于 RSS 的目的,我还为每个条目添加了一个 guid。我还添加了代码,将 RichTextBox
中的部分格式化文本转换为 HTML。将彩色和缩进的代码片段或 HTML/XML 粘贴到 RichTextBox
中应该没有任何问题。它在 HTML 页面上看起来相当不错。请注意,它目前尚不支持超链接。
许可证
本文未附加明确的许可证,但可能在文章文本或下载文件本身中包含使用条款。如有疑问,请通过下面的讨论区联系作者。
作者可能使用的许可证列表可以在此处找到。