C# 中的 ASP.NET ListEditor

发布日期: 4/1/2004 | 更新日期: 4/1/2004

Chris Lovett

Microsoft Corporation

2000 年 7 月 25 日

编者按:现在您可以下载 .NET 框架 SDK 技术预览 ,其中包括 C# 编译器。请注意,这是一个早期的技术预览,而且下载量很大。

下载 Listeditor.exe

上周,在奥兰多举办的 Microsoft Professional Developer's Conference (PDC) 上,我感到兴奋之极。能够站在数千名业内最佳的软件工程师面前,并向他们展示我们在过去的几年内一直忙于开发的内容,是多么令人激动啊!我在演讲的前一夜彻夜未眠。我们随后收到的问题的水平给我留下了深刻的印象,而且我非常感谢那些积极的反馈!

有趣的是,这个新的 .NET 平台是 Microsoft 秘密开发的产品之一,甚至连媒体都未能捕捉到它的身影。他们仍然认为 .NET 只不过是一个空头软件。那些参加过 PDC 的人员了解真实情况。.NET 框架中有大量令人惊奇的创新之处 — 它的开发工作已经持续了几年。我本人现在已用 C # 进行了一年左右的编程,我喜欢它!它无疑是我曾经用过的最高效的编程环境 — 我已经用过已面市的几乎所有的环境。

我们有一个崭新的、完全用 C# (C-sharp) 实现的 XML 系列技术。这包括流级类(例如,XmlReader 和 XmlWriter);W3C DOM 类(例如,XmlNode、XmlDocument、XmlElement 等);XPath 类(例如,XmlNavigator)和XSLT 类(包括 XslTransform)。

您将看到有关所有这些资料的大量概述文章和参考资料,因此我不会在此赘述。相反,我决定实现我在 PDC 上许下的诺言,即,将我的 April article 中的 ListEditor 应用程序移植到 C# 上,并使其在这个新的 .NET ASP+ 框架中运行。请参阅 ASP+ 和 C# 源代码。

现在,因为我已经使用数据传输格式 XML,将此应用程序设计为在客户端和服务器之间松散耦合,所以此应用程序的客户端保持不变(除了 .asp 文件名变为 .aspx)。


1.

我可以简单地插入一个新的服务器端实现,这一事实进一步例证了为何松散耦合 Web 结构能够工作得那样好。 传递出去的 XML 是我在客户端上编辑的列表,返回的 XML 由小的 XML "updategram" 组成,如下所示:

<updatesschemastamp="8">
 <updatetype="modify" location="//item[id='102']" by="clovett" name="description">
  <old></old>
  <new>Fine-grained notification of changes in the tree</new>
 </update>
</updates>

移植到 ASP+

我直接将原始的 ASP JScript? 代码移植到 ASP+ C#。我本可以执行更多的操作(重新设计服务器端代码,以便使用所有新的 .NET Web 服务、ADO+ 等),但是我还是决定不这样做,这是由于我要为您展示从现有的 Web 应用程序插入到新的 .NET 框架中是多么容易。

因为移植工作进展得很顺利,而且 C# 又是强类型语言,所以我在我的原始代码中发现并修复了大量错误。以前,编辑器根本不能处理将数据存储在属性中的列表;现在,这得到了修复。我还修改了 ListEditor,以便它不再允许您以可导致无效 XML 的方式来编辑架构。以前,人们可以通过将假元素名放在架构中来方便地中断 XML。但该新版本相比强壮得多。它还将不再允许您删除必需的 ID 元素。

ASP+ 在我每次更改 C# 时都对其进行编译,以便它完全按照旧的 ASP 模式工作。通常,在您进行了一些更改之后,当 ASP 首次编译页面时,您会看到小小的延迟(1 秒 或 2 秒),但是随后的请求几乎会瞬间完成。

我还发现新的 XML 类中有几个新错误,希望您在使用“技术预览”环节时也会发现它们。幸运的是,这些错误都顺利地排除了。您在代码中将看到对这一影响的 "BUGBUG" 注释。

然后,我决定稍微整理一下我的代码,方法是将 ListEditor 页中的所有实际功能都移到一个名为 "ListEditor" 的新的 C# 类中。该类具有下列公共方法:

