65.9K
CodeProject 正在变化。 阅读更多。
Home

如何为 ASP.NET AJAX 创建 HTML 编辑器

2008 年 5 月 27 日

LGPL3

13分钟阅读

viewsIcon

1032465

downloadIcon

20970

本文讨论了如何在 Microsoft AJAX 环境中创建 HTML 编辑器服务器控件。

引言

大多数博客、论坛和 Wiki 应用程序都使用 **HTML 编辑器**作为内容的主要创作工具。通过此类控件,在线用户可以创建和编辑 HTML 文档。用户可以修改文本(包括其格式、字体和颜色),以及添加链接和图像。通常,用户还可以查看和/或编辑 HTML 源代码。

Microsoft AJAX (ASP.NET AJAX Extensions) 引入了一种新的服务器控件实现模型,该模型包含相关的脚本。本文讨论了如何在 Microsoft AJAX 环境中创建 HTML 编辑器服务器控件。读者还可以下载 源代码,包括一个示例网页,并查看在线 演示

背景

DesignMode

现在,大多数现代浏览器都支持用户对显示的 HTML 内容进行“编辑”。当文档的 designMode 属性设置为 true 时,页面上的渲染 HTML 可以被用户编辑。在 designMode 模式下,文档的 execCommand 方法支持其他命令,这些命令可以“以编程方式”修改文档内容。例如,将字符串 bold 作为第一个参数传递给 execCommand 会通过添加适当的 HTML 标签和/或属性使选定的文本变为粗体。

本文末尾列出的 资源 更详细地讨论了 designModeexecCommand,并描述了如何实现一个基本的 HTML 编辑器。此外,本文提供的源代码也可以用于实现示例。

Microsoft AJAX 模型,用于带有相关脚本的服务器控件

作为 ASP.NET AJAX 的一部分,Microsoft 引入了一种新的“模型”,用于通过客户端脚本扩展服务器控件的功能。该模型在 ASP.NET AJAX 教程 中进行了描述,应与本文一起阅读。总的来说,我们创建两个相关的控件:

为了让 ScriptManager 知道如何创建和初始化相关的客户端控件,我们实现了新的 IScriptControl 接口,并添加了两个回调方法。然后,我们在 OnPreRenderRender 中添加了几行代码,以便在页面生命周期的适当时间触发这些回调。具体细节在教程中描述,并在下面对 HtmlEditor.cs 的讨论中再次提及。

我们还将客户端行为封装在 JavaScript 类中,该类实现为 Microsoft AJAX 客户端控件。这使得客户端控件对象能够以标准方式由客户端 AJAX 代码创建和初始化。具体细节在教程中描述,并在下面对 HtmlEditor.js 的讨论中再次提及。

源代码

单击下面的链接以下载源代码,包括一个示例网页。

系统要求

  • ASP.NET 4.0 或更高版本
  • ASP.NET AJAX

