开发优先级:乐趣第一

 

邓肯·麦肯齐
Microsoft 开发人员网络

2003 年 8 月 20 日

总结: Duncan Mackenzie 展示了功能的酷感如何增加在开发项目早期完成的机会,因为他创建了一个应用程序来检索和显示 RSS 源。 (13 个打印页)

适用于:
   Microsoft® Visual Basic® .NET

下载本文的源代码

人员让我继续工作

如果你一直在关注我的专栏,你可能会认为我把所有时间都花在编写有趣而酷炫的代码示例上,而无需处理任何无聊的事情。 嗯,这当然是计划,但它并不总是这样工作。 最近,我一直忙于在一个相对无聊的系统上工作,以至于我没有时间写任何有趣的东西。

不过,最近,我被交给了一个内部系统的新要求列表,这些要求按我应该处理每个功能的顺序排列。 我很快注意到,真正酷的东西隐藏在列表的末尾,标记为“好有”,这是PM所说的“我们 永远不会 建立这个... 但我们把它放在名单上,以幽默你。使用虚伪的假装,我对列表的排序顺序感到困惑,我决定先处理很酷的功能,并在我整理桌面并整理硬盘驱动器后,保存那些无聊的“优先级 1 /必须拥有”功能。

MSDN 中的规划页

在进入本文主题的“酷”功能之前,我确实应该提供一些有关我正在处理的系统的上下文。 几个月前,我构建了一个名为“页面规划器”的内部系统,该系统用于设计和生成构成 MSDN 开发人员中心的页面 (包括我为) 规划内容的 Visual Basic 网站。 此系统 (如图 1 所示,) 允许我们更新网站上的所有单独页面,包括所有特定于技术的文章页面,例如 https://msdn.microsoft.com/vbasic/using/building/windows

图 1. 页面规划器工作

维护这些网站的main日常工作涉及在新文章可用时更新相应的技术信息页面。 这通常通过手动输入 URL 或从浏览器窗口拖动链接来完成。 最新版本的 MSDN RSS 源 (,其中包含新内容) 的最新列表,以及类似网站(如 4GuysFromRolla.com)的类似源,生成了“从 RSS 源拖放链接”的“好有”请求。

设计新功能

作为一个相当低优先级的项目,请求没有比这一行更详细的描述,但我对 MSDN 周围的人正在寻找什么有一个很好的想法,所以我从一个粗略的设计开始。