public class ListEditor
{
    public ListEditor(HttpApplicationState app);
    public string GetUserName(HttpRequest Request, HttpResponse Response);
    public string GetNextId(string filename, string where);
    public string Update(string filename, TextReader updategram);
    public string UpdateSchema(string filename, TextReader updategram);
    public string Sync(string filename, string timestamp, string xsl);
}

ASP+ 页现在成为委托给该类的所有“皮包骨型”包装程序。例如,save.aspx 页看上去如下所示:

<%@ Page Language="C#" Src="ListEditor.cs" %>
<%@ Import Namespace="System.Collections" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.NewXml" %>
<%@ Assembly Name="System.XML.dll" %>
<%
    Response.Expires = -1;
    Response.ClearContent();
    ListEditor editor = new ListEditor(Application);
    Application.Lock();
    string result;
    try {
        result = editor.Save(Server.MapPath(Request.QueryString["file"]),new StreamReader(Request.InputStream));
          } catch (Exception e) {
            result = editor.FormatException(e);
          }
    Application.UnLock();
%>
<%=result%>

GetUserName 函数只封装身份验证代码,该代码的外观与原始 ASP 代码基本相同。这次,我提供一种通过将用户名指定为 URL 参数来跳过身份验证的方法。例如:

http://clovett4/listeditor/edit.aspx?file=feedback.xml&user=test

GetNextId 函数返回在将新项插入到列表或架构中时要使用的下一个唯一 ID。此函数是从 nextid.aspx 页调用的:

http://clovett4/listeditor/nextid.aspx?file=feedback.xml&where=/test

此函数的代码看上去如下所示(关键片段以粗体表示):

public string GetNextId(string file, string where)
{
    XmlDocument curdoc = new XmlDocument();
    curdoc.Load(file);
    XmlNavigator nav = new DocumentNavigator(curdoc);
    if (nav.SelectSingle(where)) {
        int next = Int32.Parse(nav.GetAttribute("next"))+1;
        string value = Int32.Format(next,"d");
        nav.SetAttribute("next", value);
        curdoc.Save(filename);
        return "ok " + value;
    }
    return "error: node not found";
}

首先,上面的代码加载要被编辑为 XmlDocument 对象(在本例中为 "feedback.xml")的列表。该 XmlDocument 对象是新的 C# 版本的 World Wide Web Consortium (W3C) Document Object Model

接着,该代码用 XmlNavigator 类包装该 XmlDocument 对象。它使用 XPath 表达式来查找包含 next 属性的节点,该属性随后递增。然后,它将文档保存回磁盘中,并将新 ID 返回到客户端。

XmlNavigator

XmlNavigator 是 .NET 框架中一个重要的新类,因此,我想花点时间来介绍它。XMLNavigator 可为任何 XML 树提供“游标”或“迭代程序”,以及下列用于围绕树进行移动的方法:

MoveToNext 移到下一辈。

MoveToPrevious 移到上一辈。

MoveToParent 在树中上移。

MoveToFirstChild 在树中下移。

MoveToAttribute 移到属性轴中。

XmlNavigator 类还可以用如下方法修改树:

Insert 将新节点插入到树中。

Move 围绕树移动节点。

Remove 将节点从树中删除。

CopyChildren — 有无数人询问过这一方法!

XmlNavigator 类还为处理 XPath 表达式提供丰富的支持。在本例中,我们使用了 SelectSingle 函数,它是 MSXML selectSingleNode 方法新的等效托管方法。

XMLNavigator 类还为 XPath 表达式提供其他方法:

Select 是 MSXML selectNodes 方法的等效方法。

Evaluate 从表达式返回大量字符串结果。

Matches 返回有关当前的代码是否与表达式相匹配的结果。

Compile 对表达式进行编辑,以便您可以反复使用它。

例如,

int total = (int)nav.Evaluate("count(/list/item)");

将返回列表中项元素的数量。

现在,您很有可能自言自语道:“这的确是个好东西,但为什么用游标呢?为什么不用这些方法扩展 DOM 呢?”答案很简单。XmlNavigator 类可以定位任何 XML,而不仅仅是导航内存中的 DOM 树。例如,我们有一个可用来定位系统注册表的示例 XmlNavigator 类。因为 DOM 不能进行缩放,所以,用注册表中的所有内存填充内存中的 DOM 树根本不现实。DOM 需要“节点的对象标识” — 这意味着当您每次从 DOM 请求一个节点时,您都应该返回到同一个对象。这使得很难进行虚拟化。XmlNavigator 类解决了此问题 — 而且我们实际上已经通过提供名为 DataDocumentNavigatorXmlNavigator 子类使用了它,该子类可以定位存储在 ADO+ 数据集中的关系表、行和列。

