开发优先级:乐趣是第一位的
摘要:Duncan Mackenzie 通过用他的方式创建应用程序以检索和显示 RSS 新闻快递,说明了功能的出色性如何增加在开发项目的早期完成它的可能性。
适用于:
Microsoft® Visual Basic® .NET
本页内容
人们不停地让我工作
规划 MSDN 的页面
设计我们的新功能
显示新闻快递
存储和检索新闻快递列表
资源
编码问题
人们不停地让我工作
如果您一直在阅读我的专栏,则您可能认为我将所有时间都花在编写好玩且出色的代码示例上面,而无须从事任何无聊的工作。唔,按照计划当然是这样,但事情的发展却不总是如此。最近,我一直在忙于开发一个比较无聊的系统,以至于我实际上没有时间来编写任何有趣的东西了。
可是,最近我接到了一个内部系统的新需求的列表,该列表按照假定我完成各个功能的顺序排列。我迅速注意到真正出色的功能隐藏在该列表的尾部,并且被标记为“最好具有”— 这是项目经理对以下含义的另一种表达方法:“我们将不会生成这一功能……但我们将它放在该列表中以便让您感到有趣。”利用我分辨不清该列表的排序顺序这一站不住脚的借口,我决定首先解决这些出色的功能,而将那些令人厌烦的“第一优先级/必须具有的”功能留待我在收拾完办公桌并整理了硬盘碎片之后的某一天来完成。
规划 MSDN 的页面
在 我开始讨论作为本文主题的“出色”功能之前,我确实应该向您提供一些有关我要开发的系统的背景知识。几个月以前,我生成了一个名为 Page Planner 的内部系统,该系统用于设计和生成那些组成 MSDN 开发人员中心(包括我为其准备内容的 Visual Basic 站点)的页面。该系统(如图 1 所示)使我们可以更新该站点上的所有单独的页面,包括像 http://msdn.microsoft.com/vbasic/using/building/windows 这样的所有特定于技术的文章页面。
维 护这些站点的主要日常工作涉及到在新文章可用时更新适当的技术信息页。这通常通过手动输入 URL 或者通过从浏览器窗口中拖动链接来完成。MSDN RSS 新闻快递的最新版本(它包含新内容的最新列表)以及类似的针对像 4GuysFromRolla.com 这样的站点的新闻快递产生了对“从 RSS 新闻快递拖放链接”的“最好具有”请求。
设计我们的新功能
作为一个优先级相当低的要求,除了那一行描述以外,没有对该请求进行任何更为详细的描述,但是我非常了解经常浏览 MSDN 的人们都会寻找哪些内容,所以我从粗略设计开始工作。
RSS Viewer 窗口将支持查看 RSS 新闻快递以及将新闻快递中的链接拖入系统的其余部分。用户将能够从向所有系统用户公开的新闻快递主列表中或者从他们自己的个人列表中进行选择,并且他们还 可以直接输入新闻快递 URL(根据需要将其添加到他们的个人列表中)。
我想不出在 DataGrid 控件内显示新闻快递(具有可变大小的内容、XHTML 支持等等)的真正简单的方法 — 如果不使用一点儿自定义代码的话。因此,我决定改而使用 Web Browser 控件(在我的 Microsoft Windows? 窗体上承载 Microsoft? Internet Explorer)来显示新闻快递。使用 XSL Transform,我可以将 RSS 信息转换为 HTML,然后将该 HTML 传递到浏览器控件中以便显示。
注 该实现决策的一个不错的副作用是我可以无偿地获得拖放功能;Web Browser 控件支持将链接从它的内部拖到其他应用程序中,而无需任何附加代码。
链接主列表将被存储到某种形式的中央储存库中,但用户的个人链接集将存储在本地并且根据需要更新。最终的功能列表如下所示(仍然需要进行一些本地首选项编辑):
-
从中央存储区检索新闻快递主列表。
-
从本地存储区检索新闻快递个人列表。
-
检索 RSS 新闻快递并将其转换为 HTML。
-
在 Web 浏览器中显示 HTML。
-
使用户可以输入 RSS 新闻快递的 URL。
-
使用户可以在其新闻快递个人存储区中执行添加/编辑/删除操作。
-
使用户可以在新闻快递主列表中执行添加/编辑/删除操作(请注意,这当然不能适合于所有情况)。
在我所处的特定情况下,我将把新闻快递主列表存储到 Page Planner 主系统所使用的同一共享数据库中,然后使用 SqlClient 类检索/编辑它,但您可能需要不同的实现(基于 Web 服务的、基于文件的中央存储区等等)。为了使我的存储新闻快递主列表和个人列表的特定方法易于导出,我已经通过 IFeedList 接口将程序的这些方面设计得稍微抽象化一些。您可以实现该接口,以便创建您自己的存储和检索新闻快递列表的方法。
显示新闻快递
当然,对于这个小项目的目的来说,存储和更新新闻快递列表实在是次要的;主要功能是检索和显示 RSS 新闻快递。因此,如果我开始时就向您说明该系统的这一部分,应该是再好不过了。
为了加载新闻快递本身,我使用了 XMLDocument 的 Load 方法。然后,我将另一个 URL(或文件位置)中的 XSL 加载到一个 XSLTransform 实例中。最后,我使用 XSLTransform 类的 Transform 方法来获取 XMLDocument,并使用 XSL 转换它。转换的输出被写入流中,因此我创建了一个基于字符串的流(IO.StringWriter 的实例)来接受结果。
Dim myDoc As New XMLDocument
myDoc.Load(rssURL.Text)
Dim result As New System.Text.StringBuilder
Dim resultStream = New IO.StringWriter(result)
Dim xslt As New XSLTransform
xslt.Load(xsltURL.Text)
xslt.Transform(myDoc, _
New Xsl.XsltArgumentList, _
resultStream)
迄今为止,这确实是非常简单的代码,因为实际工作在 XSL 文件本身(包含在本文的下载资料中)中完成。XSL 本身并非能够处理任何 RSS 新闻快递,因为一致性不是 RSS 实现的优点之一。尽管如此,它对来自 weblogs.asp.net、MSDN 和 GotDotNet 的新闻快递起作用,因此就目前而言它应该足够了。在我拥有转换的结果以后,我就通过将它放入嵌入式浏览器控件中显示的 Web 页的正文中来显示它。
Dim myDOMDoc As mshtml.HTMLDocument
myDOMDoc = DirectCast( _
Me.embeddedBrowser.Document, _
mshtml.HTMLDocument)
myDOMDoc.body.innerHTML = result.ToString
我编写了您迄今为止已经看到的所有代码,并且使用一个小型示例应用程序(参见图 2)测试了 XSL 代码的各个部分。随着我继续工作,我最终抛弃了该示例,但就目前而言,它是针对各种 RSS 新闻快递来测试我的代码的好方法。
确保所有方面都有条不紊
我 遇到的第一个问题是某些新闻快递未按日期的降序(首先显示最新的项目)显示,而这是相当令人迷惑的。结果表明,尽管大多数 RSS 新闻快递已经按日期降序排序,但 MSDN 新闻快递是一个值得注意的例外。为了保持一致性,我决定通过 元素向 XSL 中添加排序代码。我未能使排序直接针对 pubDate 值起作用,从而导致了该转换的更为有趣的一个方面,因为我需要包含一个用户定义函数(使用 Visual Basic .NET 编写),以便将基于字符串的日期值转换为可以排序的新格式。
<msxsl:script language="vb" implements-prefix="utility">
function GetDate(pubDate As String)
Try
Dim myDate as Date = CDate(pubDate)
Return myDate.ToString("yyyyMMddHHmmss")
Catch
End Try
end function
</msxsl:script>
该函数本身非常简单,我只是努力从字符串转换为日期,然后用易于排序的格式重新输出为字符串。利用 函数,我使用 GetDate 函数的结果来执行实际的排序。
<xsl:template match="/rss/channel">
<xsl:for-each select="./item">
<xsl:sort order="descending"
select="utility:GetDate(./pubDate)" />
<xsl:apply-templates select="." />
</xsl:for-each>
</xsl:template>
GetDate 函数周围的 Try…Catch 块不处理错误,但它使 XSL 仍然能够正常工作,即使该代码无法分析日期字符串。
多样性是生活的调味品,但在 RSS 中例外
RSS 的规范在许多方面(包括正确地处理 HTML 内容以及应该以何种方式指定日期)是灵活的,这意味着它允许使用多种方法。当您要编写系统以输出 RSS 时,这种灵活性非常了不起,但当您要读入 RSS 时,它却会带来一点儿困难。这种灵活性首先给我造成与日期有关的问题;我所测试的大多数新闻快递使用了 pubDate 元素,但一些新闻快递却改而使用了 dc:date 元素。我通过添加一个 函数来使用和显示任意可用的日期属性,从而处理了该问题。
<xsl:choose>
<xsl:when test='pubDate'>
<p>Posted on:
<xsl:value-of select="pubDate" /></< font>p>
</< font>xsl:when>
<xsl:when test='dc:date'>
<p>Posted on:
<xsl:value-of select="dc:date" /></< font>p>
</< font>xsl:when>
<xsl:otherwise>
<p>Couldn't Retrieve Date</< font>p>
</xsl:otherwise>
</xsl:choose>
我必须使用一个类似的方法来处理已下载的 RSS 新闻快递内部的任何 HTML 内容,并且使用 在三种处理 RSS 新闻快递内部的 HTML 内容的主要方法之间进行取舍。这三种常见方法是:
-
用 content:encoding 标记标志 HTML 块,并对所有这些标记进行 HTML 编码。
-
将 HTML 放入 xhtml:body 元素中,并且保留 HTML 标记原样不动。
-
只须将其保留为未标记的和嵌入式的,就像纯文本一样。
为了确保我可以处理上述三种情况中的每一种,我使用了另一个 函数(在 XSLT 中)来为每种特定格式选取正确的实现,并且在我无法确定所使用的确切方法时只是采用未编码的内容。
<xsl:choose>
<xsl:when test='xhtml:body'>
<xsl:copy-of select='xhtml:body'/>
</< font>xsl:when>
<xsl:when test='content:encoded'>
<xsl:value-of
disable-output-escaping='yes'
select='content:encoded'/>
</< font>xsl:when>
<xsl:otherwise>
<xsl:value-of
disable-output-escaping='yes'
select='description'/>
</xsl:otherwise>
</xsl:choose>
最终结果应该对于任何使用上述三种方法之一将它们的内容公开为 RSS 新闻快递(xhtml:body、description 或 content:encoding)的新闻快递起作用,从而产生类似于图 3 中所示内容的最终显示。
现 在,需要特别注意的是,每当您要显示其他某个人已经提供的 HTML 内容(如 RSS 新闻快递内部的内容)时,您都需要知道可能存在的风险,尤其是当您使用我的用于替换“about:blank”页的内容的方法时。当 HTML 被显示在嵌入式浏览器中时,它是在本地区域内运行,该区域很可能具有比 Internet 区域低得多的安全限制。尽管您可以采取一些方法在显示 HTML 之前对其进行清理,但要保证它完全安全,需要完成大量的工作。请查看这一有用的网络日记张贴,它描述了由 RSS 中的 HTML 导致的一些问题以及如何避免这些问题。
存储和检索新闻快递列表
在我显示了 RSS 新闻快递,并且已经用足够的示例数据测试该系统(说明:它针对我的网络日记的新闻快递工作)以确保它能够正常工作之后,我就可以继续创建代码,以支持新闻快递个人列表和主列表的检索和编辑。就目前而言,我只实现了两个使用 IFeedList 接口的类:一个用于访问 SQL,一个使用当前用户所独有的 xml 设置文件。有关 IFeedList 接口和上述两个实现的源代码,请参见代码下载资料。
Public Interface IFeedList
Function GetList() As Feeds
Function AddFeed( _
ByVal newFeed As Feed) As Boolean
Function DeleteFeed( _
ByVal feedToToast As Feed) As Boolean
Function CanAdd() As Boolean
Function CanDelete() As Boolean
End Interface
对于基于个人文件的版本,我假设您可以随意地添加和删除项目,但是对于 Microsoft® SQL Server™ 版本(假定使用它来访问在多个用户之间共享的主列表),我需要更多的安全性。我使用集成式身份验证,因此您或许可以通过在 SQL Server 中限制用户权限来处理所有安全性问题,但我决定使用服务器角色,并且通过查看用户的角色成员身份来检查用户的权限。当然,任何基础表或数据库对象安全性限 制也都将有效,从而提供了辅助安全层。下面显示了 CanAdd 的实现,包括对检查角色成员身份的 StoredProc 的调用。
Public Function CanAdd() As Boolean _
Implements IFeedList.CanAdd
'does the currently logged on user
'have rights to add to a table?
'check if is in the
'"FeedAdministrator" role in SQL Server
Return IsInRole("FeedAdministrator")
End Function
Private Function IsInRole( _
ByVal Role As String) As Boolean
Try
Dim conn As New _
SqlClient.SqlConnection( _
Me.m_connectionString)
conn.Open()
Dim cmdIsInRole As New _
SqlClient.SqlCommand( _
"IsInRole", conn)
cmdIsInRole.Parameters.Add( _
"@Role", SqlDbType.NVarChar, _
128).Value = Role
cmdIsInRole.Parameters.Add( _
"@RC", SqlDbType.Int)
cmdIsInRole.Parameters( _
"@RC").Direction = _
ParameterDirection.ReturnValue
cmdIsInRole.Parameters.Add( _
"@Result", SqlDbType.Bit)
cmdIsInRole.Parameters( _
"@Result").Direction = _
ParameterDirection.InputOutput
cmdIsInRole.Parameters( _
"@Result").Value = 0
cmdIsInRole.ExecuteNonQuery()
Return CBool( _
cmdIsInRole.Parameters( _
"@Result").Value())
Catch ex As Exception
Return False
End Try
End Function
我还稍微更新了 UI,以便支持从一系列可用的新闻快递中进行选择,以及使您可以将任何已加载的新闻快递添加到您的个人(本地)列表中。图 4 显示了最后的界面,它已经很完整,不仅具有新的 Save 按钮,而且还具有一个组合框 — 您可以使用它来从已保存的新闻快递之一中进行选择,或者直接输入某个 RSS 新闻快递的 URL。
在 我开发该系统的过程中,我决定将它分解,以便在将来更加易于重用。因此,嵌入式浏览器现在与 XSL 和 RSS 代码结合在一起,构成了一个自定义控件,该控件已被放到图 4 中显示的窗体上。为了在我的实际应用程序中使用这些代码,我将可能进行一些附加更改,以使我可以传入 SQL 连接,并且将整个窗体及其所有关联代码放入一个库项目中。最后,我将具有这样的一个应用程序:我可以非常容易地从现有 Windows 窗体应用程序中的按钮启动它。然而,我已经将该示例生成为一个独立的应用程序,以便您可以完全独立地运行它。
资源
毫无例外地,我需要使用 Web 上的各种位置的一些资源来生成完善的应用程序。我在这一特定示例中没有使用任何 GotDotNet 用户示例,但我确实使用了以下资源:
-
Eric J. Smith 的完美的 CodeSmith 实用工具,目的是生成我的强类型新闻快递收藏。
-
从 RSS Bandit 的模板文件夹中窃取的一些起始 XSL(另请参见工作区)。
-
XSL 的各种知识以及来自 Kent Sharkey 的“技术支持”。
我还将为您介绍一些有益的 RSS 数据源、可以使用本文中的代码显示的绝妙资料以及一些了不起的读物。
-
Weblogs @ ASP.NET,主要的 Microsoft® .NET 网络日记站点,带有完整的 RSS 新闻快递,位于 http://weblogs.asp.net。
-
GotDotNet 具有所有种类的资源(包括最近张贴的用户示例、工作区等等)的新闻快递。请在 http://www.gotdotnet.com/community/resources/rsshome.aspx 查看它们。
-
MSDN 也具有 RSS,提供了整个站点或单个主题区域(如 Visual Basic)的最新发布的文章的列表。请在 http://msdn.microsoft.com/aboutmsdn/rss.asp 阅读有关它们的所有信息。
-
我已经在 http://msdn.microsoft.com/vbasic/support/community/blogs 编辑了一个重点研究 .NET 的博客的精选列表,并且您始终可以针对我的位于 http://weblogs.asp.net/duncanma/rss.aspx 的新闻快递测试您的代码。
当然,还存在很多其他 RSS 新闻快递,但这些站点中的新闻快递应该足够您使用一阵子的。
编码问题
在 我的一些“Coding4Fun”专栏文章的结尾,我会提出几个编码问题,如果您对它们感兴趣,可以进行一番研究。对于本文,问题在于创建任何能够输出或 使用 RSS 的应用程序。最好的选择是托管代码(Visual Basic .NET、C#、J# 或托管 C++),但是,公开 COM 接口的非托管组件也是不错的选择。请将您的作品张贴到 GotDotNet,并给我发送电子邮件(duncanma@microsoft.com),说明您所做的工作以及您感兴趣的原因。您可以随时给我发送电子邮件,但请您只发送指向代码示例的链接,而不要发送示例本身(我先替我的收件箱谢谢您)。
您对嗜好者内容有何见解?请将您的见解发送至 duncanma@microsoft.com,并祝编码愉快!
Coding4Fun
Duncan Mackenzie 白天是 Microsoft Visual Basic .NET 的 MSDN 内容策略者,在夜晚他则是一个孜孜不倦的代码编写人员。据说没有 Earl Grey 茶他就无法做任何工作,希望这不是真的。有关 Duncan 的详细信息,请访问他的站点。