组件组成部分

  • HtmlEditor.cs(我们服务器控件的 C# 文件)
  • HtmlEditor.js(我们客户端控件的 JavaScript 文件)
  • Images/*.gif(我们工具栏图像的图像文件)

UI 元素

组件外观

  • 顶部两行工具栏
  • 底部两个选项卡
  • 一个编辑器区域,可在任一模式下显示和/或编辑文档

Screenshot - HtmlEditor.jpg

单击 此处 查看在线演示。

概述

HTML 结构

  • 控件的 Div 容器
    • 每个 ToolbarDiv 容器
      • 用于下拉列表的 Select 元素
      • 用于按钮的 Img 元素
    • 用于设计编辑器的 Div 容器
      • 用于设计模式文档的 IFrame 元素
    • 用于 HTML 编辑器的 Div 容器
      • 用于 HTML 模式文档的 IFrame 元素
        • 用于 HTML 模式编辑的 Textarea
    • 用于 TabbarDiv 容器
      • 每个选项卡的 Div 元素
        • 用于选项卡图标的 Img 元素
        • 用于选项卡文本的 Span 元素

服务器控件

  • 为每个所需的 HTML 元素创建子控件
  • 提供用于配置属性(颜色等)的 public 属性方法
  • 实现传递给客户端控件进行初始化的属性的属性方法
  • 实现默认属性值
  • 实现 IScriptControl 方法

客户端控件

  • 提供用于在初始化时从服务器控件传递的属性的 getset 属性方法
  • 动态创建 IFrame 文档
  • designMode 设置为 true 以用于设计模式文档
  • ToolbarTabbar 鼠标事件提供适当的事件处理程序
  • 在适当的时候转换为 XHTML
  • designMode 浏览器插入的已弃用语法转换为基于标准的等效语法
  • 过滤允许的标签和属性
  • 移除/恢复 designMode 浏览器插入的无关紧要的默认标签

HtmlEditor.cs

该组件本身包含许多不同的 HTML 元素,因此服务器控件类派生自 CompositeControl。此外,该类必须实现 IScriptControl 方法。

public class HtmlEditor : CompositeControl, IScriptControl

CompositeControl 中,子控件在 CreateChildControls 方法中添加。

protected override void CreateChildControls()
{
    ...

    CreateToolbars(...);

    this.Controls.Add(CreateHtmlArea());
    this.Controls.Add(CreateDesignArea());
    this.Controls.Add(CreateTabbar());
    this.Controls.Add(CreateUpdateArea());

    base.CreateChildControls();
}

要实现 IScriptControl 接口,需要两个回调方法。第一个是 GetScriptReferences,它告诉 ScriptManager 需要加载哪些相关的脚本文件以及从哪里加载。对于此服务器控件,我们选择将 HtmlEditor.js 文件嵌入到我们的程序集资源中。我们告诉 ScriptManager 完整的资源路径和程序集名称,以便它能够从那里加载,从而简化部署。

protected virtual IEnumerable<ScriptReference>
    GetScriptReferences()
{
    ScriptReference htmlEditorReference =
        new ScriptReference(
        "Winthusiasm.HtmlEditor.Scripts.HtmlEditor.js",
        "Winthusiasm.HtmlEditor");

    ...

    return new ScriptReference[] { htmlEditorReference, ... };
}

第二个回调方法 GetScriptDescriptors 将客户端控件中的属性“映射”到服务器控件中的属性。ScriptManager 使用此信息在其客户端创建过程中为客户端控件设置适当的值。

protected virtual IEnumerable<ScriptDescriptor>

    GetScriptDescriptors()
{
    ScriptControlDescriptor descriptor =
        new ScriptControlDescriptor("Winthusiasm.HtmlEditor",
        this.ClientID);

    descriptor.AddProperty("htmlencodedTextID",
                            this.HtmlEncodedTextID);
    ...

    return new ScriptDescriptor[] { descriptor };
}

尽管我们已经实现了 IScriptControl 方法,但还需要进行两个修改才能调用 IScriptControl 回调。首先,必须修改 OnPreRender 以调用 RegisterScriptControl,如下所示:

protected override void OnPreRender(EventArgs e)
{
    ...

    if (!this.DesignMode)
    {
        // Test for ScriptManager and register if it exists.

        sm = ScriptManager.GetCurrent(Page);

        if (sm == null)
            throw new HttpException(
            "A ScriptManager control must exist on the page.");

        sm.RegisterScriptControl(this);

        ...
    }

    base.OnPreRender(e);
}

然后,必须修改 Render 以调用 RegisterScriptDescriptors

protected override void Render(HtmlTextWriter writer)
{
    if (!this.DesignMode)
        sm.RegisterScriptDescriptors(this);

    base.Render(writer);
}

HtmlEditor.js

由于 HTML 编辑器的客户端行为相当广泛,因此客户端控件需要大量的 JavaScript 代码。其中大部分是典型的 designMode 客户端编程。从本文的角度来看,整个 JavaScript 代码结构已按照 Microsoft 推荐的客户端编码模型进行格式化,该模型适用于所有基于 AJAX 客户端库构建的客户端控件。这包括:

  • 命名空间注册
  • 构造函数
  • 用逗号分隔所有方法的 Prototype 块
  • 用于每个从服务器控件传递的属性的 Prototype getset 方法
  • Prototype initialize 方法
  • Prototype dispose 方法
  • Descriptor 方法
  • 类注册

命名空间注册

Type.registerNamespace("Winthusiasm");

构造函数

Winthusiasm.HtmlEditor = function(element)
{
    Winthusiasm.HtmlEditor.initializeBase(this, [element]);

    this._htmlencodedTextID = "";
    ...
}

用逗号分隔方法的 Prototype 块

Winthusiasm.HtmlEditor.prototype =
{
    method1: function()
    {
        ...
    },

    method2: function()
    {
        ...
    },

    ...
}

用于每个从服务器控件传递的属性的 Prototype getset 方法

get_htmlencodedTextID: function()
{
    return this._htmlencodedTextID;
},

set_htmlencodedTextID: function(value)
{
    this._htmlencodedTextID = value;
},

...

Prototype initialize 方法

initialize: function()
{
    Winthusiasm.HtmlEditor.callBaseMethod(this, 'initialize');

    ...
},

Prototype dispose 方法

dispose: function()
{
    ...

    Winthusiasm.HtmlEditor.callBaseMethod(this, 'dispose');
},

Descriptor 方法

Winthusiasm.HtmlEditor.descriptor =
{
    properties: [ {name: 'htmlencodedTextID', type: String },
        ... ]
}

类注册

Winthusiasm.HtmlEditor.registerClass("Winthusiasm.HtmlEditor", 
                                     Sys.UI.Control);

Using the Code

下载适用的 zip 文件并将其解压到一个新目录。它包括:

  • Winthusiasm.HtmlEditor 控件项目
  • 一个使用 HtmlEditor 控件的示例网站
  • 包含两者的解决方案

双击解决方案文件以启动 Visual Studio,然后从菜单中选择“生成/重新生成解决方案”。这将生成项目并将项目 DLL 复制到示例网站的 Bin 文件夹。将示例网站设置为“启动项目”,将 Demo.aspx 设置为“启动页”,然后按 F5。

使用模型

  • 将网站创建为“支持 **AJAX** 的 ASP.NET 网站”
  • 将程序集 Winthusiasm.HtmlEditor.dll 复制到 Bin 文件夹
  • 向页面添加 Register 语句
  • 向页面添加自定义控件 **标签**
  • 使用 Text 属性设置编辑器 HTML
  • 在适当的时候 **保存** HTML
  • 使用 Text 属性获取“保存”的 HTML

示例 Register 语句

<%@ Register TagPrefix="cc"
    Namespace="Winthusiasm.HtmlEditor"
    Assembly="Winthusiasm.HtmlEditor" %>

示例自定义控件 **标签**

<cc:HtmlEditor ID="Editor"
               runat="server"
               Height="400px"
               Width="600px" />

示例设置 Text

Editor.Text = initialText;

使用模型详解:在适当的时候保存 HTML

编辑器的“客户端”Save 方法指示编辑器存储当前 HTML(如果适用,则转换为 XHTML),并清除 modified 标志。当编辑器属性 AutoSave 设置为 true(默认值)时,在表单提交之前,客户端 Save 方法会作为客户端 ASP.NET 验证过程的一部分 **自动** 调用。所有 CausesValidation 属性设置为 true(默认值)的控件都会触发此行为。如果 AutoSave 实现不适用或不足,则可以通过可选的 SaveButtons 属性或手动附加客户端脚本来触发客户端 Save

检索保存的文本

在服务器端事件处理程序中,使用 Text 属性检索“保存”的文本。

DataStore.StoreHtml(Editor.Text);

关注点

嵌入式资源

为了简化部署,HtmlEditor.js 文件和工具栏按钮的图像文件被 嵌入HtmlEditor.dll 程序集中的资源中。

HtmlEncode 和 HtmlDecode

当客户端触发提交行为时,将未编码的 HTML 存储在表单控件(如文本区域或隐藏输入元素)中是存在问题的。

Screenshot - Error500.jpg

此实现使用 HiddenField 来存储编辑后的 HTML,因此始终以 HtmlEncoded 状态存储文本。

回发时设置 Text 属性

用于存储 HTML 文本的 HiddenFieldUpdatePanel 中创建。如果在“异步”回发期间设置了 Text 属性,服务器控件将通过调用 UpdatePanel 上的 Update 来响应,并向 ScriptManager 注册 DataItem。由于客户端控件使用 endRequest 事件处理程序来监视所有 PageRequestManager 更新,因此它会检测到 DataItem 已注册,并自动更新编辑器中的 HTML。

XHTML 转换

designMode 模式下,Internet Explorer 和 Firefox 都会输出“HTML”。为了转换为“XHTML”,此实现会读取“客户端”DOM 树,并以 XHTML 格式输出元素和属性。这种将 HTML 转换为其 XHTML 等效项的“客户端”转换有效地“隐藏”了底层的 HTML 实现。当用户从设计模式切换到 HTML 模式时,显示的输出是 XHTML。此外,服务器端的 Text 属性会检索 XHTML。请注意,编辑器配置属性 OutputXHTML 默认为 true。如果设置为 false,则不进行 XHTML 转换,输出为浏览器生成的 HTML。

转换已弃用语法

Internet Explorer 和 Firefox 在 designMode 模式下运行时都会输出并“期望修改”已弃用的语法。因此,此编辑器的实现会在转换为 XHTML 时将已弃用的语法转换为基于标准的等效语法。它会在转换回 designMode HTML 时“恢复”已弃用的语法。请注意,编辑器配置属性 ConvertDeprecatedSyntax 默认为 true。如果设置为 false,则不进行转换,输出将包含已弃用的语法。

在 Internet Explorer 中转换段落

9.0 及更早版本的 Internet Explorer 在 designMode 模式下运行时会输出并“期望修改”段落元素。如果编辑器配置属性 ConvertParagraphs 设置为 true,此编辑器的实现会在 designMode 模式下“修改”段落元素的显示样式。在转换为 XHTML 时,它会将段落元素转换为适当的 break 和/或 div 元素,并在转换回 designMode HTML 时“恢复”段落元素。请注意,此配置属性 **仅** 适用于 9.0 及更早版本的 Internet Explorer,并且默认为 false。除非设置为 true,否则不会进行转换,输出将包含段落元素。

注意事项

脚本注入

包含 HTML 编辑器的网页很可能存储用户创建的 HTML。稍后,它可能会在另一个网页中显示该 HTML,可能供其他用户查看。这会带来经典的脚本注入漏洞,甚至可能是 SQL 注入漏洞。HTML 编辑器的客户端应采取适当的 措施 来控制这些风险。

浏览器测试

本文讨论的控件已在 Firefox 2+、IE 6+ 和 Opera 9+ 上进行了测试。

结论

通过使用 Microsoft 的教程以及下面列出的 designModeexecCommand 资源,HTML 编辑器服务器控件已实现为可在 Microsoft AJAX 环境中工作。可能的增强功能包括:

  • 对 Safari 的支持
  • 对其他 HTML 结构的支持
  • 其他配置属性

额外资源

designMode 和 execCommand

其他教程和文章

在线文档

历史

  • 2012 年 6 月 15 日
    1. Internet Explorer 9 选择和范围问题的解决方法
    2. 将 FormatHtmlMode 限制在非标准模式下的 Internet Explorer 版本
  • 2009 年 7 月 1 日
    1. ViewState 大小减少 72%
  • 2009 年 6 月 19 日
    1. 客户端 ASP.NET Validator 支持的修复
  • 2009 年 6 月 17 日
    1. 完全支持客户端 ASP.NET Validators
    2. 修复 pre 标签转换问题
  • 2009 年 6 月 7 日
    1. 修复了 Internet Explorer 复制 DOM 元素的 XHTML 转换问题
    2. valign 包含在默认的 AllowedAttributes
    3. 将已弃用的 valign 属性转换为基于标准的等效属性
    4. 修复了 GetInitialHtmlEditorOuterHTML 中缺失的 textarea 结束标签
  • 2009 年 4 月 29 日
    1. 重构 Link 对话框如何读取 href 属性
    2. 重构 Image 对话框如何读取 src 属性
    3. 重构以支持类扩展
    4. 重构客户端 descriptor
    5. ModalPopupExtender 问题的解决方法
    6. 修复了内部计时器的客户端 Save 问题
  • 2009 年 4 月 9 日
    1. DesignModeEmulateIE7 属性
    2. Internet Explorer 8 designMode 滚动条问题的解决方法
  • 2009 年 3 月 19 日
    1. CLSCompliant(true) 程序集属性
    2. 演示页面上的 FAQ 等链接
    3. SetMode 时强制关闭打开的对话框
    4. Visual Studio 2008 设计器渲染的解决方法
    5. IFrame onload 验证器问题的解决方法
    6. 修复了多个 W3C 验证器问题
    7. 修复了 ToLower Culture 问题
  • 2008 年 7 月 13 日
    1. 修复了 Firefox 3 删除/退格键问题
  • 2008 年 6 月 29 日
    1. 修复了 Firefox 3 初始渲染布局问题
    2. 修复了 Opera 9.5 问题
  • 2008 年 5 月 28 日
    1. Visual Studio 2008 (ASP.NET 3.5) 源代码
    2. Visual Studio 2005 (ASP.NET 2.0) 源代码
  • 2008 年 5 月 25 日
    1. 修复了初始化问题
    2. 修复了 Color.htm 问题
    3. 修复了转换问题
    4. 修复了 Link.htm 问题
  • 2008 年 2 月 3 日
    1. 修复了 Image.htm 问题
  • 2008 年 1 月 2 日
    1. 客户端类的命名空间更改
    2. 客户端 GetVersion 方法
    3. 在默认允许的标签中添加了 subsup
    4. 修复了 DesignModeCss 绝对路径问题
    5. 修复了设计器嵌入图像问题
  • 2007 年 12 月 3 日
    1. AutoSaveValidationGroups 属性
    2. 修复了 ConvertParagraphs 水平线问题
  • 2007 年 11 月 21 日
    1. AutoSave 属性
    2. Modified 服务器端属性
    3. ValidationProperty 属性
    4. 修复了客户端 GetText 方法
    5. 修复了资源标识符
    6. 强制执行像素高度和宽度
  • 2007 年 10 月 30 日
    1. SaveButtons 属性
    2. 在“附加资源”部分添加了 Selection 和 Range 链接
  • 2007 年 10 月 18 日
    1. 工具栏
    2. 分隔符工具栏元素
    3. 工具栏背景图像
    4. 配色方案:自定义、Visual Studio、默认
    5. ColorScheme 属性
    6. CreateColorSchemeInfo 服务器端事件属性
    7. ToolstripBackgroundImage 属性
    8. ToolstripBackgroundImageCustomPath 属性
    9. NoToolstripBackgroundImage 属性
    10. EditorInnerBorderColor 属性
    11. SelectedTabTextColor 属性
    12. DialogSelectedTabTextColor 属性
    13. 演示选项:切换模式、配色方案和工具栏
    14. 修复了 ScriptReference 问题
    15. 重构了 Color.htm
  • 2007 年 10 月 9 日
    1. 对话框框架
    2. 文本颜色对话框
    3. 背景颜色对话框
    4. 超链接属性对话框
    5. 图像属性对话框
    6. 对话框颜色属性
    7. DialogFloatingBehavior 属性
    8. CreateDialogInfo 事件属性
    9. alttitle 包含在允许的属性中
    10. 修复了上下文问题
    11. 修复了 XHTML 字体转换问题
  • 2007 年 9 月 24 日
    1. Toolbars 属性
    2. 移除了 ToolbarButtonsTop 属性
    3. 移除了 ToolbarButtonsBottom 属性
    4. 移除了 ToolbarSelectLists 属性
    5. 修复了空工具栏问题
  • 2007 年 8 月 29 日
    1. 可选工具栏按钮:保存、新建、设计、HTML、视图
    2. ToggleMode 属性
    3. ToolbarDocked 属性
    4. ToolbarClass 属性
    5. EditorBorderSize 属性
    6. EditorBorderColor 属性
    7. SelectedTabBackColor 属性
    8. ModifiedChanged 客户端事件处理程序属性
    9. ContextChanged 客户端事件处理程序属性
    10. Save 服务器端事件处理程序属性
    11. 修复了 ConvertParagraphs 问题
  • 2007 年 8 月 7 日
    1. ToolbarButtonsTop 属性
    2. ToolbarButtonsBottom 属性
    3. ToolbarSelectLists 属性
    4. CreateToolbarInfo 事件属性
    5. TextDirection 属性
    6. 在允许的标签中包含 dl
  • 2007 年 8 月 5 日
    1. ConvertParagraphs 属性
    2. ReplaceNoBreakSpace 属性
    3. 修复了 Internet Explorer 水平线问题
    4. 修复了 Internet Explorer 有序列表和无序列表问题
    5. 修复了 $find 启动问题
  • 2007 年 5 月 29 日
    1. Internet Explorer 6 安全上下文问题的额外修复
    2. EditorBackColor 属性
    3. EditorForeColor 属性
    4. DesignModeCss 属性
    5. NoScriptAttributes 属性
  • 2007 年 5 月 25 日:修复了 Internet Explorer 6 安全上下文问题
  • 2007 年 5 月 15 日:支持 Opera 9+
  • 2007 年 5 月 14 日:添加了属性
    1. InitialMode
    2. DesignModeEditable
    3. HtmlModeEditable
  • 2007 年 5 月 11 日:修复了 Firefox 隐藏/显示编辑器问题
  • 2007 年 5 月 4 日
    1. 添加了 Internet Explorer 键盘按下处理程序
    2. 修复了回发设置初始 Text 问题
  • 2007 年 5 月 2 日:修复了初始文本问题
  • 2007 年 5 月 1 日
    1. 添加了 Modified 和 Save 概念
    2. 转换了 ublockquotealign 的已弃用语法
  • 2007 年 4 月 10 日:XHTML 输出
  • 2007 年 4 月 2 日:修复了 Internet Explorer 焦点问题
  • 2007 年 3 月 31 日:初始文章
© . All rights reserved.