XmlNavigator 类的 XPath 支持还允许您为 XslTransform 类提供输入。插入 DataDocumentNavigator 后,您很快就会看到,您现在可以像转换 XmlDocument 对象的内容那样方便地用 XSLT 来转换关系数据。非常棒!

让我们回到 ListEditor。正如我移植这个 ListEditor 时那样,在将您的代码从 DOM 切换到 XmlNavigator 模型时也涉及到一些工作。我发现这很容易做 — 而且,因为可以使用一些方便的方法(例如,CopyChildren),所以,我的代码实际上变小了。

让我们看一下另一个函数。当 updategram 的类型为 type="insert" 时,Update 函数将调用下面的 Insert 函数.

private string Insert(XmlNavigator doc,
                      XmlNavigator schema,
                      XmlNavigator update)
{
    string id = update.GetAttribute("id");
    schema.SelectSingle("//ElementType");
    string name = schema.GetAttribute("name");
    string idname = "@id";
    if (schema.SelectSingle("element[@type='id']"))
        idname = "id";
    XmlNavigator doc2 = doc.Clone();
    string query = "//" + name + "[" + idname + "='" + id + "']";
    if (doc2.SelectSingle(query))
        return "id " + id + " already in use";
    query = update.GetAttribute("before");
    TreePosition pos = TreePosition.LastChild;
    if (query != null && query != "") {
        if (doc.SelectSingle(query))
            pos = TreePosition.Before;
    }
    doc.CopyChildren(pos, update);
    return "ok";
}

对于那些看过原始源代码的用户来说,该代码看上去将很熟悉。唯一显著的区别是它使用了 CopyChildren 方法。您还将注意到上面的代码没有插入空白。当您 Save(保存)文档时,新的 XmlDocument 类设置 XML 的格式,以便使其具有可读性。

XmlDocument

让我们再看一段代码 — 这次是一些 DOM 代码。与原始的 ListEditor 一样,该新版本将在一个处于 ASP Application 状态的共享文档中,对更新进行存储。在每次收到更新时,Save.asp 和 SaveSchema.asp 页都附加到这个共享文档。此操作的新 C# 代码如下所示:

private void AppendLog(XmlDocument updates, string schemastamp,
                       string timestamp)
{
    XmlDocument log = this.UpdateLog;
    XmlElement logroot = log.DocumentElement;
    logroot.SetAttribute("schemastamp",schemastamp);
    logroot.SetAttribute("timestamp", timestamp);
    foreach (XmlNode update in updates.DocumentElement) {
        if (update.NodeType == XmlNodeType.Element) {
            XmlElement newnode = log.ImportNode(update, true);
            newnode.SetAttribute("timestamp", timestamp);
            newnode.SetAttribute("schemastamp", schemastamp);
            logroot.AppendChild(newnode);
        }
    }
}

请注意,新的 XML 文档对象模型被很好地集成到 C# 编程语言中,因此您可以针对 XmlNode 类的子级使用 foreach 语句。另请注意,(正如在 W3C DOM 规范中定义的那样),为了使节点从一个文档对象移到另一个文档对象,您需要使用 ImportNode 方法。

结论

在熟悉了 JavaScript 编程之后,您会很容易想到 C#。我发现自己在最终习惯于编写 "string name = ..." 之前,经常键入 "var name = ... "。除此之外,可以很自然地过渡。

新的 .NET 框架和 C# 都是真实的。不久,我的新的 C# 版本的 ListEditor 就会在各个方面远远超越旧版本。最大的挑战在于如何将其中的代码写得更加简洁、更加工整,如何向其中添加新功能,以及如何令其更多地使用绝佳的新 .NET 框架。

ListEditor 仍然是为少量代码设计的。性能瓶颈实际上是文件系统,因为它在每次发出 ASP+ 请求时都加载、更新和保存 XML 列表。然而,实际上它应当将资料缓存到内存中,或者使用真正的数据库后端。但是,如果您针对一百万个列表进行少量编辑,ListEditor 确实具有明显的优势。

Chris Lovett 是 Microsoft 的 XML 小组的项目经理。

转到原英文页面

posted on 2005-01-17 15:13  Manho  阅读(635)  评论(8编辑  收藏  举报

导航