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






4.96/5 (106投票s)
本文讨论了如何在 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 标签和/或属性使选定的文本变为粗体。
本文末尾列出的 资源 更详细地讨论了 designMode
和 execCommand
,并描述了如何实现一个基本的 HTML 编辑器。此外,本文提供的源代码也可以用于实现示例。
Microsoft AJAX 模型,用于带有相关脚本的服务器控件
作为 ASP.NET AJAX 的一部分,Microsoft 引入了一种新的“模型”,用于通过客户端脚本扩展服务器控件的功能。该模型在 ASP.NET AJAX 教程 中进行了描述,应与本文一起阅读。总的来说,我们创建两个相关的控件:
- 实现
IScriptControl
接口的服务器控件 - 派生自
Sys.UI.Control
(AJAX 客户端库的一部分)的相关客户端控件
为了让 ScriptManager
知道如何创建和初始化相关的客户端控件,我们实现了新的 IScriptControl
接口,并添加了两个回调方法。然后,我们在 OnPreRender
和 Render
中添加了几行代码,以便在页面生命周期的适当时间触发这些回调。具体细节在教程中描述,并在下面对 HtmlEditor.cs 的讨论中再次提及。
我们还将客户端行为封装在 JavaScript 类中,该类实现为 Microsoft AJAX 客户端控件。这使得客户端控件对象能够以标准方式由客户端 AJAX 代码创建和初始化。具体细节在教程中描述,并在下面对 HtmlEditor.js 的讨论中再次提及。
源代码
单击下面的链接以下载源代码,包括一个示例网页。
系统要求
- ASP.NET 4.0 或更高版本
- ASP.NET AJAX
组件组成部分
- HtmlEditor.cs(我们服务器控件的 C# 文件)
- HtmlEditor.js(我们客户端控件的 JavaScript 文件)
- Images/*.gif(我们工具栏图像的图像文件)
UI 元素
组件外观
- 顶部两行工具栏
- 底部两个选项卡
- 一个编辑器区域,可在任一模式下显示和/或编辑文档
单击 此处 查看在线演示。
概述
HTML 结构
- 控件的
Div
容器- 每个
Toolbar
的Div
容器- 用于下拉列表的
Select
元素 - 用于按钮的
Img
元素
- 用于下拉列表的
- 用于设计编辑器的
Div
容器- 用于设计模式文档的
IFrame
元素
- 用于设计模式文档的
- 用于 HTML 编辑器的
Div
容器- 用于 HTML 模式文档的
IFrame
元素- 用于 HTML 模式编辑的
Textarea
- 用于 HTML 模式编辑的
- 用于 HTML 模式文档的
- 用于
Tabbar
的Div
容器- 每个选项卡的
Div
元素- 用于选项卡图标的
Img
元素 - 用于选项卡文本的
Span
元素
- 用于选项卡图标的
- 每个选项卡的
- 每个
服务器控件
- 为每个所需的 HTML 元素创建子控件
- 提供用于配置属性(颜色等)的
public
属性方法 - 实现传递给客户端控件进行初始化的属性的属性方法
- 实现默认属性值
- 实现
IScriptControl
方法
客户端控件
- 提供用于在初始化时从服务器控件传递的属性的
get
和set
属性方法 - 动态创建
IFrame
文档 - 将
designMode
设置为true
以用于设计模式文档 - 为
Toolbar
和Tabbar
鼠标事件提供适当的事件处理程序 - 在适当的时候转换为 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
get
和set
方法 - 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 get
和 set
方法
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 存储在表单控件(如文本区域或隐藏输入元素)中是存在问题的。
此实现使用 HiddenField
来存储编辑后的 HTML,因此始终以 HtmlEncoded
状态存储文本。
回发时设置 Text 属性
用于存储 HTML 文本的 HiddenField
在 UpdatePanel
中创建。如果在“异步”回发期间设置了 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 的教程以及下面列出的 designMode
和 execCommand
资源,HTML 编辑器服务器控件已实现为可在 Microsoft AJAX 环境中工作。可能的增强功能包括:
- 对 Safari 的支持
- 对其他 HTML 结构的支持
- 其他配置属性
额外资源
designMode 和 execCommand
- designMode 属性
- execCommand 方法
- Midas 规范
- Mozilla 中的富文本编辑
- Mozilla 富文本编辑演示
- Midas - Mozilla 开发者中心
- 将使用 document.designMode 的应用程序从 Internet Explorer 转换为 Mozilla
- 富文本编辑器 - 第一部分
- 富文本编辑器 - 第二部分
- Opera 浏览器 Wiki - Textarea 编辑器
- FreeTextBox
- Selection 对象 (document) - Internet Explorer
- DOM: Selection - MDC
- TextRange 对象 - Internet Explorer
- DOM: Range - MDC
其他教程和文章
在线文档
历史
- 2012 年 6 月 15 日
- Internet Explorer 9 选择和范围问题的解决方法
- 将 FormatHtmlMode 限制在非标准模式下的 Internet Explorer 版本
- 2009 年 7 月 1 日
ViewState
大小减少 72%
- 2009 年 6 月 19 日
- 客户端 ASP.NET
Validator
支持的修复
- 客户端 ASP.NET
- 2009 年 6 月 17 日
- 完全支持客户端 ASP.NET
Validators
- 修复
pre
标签转换问题
- 完全支持客户端 ASP.NET
- 2009 年 6 月 7 日
- 修复了 Internet Explorer 复制 DOM 元素的
XHTML
转换问题 - 将
valign
包含在默认的AllowedAttributes
中 - 将已弃用的
valign
属性转换为基于标准的等效属性 - 修复了
GetInitialHtmlEditorOuterHTML
中缺失的textarea
结束标签
- 修复了 Internet Explorer 复制 DOM 元素的
- 2009 年 4 月 29 日
- 重构
Link
对话框如何读取href
属性 - 重构
Image
对话框如何读取src
属性 - 重构以支持类扩展
- 重构客户端
descriptor
ModalPopupExtender
问题的解决方法- 修复了内部计时器的客户端
Save
问题
- 重构
- 2009 年 4 月 9 日
DesignModeEmulateIE7
属性- Internet Explorer 8
designMode
滚动条问题的解决方法
- 2009 年 3 月 19 日
CLSCompliant(true)
程序集属性- 演示页面上的 FAQ 等链接
- 在
SetMode
时强制关闭打开的对话框 - Visual Studio 2008 设计器渲染的解决方法
- IFrame
onload
验证器问题的解决方法 - 修复了多个 W3C 验证器问题
- 修复了
ToLower
Culture 问题
- 2008 年 7 月 13 日
- 修复了 Firefox 3 删除/退格键问题
- 2008 年 6 月 29 日
- 修复了 Firefox 3 初始渲染布局问题
- 修复了 Opera 9.5 问题
- 2008 年 5 月 28 日
- Visual Studio 2008 (ASP.NET 3.5) 源代码
- Visual Studio 2005 (ASP.NET 2.0) 源代码
- 2008 年 5 月 25 日
- 修复了初始化问题
- 修复了 Color.htm 问题
- 修复了转换问题
- 修复了 Link.htm 问题
- 2008 年 2 月 3 日
- 修复了 Image.htm 问题
- 2008 年 1 月 2 日
- 客户端类的命名空间更改
- 客户端
GetVersion
方法 - 在默认允许的标签中添加了
sub
和sup
- 修复了
DesignModeCss
绝对路径问题 - 修复了设计器嵌入图像问题
- 2007 年 12 月 3 日
AutoSaveValidationGroups
属性- 修复了
ConvertParagraphs
水平线问题
- 2007 年 11 月 21 日
AutoSave
属性Modified
服务器端属性ValidationProperty
属性- 修复了客户端
GetText
方法 - 修复了资源标识符
- 强制执行像素高度和宽度
- 2007 年 10 月 30 日
SaveButtons
属性- 在“附加资源”部分添加了 Selection 和 Range 链接
- 2007 年 10 月 18 日
- 工具栏
- 分隔符工具栏元素
- 工具栏背景图像
- 配色方案:自定义、Visual Studio、默认
ColorScheme
属性CreateColorSchemeInfo
服务器端事件属性ToolstripBackgroundImage
属性ToolstripBackgroundImageCustomPath
属性NoToolstripBackgroundImage
属性EditorInnerBorderColor
属性SelectedTabTextColor
属性DialogSelectedTabTextColor
属性- 演示选项:切换模式、配色方案和工具栏
- 修复了
ScriptReference
问题 - 重构了 Color.htm
- 2007 年 10 月 9 日
- 对话框框架
- 文本颜色对话框
- 背景颜色对话框
- 超链接属性对话框
- 图像属性对话框
- 对话框颜色属性
DialogFloatingBehavior
属性CreateDialogInfo
事件属性- 将
alt
和title
包含在允许的属性中 - 修复了上下文问题
- 修复了 XHTML 字体转换问题
- 2007 年 9 月 24 日
Toolbars
属性- 移除了
ToolbarButtonsTop
属性 - 移除了
ToolbarButtonsBottom
属性 - 移除了
ToolbarSelectLists
属性 - 修复了空工具栏问题
- 2007 年 8 月 29 日
- 可选工具栏按钮:保存、新建、设计、HTML、视图
ToggleMode
属性ToolbarDocked
属性ToolbarClass
属性EditorBorderSize
属性EditorBorderColor
属性SelectedTabBackColor
属性ModifiedChanged
客户端事件处理程序属性ContextChanged
客户端事件处理程序属性Save
服务器端事件处理程序属性- 修复了
ConvertParagraphs
问题
- 2007 年 8 月 7 日
ToolbarButtonsTop
属性ToolbarButtonsBottom
属性ToolbarSelectLists
属性CreateToolbarInfo
事件属性TextDirection
属性- 在允许的标签中包含
dl
- 2007 年 8 月 5 日
ConvertParagraphs
属性ReplaceNoBreakSpace
属性- 修复了 Internet Explorer 水平线问题
- 修复了 Internet Explorer 有序列表和无序列表问题
- 修复了
$find
启动问题
- 2007 年 5 月 29 日
- Internet Explorer 6 安全上下文问题的额外修复
EditorBackColor
属性EditorForeColor
属性DesignModeCss
属性NoScriptAttributes
属性
- 2007 年 5 月 25 日:修复了 Internet Explorer 6 安全上下文问题
- 2007 年 5 月 15 日:支持 Opera 9+
- 2007 年 5 月 14 日:添加了属性
InitialMode
DesignModeEditable
HtmlModeEditable
- 2007 年 5 月 11 日:修复了 Firefox 隐藏/显示编辑器问题
- 2007 年 5 月 4 日
- 添加了 Internet Explorer 键盘按下处理程序
- 修复了回发设置初始
Text
问题
- 2007 年 5 月 2 日:修复了初始文本问题
- 2007 年 5 月 1 日
- 添加了 Modified 和 Save 概念
- 转换了
u
、blockquote
和align
的已弃用语法
- 2007 年 4 月 10 日:XHTML 输出
- 2007 年 4 月 2 日:修复了 Internet Explorer 焦点问题
- 2007 年 3 月 31 日:初始文章