RSS 查看器窗口将支持查看 RSS 源,以及将源中的链接拖动到系统的其余部分。 用户将能够从向所有系统用户或他们自己的个人列表公开的源主列表中进行选择,如果需要) ,他们可以选择直接输入源 URL, (将其添加到其个人列表中。

我想不出在 DataGrid 控件中显示具有可变大小内容、XHTML 支持等) 的源 (的简单方法,而无需一些自定义代码。 因此,我决定改为使用 Web 浏览器控件 (在我的 Microsoft® Windows® 窗体) 上托管 Microsoft Internet Explorer 来显示源。 使用 XSL 转换,可以将 RSS 信息转换为 HTML,然后将该 HTML 传递到浏览器控件进行显示。

注意 此实现决策的一个很好的副作用是,我免费获得拖放功能;Web 浏览器控件支持将链接从中拖动到其他应用程序中,而无需任何其他代码。

链接的主列表将存储在某种形式的中央存储库中,但用户的个人链接集将存储在本地并根据需要更新。 由于仍需要某些本地首选项编辑,因此功能的最终列表如下所示:

  • 从中央存储检索源的主列表。
  • 从本地存储检索个人源列表。
  • 检索 RSS 源并将其转换为 HTML。
  • 在 Web 浏览器中显示 HTML。
  • 允许用户输入 RSS 源的 URL。
  • 允许用户从其个人源存储中添加/编辑/删除。
  • 允许用户从源主列表添加/编辑/删除 (请注意,这肯定不适合) 的所有情况。

在我的特定情况下,我将将主源列表存储在main Page Planner 系统使用的同一共享数据库中,然后使用 SqlClient 类检索/编辑它,但可能需要 (基于 Web 服务的、基于文件的中央存储等) 实现不同的实现。 为了便于交换存储主源和个人源列表的特定方法,我设计了程序的这些方面,以便通过 IFeedList 接口稍微抽象化。 可以实现此操作以创建自己的方法来存储和检索源列表。

显示源

当然,存储和更新源列表真正次要于这个小项目的目的:main功能是检索和显示 RSS 源。 因此,最好我首先向你展示系统的那部分。

为了加载源本身,我使用 XMLDocumentLoad 方法。 然后,我将 XSL 从另一个 URL (或文件位置) 加载到 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 的源,因此现在应该已经足够了。 获得转换结果后,通过将转换结果放入嵌入浏览器控件中显示的网页正文中来显示它。

Dim myDOMDoc As mshtml.HTMLDocument
myDOMDoc = DirectCast( _
    Me.embeddedBrowser.Document, _
    mshtml.HTMLDocument)

myDOMDoc.body.innerHTML = result.ToString

我编写了到目前为止看到的所有代码,并使用小型示例应用程序测试了 XSL 代码的各个位, (请参阅图 2) 。 在继续操作时,我最终放弃了该示例,但就目前而言,这是针对各种 RSS 源测试代码的好方法。

图 2. 使用简单表单测试代码

确保一切正常

我遇到的第一个问题是,某些源没有以降序日期顺序显示 (最近项) ,这相当令人困惑。 事实证明,虽然大多数 RSS 源已经按日期降序排序,但 MSDN 源是一个明显的例外。 为了保持一致性,我决定通过 <xsl:sort> 元素将排序代码添加到 XSL。 由于我需要包含使用 Visual Basic .NET () 编写的用户定义的函数,以便将基于字符串的日期值转换为可排序的新格式,因此无法直接针对 pubDate 值进行排序,导致该转换的一个更有趣的方面。

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

函数本身非常简单,我只是尝试从字符串转换为日期,然后以允许轻松排序的格式输出回字符串。 <使用 xsl:sort> 函数,我使用 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 函数周围的 Catch 块不会处理错误,但它允许 XSL 仍然正常运行,即使代码无法分析日期字符串。

品种是生活的香料, 除非它是在你的 RSS

RSS 的规范在很多方面都是灵活的,包括正确处理 HTML 内容以及如何指定日期,也就是说,它允许多种方法。 当你编写系统来输出 RSS 时,这种灵活性非常出色,但当你尝试读取 RSS 时,它会使生活变得有点困难。 这种灵活性使我首先遇到日期问题:我测试的大多数源都使用了 pubDate 元素,但有一些源使用了 dc:date 元素。 我通过添加 xsl<:choose> 函数来处理此问题,以便使用和显示可用的日期属性。

<xsl:choose>
    <xsl:when test='pubDate'>
        <p>Posted on: 
            <xsl:value-of select="pubDate" /></p>
    </xsl:when>
    <xsl:when test='dc:date'>
        <p>Posted on: 
            <xsl:value-of select="dc:date" /></p>
    </xsl:when>
    <xsl:otherwise>
    <p>Couldn't Retrieve Date</p>
    </xsl:otherwise>
</xsl:choose>

我必须使用类似的方法来处理下载的 RSS 源中的任何 HTML 内容,使用 <xsl:choose> 在 RSS 源内处理 HTML 内容的三种main方法之间进行选择。 这三种常见方法是:

  • 使用 content:encoding 标记标记 HTML 块,并对所有标记进行 HTML 编码。
  • 将 HTML 放入 xhtml:body 元素中,并保留 HTML 标记不变。
  • 只需将其保留为未标记且内联,就像纯文本一样。

为了确保我能够处理这三种情况中的每一种,我在 XSLT) 中使用了另一个 <xsl:choose> 函数 (来为每个特定格式选择正确的实现,如果无法确定所使用的确切方法,则只假定未编码的内容。

<xsl:choose>
    <xsl:when test='xhtml:body'>
        <xsl:copy-of select='xhtml:body'/>
    </xsl:when>
    <xsl:when test='content:encoded'>
        <xsl:value-of 
            disable-output-escaping='yes'
            select='content:encoded'/>
    </xsl:when>
    <xsl:otherwise>
        <xsl:value-of
            disable-output-escaping='yes'
            select='description'/>
    </xsl:otherwise>
</xsl:choose>

最终结果应适用于使用这三种方法之一将其内容公开为 RSS 源的任何源, (xhtml:body、description 或 content:encoding) ,从而生成类似于图 3 所示的最终显示。

图 3. 显示源中的 HTML 内容

现在,请务必注意,每当要显示其他人 (提供的 HTML 内容(例如 RSS 源) 中的内容),您需要了解可能的风险,尤其是在使用我的方法替换“about:blank”页面的内容时。 当 HTML 显示在嵌入式浏览器中时,它在本地区域中运行,其安全限制可能比 Internet 区域低得多。 尽管有方法可以在显示 HTML 之前对其进行清理,但要保证 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 显示了最终界面,其中包含新的“保存”按钮和组合框,你可以使用该框从保存的源之一进行选取或直接输入 RSS 源的 URL。

图 4。 最终窗体包含“保存”按钮

在开发该系统时,我决定将其分解,以便将来更易于重复使用。 因此,嵌入浏览器现在与 XSL 和 RSS 代码合并到自定义控件中,该控件已放置在图 4 所示的窗体上。 为了在实际应用程序中使用此代码,我可能会进行一些额外的更改,以便我传入 SQL 连接,并将整个表单及其所有关联的代码放入库项目中。 最后,我将拥有一些可以非常轻松地从现有Windows 窗体应用程序上的按钮启动的内容。 但是,我已将此示例构建为独立应用程序,以便你可以自行运行所有应用程序。

资源

与往常一样,我需要使用 Web 上不同位置的一些资源来生成已完成的应用程序。 在此特定示例中,我未使用任何 GotDotNet 用户示例,但确实使用了:

  • Eric J. Smith 出色的 CodeSmith 实用工具,用于生成我的强类型源集合。
  • 某些初学者 XSL 从 RSS Bandit 的模板文件夹中被盗, (检查工作区太) 。
  • 肯特·沙基的各种 XSL 和“技术支持”部分。

我还将指出 RSS 数据的一些良好来源、使用本文中的代码显示的绝佳材料,以及一些出色的阅读内容。

当然,有很多其他 RSS 源在那里,但这些站点的源应该足以让你继续相当长一段时间。

编码挑战

在我的一些 Coding4Fun 列的末尾,我将面临一些编码挑战, 如果你有兴趣,需要你处理一些代码。 在本文中,挑战在于创建任何输出或使用 RSS 的内容。 托管代码是首选 (Visual Basic .NET、C#、J# 或托管 C++,请) ,但公开 COM 接口的非托管组件也不错。 只需将你制作的任何内容发布到 GotDotNet ,然后向我发送一封电子邮件, () duncanma@microsoft.com ,并解释你所做的工作以及为什么你觉得它很有趣。 你可以随时向我发送你的想法,但请只向我发送代码示例链接,而不是向我发送示例本身 (我的收件箱,感谢你提前) 。

对业余爱好者内容有自己的想法? 在 上告诉我 duncanma@microsoft.com,并愉快地编码!

 

Coding4Fun

Duncan Mackenzie 是 MSDN 白天的 Microsoft Visual Basic .NET 内容策略师,也是深夜的专用编码员。 有人建议,没有他的格雷伯爵茶,他根本无法做任何工作,但让我们希望我们永远不必发现。有关邓肯的更多信息,请参阅 他的网站