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

ASP.NET HTML 编辑器控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (66投票s)

2012年10月17日

CPOL

13分钟阅读

viewsIcon

200740

downloadIcon

11282

创建一个有用的 ASP.NET HTML 编辑器自定义控件。

引言

如今,许多 Web 应用程序,如论坛和博客,都使用 HTML 编辑器作为用户发布的主要工具。基于 Web 的 HTML 编辑器是一种控件,允许在线用户创建和编辑他们的 HTML 文档,用户可以在其中编写文本并设置字体、颜色、大小和超链接,还可以插入图像、SWF 和文件。此外,用户可以查看其文档的 HTML 代码并进行编辑,并在设计模式下查看结果,反之亦然。

本文讨论如何创建具有某些安全考虑的 HTML 编辑器服务器控件。

背景 

四年前,我当时在一家对开发安全产品非常谨慎的公司工作,所以我们的技术经理要求我开发一个 HTML 文本编辑器作为我们 CMS 门户中的一个模块,供我们重要的客户使用,因为安全是他们的首要任务。因此,我开发了一个考虑了 XSS 等重要安全问题的 HTML 编辑器。通常,它不如当前新版本的文本编辑器(如 FCKEditor)安全,但对我来说,这是一个很好的机会,可以为我大脑中编程一些创意 Web 软件的必要性创造一个有用、用户友好且最重要的是独特的体验。我希望这篇文章也能帮助你。

系统要求

  • ASP.NET 2+

UI

组件外观

  • 顶部有三排工具栏
  • 用于添加表格、按钮等元素的对话框
  • 一个编辑器区域,以两种模式显示和/或编辑文档

Asp.net HTML Editor component

组件构成

此自定义控件分为两部分:服务器端和客户端。服务器端负责一些任务,例如使用 AntiXSS 处理安全问题、提供 HTML 编辑器的 HTML 源代码、设置安全级别、提供设置编辑器值和从编辑器获取安全值等方法、脚本注册、存储上传的文件、提供从 HTML 编辑器发送的解码数据以及为控件提供 WebResources。

客户端确保消除 HTML 编辑器源代码中一些危险内容(XSS 方面),这些内容将发送到服务器端并进行编码,将数据发送到服务器端等。

在客户端有六个 JavaScript 源文件

  • RichText.js 是主要的 javascript 源代码,所有与编辑器功能相关的事情都在这里完成。
  • Encoder.js 提供所有用于编码数据的方法
  • Loading.js 为编辑器提供可选的加载器。(其中一部分已被注释,如果你想要加载器,请取消注释它们)
  • Slider.js 为调整编辑器文本区域大小提供一个滑块
  • jscolor.js 是一个我只是使用的开源 javascript

在服务器端,包含五个类

  • HtmlSourceInitializer.cs:初始化编辑器 HTML
  • Registrar.cs:将脚本列表注册到我们的控件中
  • Security.cs:负责使用 AntiXSS 和 HtmlSanitizationLibrary 进行安全防护
  • SourceActions.cs:获取从客户端发送的数据
  • RichTextBox.cs:建立 HtmlEditor 控件的可访问方法和属性

EditorStyles、RichTextBoxIcons、Colorpicker 是其他部分。

HtmlSourceInitializer.cs

该组件有一些自己的 HTML 源代码,用于创建 HTML 编辑器视图。此类别使用 StringBuilder 将已添加到 _HtmlSource 中的 HTML 编辑器视图部分与 InitializeHtmlSource 方法一起追加,然后将 _HtmlSource 放入静态 RichTextHtmlSource 属性中。

internal static class HtmlSourceInitializer
{
#region fields
private static StringBuilder _HtmlSource = null;
#endregion

#region getHtmlSource
public static StringBuilder RichTextHtmlSource
{
    get
    {
        if (_HtmlSource != null)
        {
            return _HtmlSource;
        }
    return null;
    }
}
#endregion

#region Initialize html source
/// <summary>
/// Initializes the Html source of editor
/// </summary>
/// <param name="CurrentPage"></param>
public static void InitializeHtmlSource(Page CurrentPage)
{
    StringBuilder HtmlSource = new StringBuilder();
        HtmlSource.Append("<center id=\"centerElement\" style=\"display:none\">");
...............

Security.cs

为了防止跨站脚本攻击,我决定使用 Microsoft AntiXSS 库。正如您可以在其概述中读到的那样

"Microsoft 反跨站脚本库 V4.2 (AntiXSS V4.2) 是一个编码库,旨在帮助开发人员保护其基于 ASP.NET 的 Web 应用程序免受 XSS 攻击。它与大多数编码库不同,因为它使用白名单技术——有时也称为包含原则——来提供针对 XSS 攻击的保护。

这种方法的工作原理是首先定义一个有效或允许的字符集,并对该集之外的任何内容(无效字符或潜在攻击)进行编码。白名单方法比其他编码方案具有多个优势。"

_SetHighLevelSecurityForHtmlTags 字段已设置为 True,这意味着从客户端发送的 HTML 文档将分两级解码,并仅更改为文本代码。例如,"<b>hello</b>" 将变为类似 ";&lt;b&gt;hello&lt;/b&gt;" 的内容。这在某些情况下很有用,您希望获得更安全的 HTML 代码,检查后可以将其解码以再次获得纯 HTML 源代码。

EncodeHtml 方法使用 AntiXSS HtmlEncode 方法对创建文档的字符串 HTML 源进行编码。

方法。此外,GetSafeHtmlFragment 方法返回标签保持不变的 HTML 片段。

public static class Security
{
    /// <summary>
    /// if this properties is true, in this case will set two steps security over exchanged data
    /// The default value of this property is true
    /// </summary>
    internal static bool _SetHighLevelSecurityForHtmlTags = true;
    internal static string EncodeHtml(string html)
    {
        return AntiXss.HtmlEncode(Sanitizer.GetSafeHtmlFragment(html));
    }
}

SourceActions.cs

SourceActions 类通过 SourceProvider 提供从客户端发送的 HTML 源代码。

public class SourceActions : System.Web.UI.Page
{
#region fields
  internal string _SourceCode = string.Empty;
  #endregion

#region Cunstructor
public SourceActions()
{
//
// TODO: Add constructor if it is requiered
//
}
#endregion Cunstructor
#region Source provider
/// <summary>
/// provide editor html source code
/// </summary>
/// <summary>
/// Sets the Current page.
/// </summary>
/// <param name="CurrentPage">The current page.</param>
/// <returns>void</returns>
internal void SourceProvider(Page CurrentPage)
{
    CurrentPage.ClientScript.GetPostBackEventReference(CurrentPage, string.Empty);
    if (CurrentPage.IsPostBack)
    {
        string eventTarget = (CurrentPage.Request["__EVENTTARGET"] == null ? 
               string.Empty : CurrentPage.Request["__EVENTTARGET"]);
        string eventArgument = (CurrentPage.Request["__EVENTARGUMENT"] == null ? 
               string.Empty : CurrentPage.Request["__EVENTARGUMENT"]);
        if (eventTarget == "getHtmlData")
        {
            if (SigmaToolBox.TextEditor.Security._SetHighLevelSecurityForHtmlTags)
            {
                _SourceCode = SigmaToolBox.TextEditor.Security.EncodeHtml(eventArgument);
            }
        else
        {
            _SourceCode = eventArgument;
             }
    }
}
}
#endregion
}

RichTextBox.cs

SetValue 方法

此方法将为编辑器设置一些值,例如 HTML 文档。例如,如果您想使用已存储在数据库或其他资源中的某些文档,您可以使用此方法将您的文档插入编辑器进行编辑。

/// <summary>
/// This function sets a value to the text editor like some text
/// or html data from database or other resources for edition activities
/// </summary>
/// <param name="Value"></param>
[Bindable(true)]
[Category("Appearance")]
[DefaultValue("")]
[Localizable(true)]
public void SetValue(string Value)
{
    string script = "disableElement(document.getElementById('textToolsContainer'));" + 
       "document.getElementById('textEditor').style.display='none';isOnSourceMode" + 
       "=true;isOndesignMode=false;document.getElementById('sourceTxt')." + 
       "style.display='block';document.getElementById('sourceTxt').value='" + Value + "'";
    Page.ClientScript.RegisterStartupScript(Page.GetType(), 
      "valueSetterScript", script, true);
} 

GetValue 方法

此函数获取在文本编辑器中提供的数据,用于某些目的,例如:在指定页面中演示、存储在数据库中等。

public string GetValue()
{
    return this.Page.Server.HtmlDecode(AntiXss.HtmlAttributeEncode(GetDecodedValue()));
}

OnPreRender 必须修改为调用 RegisterStartupScript,如下所示

protected override void OnPreRender(EventArgs e)
{

    base.OnInit(e);
    Registrar.RegisterScripts(new List<string> { "SigmaToolBox.js.loading.js", 
      "SigmaToolBox.js.testJS.js", "SigmaToolBox.js.Encoder.js", 
      "SigmaToolBox.js.slider.js" }, this.Page, this.GetType());
    string InitializerJS = Page.ClientScript.GetWebResourceUrl(this.GetType(), 
              "SigmaToolBox.js.Initializer.js");
          this.Page.ClientScript.RegisterStartupScript(this.GetType(), 
          "RichText", "<script language="\""javascript\" src='" + 
          InitializerJS + "'></script>");
}

RichText.js

由于 HTML 编辑器的客户端行为相当广泛,因此客户端控件中需要相当多的 JavaScript。这大部分是典型的 designMode 客户端编程。

这包括一些重要的函数,它们承担一些任务,例如

  • 在编辑器上执行命令
  • 元素创建
  • 所有其他设计模式活动
  • 创建事件处理程序

文档是使用这些 javascript 函数创建和设计的,然后如果有人想在服务器端使用它(例如:存储/解码),则应将其发送到服务器。此操作通过 sendValue() 发生。

sendValue() 函数将创建的文档发送到服务器,同时它还清除了某些存在 XSS 攻击风险的标签,如 scriptiframejavascript,并在发送到服务器之前使用 Encoder.js(通过 htmlEncode() 函数)对文档进行编码。实际上,编码过程将分两步进行,第一步在客户端,第二步在服务器端。

//Sends all elements of textEditor to the server

function sendValue(){

    var innerHtmlData= usedFrame.innerHTML;

    var clearData=innerHtmlData.toLowerCase().replace(/<script[^>]*?>/g,"");

    var clearData = clearData.replace(/<\/script>/g, "");

    clearData = clearData.replace(/javascript/g, "");

    clearData = clearData.replace(/script/g, "");

    clearData = clearData.replace(/<iframe[^>]*?>/g, "");

    clearData = clearData.replace(/<\/iframe>/g, "");

    clearData = Encoder.htmlEncode(clearData);

    __doPostBack('getHtmlData', clearData);

}

这个编辑器最关键的函数,换句话说,是它的核心,是 textEdit(x,y) 函数,它负责所有关于文本编辑的问题。其背后的基本思想是使用浏览器的一个基本函数,它允许您在当前文档、当前选择或给定范围内执行命令,例如加粗、斜体、下划线、复制、删除、重新格式化等,名为 execCommand

execCommand 方法生成的代码在不同浏览器中是不同的。Internet Explorer 使用 HTML 标签,Firefox、Google Chrome 和 Safari 生成内联样式,Opera 有时使用 HTML 标签,有时使用样式。

例如,如果在非粗体文本上执行“粗体”命令,

•Internet Explorer 和 Opera 会在其周围生成一个 strong 元素,

•Firefox、Google Chrome 和 Safari 会在其周围生成一个 span 元素,并将该 span 元素的 fontWeight 样式属性设置为“bold”。如果非粗体文本周围存在一个元素,则 Firefox、Google Chrome 和 Safari 会将该元素的 fontWeight 样式属性设置为“bold”。

如果在粗体文本上执行“粗体”命令,浏览器会删除指定的样式属性和/或包含文本的元素。

这导致我在识别粗体、下划线和斜体文本以粗体、斜体和下划线按钮的形式显示时遇到了问题。无论如何,我为 Firefox 和 IE 解决了这个问题,但它在其他浏览器如 Chrome、Opera 和 Firefox 中仍然存在一些问题。getActiveButtons() 方法如下所示:

function getActiveButtons(){
    isColorPickerInAction=isAdvanceColorPickerInAction=false;
    var isBold=false;var isItalic=false;var isUnderline=false;
    try{
       if(document.all){
           var xmlDocument=StringtoXML(getRangeNode(window.frames["textEditor"]));
           var richTextElementsnodes=xmlDocument.documentElement.getElementsByTagName("nodeName");
           var NodesCount=richTextElementsnodes.length;
           for(var i=0;i<NodesCount;i++){
                var innerNodeValue=richTextElementsnodes[i].firstChild.nodeValue;
                //alert(innerNodeValue)
                switch(innerNodeValue){
                    case "STRONG":
                        isBold=true;
                        break;
                    case "EM":
                        isItalic=true;
                        break;
                    case "U":
                        isUnderline=true;
                        break;
                }
           }
       }
       else{
            var retrivedData=getRangeNode(textEditorElement.contentWindow);
            for(var i=0;i<retrivedData.length;i++){
                switch(removeSpaces(retrivedData[i])){
                    case "font-style:italic":
                        isItalic=true;
                        break;
                    
                    case "text-decoration:underline":
                        isUnderline=true;
                        break;
                    
                    case "font-weight:bold":
                        isBold=true;
                        break;
                }
            }
       }
   }
   catch(e){
        
   }
   setActivationStatus(isBold,isItalic,isUnderline);
} 

StringtoXML() 方法将输入字符串(由 getRangeNode() 方法生成)转换为 XML:

function StringtoXML(text){
    var doc;
    if (window.ActiveXObject){
      doc=new ActiveXObject('Microsoft.XMLDOM');
      doc.async='false';
      doc.loadXML(text);
    } 
    else{
      parser=new DOMParser();
      doc=parser.parseFromString(text,"text/xml");
    }
    return doc;
} </span> 

getRangeNode() 方法获取选中文本的父节点及其样式,以识别选中文本的样式:

 function getRangeNode(win){
      var retrivedString="";
      checkCursor(usedFrame);
      if (window.getSelection){
        node = win.getSelection().anchorNode;
        var nodeStyleAttribute=node.parentNode.getAttributeNode("style").nodeValue.toString();
        var nodeStyleAttributeChildes=nodeStyleAttribute.split(";");
        retrivedString=nodeStyleAttributeChildes;
      }
      else if (win.document.selection){
        var range = win.document.selection.createRange();
        if (range){
            node = range.parentElement();
            nodesList="";
            retrivedString= "<nodesList>"+getParentNodesList(node)+
              "<nodeName>"+node.nodeName+
              "</nodeName>"+"</nodesList>";
        }
      }
  return retrivedString;
} 

语法是:object.execCommand(cmdID, showUI, value)execCommand 函数的功能和兼容性在不同浏览器中有所不同,因此在此组件中,有些功能(例如“文档保存”或“背景颜色”)在除 IE 之外的其他浏览器中无法执行。正如您在下面看到的,浏览器之间存在许多不兼容性,这使我们的工作更加困难,而且似乎它将比以前得到更多改进。

方法或属性 IE 6/7 Firefox 2 Safari 3 Opera 9
背景色

Moz/Saf 要求 #。Moz/Op 将背景颜色赋予选择所属的块级元素,IE/Saf 赋予选择本身。
要在 Moz 中获得 IE/Saf 的效果,请使用 hilitecolor

粗体
注释
内容只读 ?
IE 报错
复制 受保护的 受保护的
Ctrl+C 始终有效
创建书签 ? ? ? ?
注释
创建链接
注释
剪切 受保护的 受保护的
Ctrl+X 始终有效
方法或属性 IE 6/7 Firefox 2 Safari 3 Opera 9
减小字号 ? 不正确
Op 只允许一次减小;第二次会将文本恢复到原始字号。
删除
注释
字体名称
注释
字号 糟糕 糟糕

Moz/Op 生成一个(天哪!)<font> 标签,其 size 等于 parseInt(value)。Saf 创建一个普通的 font-size CSS 声明。

前景色
Moz/Saf 要求 #
格式块

(将选区放入标题或段落中)

有 bug 不完整
Opera 仅将选区中的第一个块级元素更改为所需的块。
方法或属性 IE 6/7 Firefox 2 Safari 3 Opera 9
heading
注释
高亮色

与 IE/Saf 中的 bgcolor 作用相同:它只给选区(而不是包含块)定义好的背景颜色。

增大字号 ? 不正确
Op 只允许一次增大;第二次会将文本恢复到原始字号。
缩进 不正确 有 bug 更不正确

Moz 每次缩进增加 40px 的左外边距。IE/Op 每次缩进增加一个(天哪!)blockquote

当应用于 <li> 时,Moz/IE 生成嵌套的 <ol/ul>,但 Op 再次插入 <blockquote>

插入水平线
注释
插入HTML ?
注释
方法或属性 IE 6/7 Firefox 2 Safari 3 Opera 9
插入图片
IE 允许调整图像大小
插入有序列表 差不多 差不多
如果新创建的有序列表与现有列表相邻,IE 和 Safari 会将两者合并。
插入无序列表 差不多
如果新创建的无序列表与现有列表相邻,IE 会将两者合并。
插入段落 替代方案
Mozilla 在选定的块周围添加一个段落。其他浏览器删除选定的块并插入一个空段落,用户可以填充。
斜体
注释
居中对齐
注释
方法或属性 IE 6/7 Firefox 2 Safari 3 Opera 9
两端对齐
注释
左对齐
注释
右对齐
注释
多选 ? ? ? ?
注释
取消缩进 有 bug

当应用于作为单个 <ol/ul> 子元素的 <li> 时,Moz/IE 将 <li> 移到 <ol/ul> 之外,而 Op 没有反应。

不幸的是,IE 在我的测试页面中有一个额外的错误:它将 <li> 完全移到我的测试元素之外。

覆盖 ? ? ? ?
注释
粘贴 受保护的 受保护的
Ctrl+V 始终有效
print ? ? ? ?
注释
重做
重做在 Safari 中有效,但如果撤销/重做次数过多,它会崩溃。在 3 中已解决。
如果您在可编辑区域中进行了自己的更改,Mozilla 和 Safari 中的撤消/重做将继续工作(尽管它会忽略您的自定义更改),但在 IE 和 Opera 中它会停止工作。
方法或属性 IE 6/7 Firefox 2 Safari 3 Opera 9
刷新 ? ? ? ?
注释
移除格式 ? ? ? ?
注释
另存为 ? ? ? ?
注释
全选 ? ? ? ?
注释
删除线
注释
使用 CSS 样式 ?
提供一个通用命令,指示样式应该使用 CSS(true;默认)还是标签(false)应用。当执行 execCommand("bold") 时,前者会生成一个 <span style="font-weight: bold";>,后者会生成一个 <b> 标签。
下标
IE/Moz/Op:再次使用相同的命令会删除下标。同时使用下标和上标会产生奇怪的效果。
上标
IE/Moz/Op:再次使用相同的命令会删除上标。同时使用下标和上标会产生奇怪的效果。
方法或属性 IE 6/7 Firefox 2 Safari 3 Opera 9
取消书签 ? ? ? ?
注释
下划线
注释
撤消
撤销在 Safari 中有效,但如果撤销/重做次数过多,它会崩溃。在 3 中已解决。
如果您在可编辑区域中进行了自己的更改,Mozilla 和 Safari 中的撤消/重做将继续工作(尽管它会忽略您的自定义更改),但在 IE 和 Opera 中它会停止工作。
取消链接
注释
方法或属性 IE 6/7 Firefox 2 Safari 3 Opera 9

另一个关键问题是找到光标上一次停留的位置。这将使编辑器知道当前 execCommand 应该影响哪个文本元素。此功能主要在 IE 中使用,因为 Firefox 和其他浏览器默认具有此功能。此任务由 checkCursor() 方法负责,正如您所看到的:

//checks the cursor and return the carret Position
function checkCursor(where){
    try{
        Current=where;
        if (!isToolBoxContainerDivDisabled)
        {
            where.focus();
            if(document.all){
                CarretPosition=document.selection.createRange();
                if(CarretPosition.text==""){
                    where.focus();
                }
            }
        }
    }
    catch(error){
        alert(error.name + ": " + error.message);
    }
}  

CreateEventForGeneratedButton() 是另一个重要的函数,它将指定的事件处理程序分配给动态创建的按钮,例如表格创建器、链接添加器等对话框中的所有按钮。

function CreateEventForGeneratedButton(ActionType){
    try{
        switch(ActionType){
            case "Link":
               (document.all)?SetCommandButton.attachEvent ("onclick",
                 SetLinkCommandEventHandler):SetCommandButton.addEventListener (
                 "click",SetLinkCommandEventHandler,false);
               break;
            case "Image":
               (document.all)?SetCommandImageButton.attachEvent ("onclick",
                  SetImageCommandEventHandler):SetCommandImageButton.addEventListener (
                  "click",SetImageCommandEventHandler,false);
                break;
            case "insertTable":
               (document.all)?SetCommandInsertTableButton.attachEvent ("onclick",
                   SetInsertTableCommandEventHandler):SetCommandInsertTableButton.addEventListener (
                   "click",SetInsertTableCommandEventHandler,false);
                break;
            case "insertButton":
               (document.all)?SetCommandInsertButton_Button.attachEvent ("onclick",
                  SetInsertButtonCommandEventHandler): SetCommandInsertButton_Button.addEventListener (
                  "click",SetInsertButtonCommandEventHandler,false);
                break;
            case "insertSWF":
                isToolBoxContainerDivDisabled=false;
               (document.all)?SetSWFCommandButton.attachEvent ("onclick",
                 SetInsertSWFButtonCommandEventHandler): SetSWFCommandButton.addEventListener (
                 "click",SetInsertSWFButtonCommandEventHandler,false);
                break;
            case "uploadSWF":
               (document.all)?SetSWFUploaderCommandButton.attachEvent ("onclick",
                  SetSWFUploaderButtonCommandEventHandler): 
                  SetSWFUploaderCommandButton.addEventListener (
                  "click",SetSWFUploaderButtonCommandEventHandler,false);
                break;
            case "CancelingInsertButtton":
               (document.all)?SetCommandCancelInsertingButton_Button.attachEvent (
                  "onclick",SetCancelingInsertButtonCommandEventHandler):
                  SetCommandCancelInsertingButton_Button.addEventListener (
                  "click",SetCancelingInsertButtonCommandEventHandler,false);
                break;
        }
    }
    catch(error){
        alert(error.name + ": " + error.message);
    }
}

Setposition() 方法设置创建对象的位置。当一个对象被创建时,它需要被定位在页面上。此方法根据鼠标和页面设置对象的位置。例如,当您拖动对话框栏时,它使用此方法并将 related 参数设置为“mouse”,当创建对象生成器时,它使用此函数并将“page”作为 related 参数。

//Sets the position of object maker
function SetPosition(e,elementName,Related){
    try{
        var ContainerElement=document.getElementById(elementName);
        switch(Related){
            case "Page":
                if(ContainerElement!=null){
                    ContainerElement.style.left=(document.body.clientWidth)/2-100 +"px";
                    ContainerElement.style.top=(document.body.clientHeight)/2-100 +"px";
                }
            break;
            case "Mouse":
                    e=e||window.event;
                    document.getElementById(elementName).style.display="block";
                    document.getElementById(elementName).style.left=e.clientX+10+"px";
                    document.getElementById(elementName).style.top=e.clientY +"px";
            break;
        }
    }
    catch(error){
        alert(error.name + ": " + error.message);
    } } 

RichText.js 不是基于面向对象编程的,但我试图使其清晰易用。

使用此代码

要使用此控件,您应该将控件的 DLL 从 RichTextBox\Control_dll 文件夹添加到您的 Visual Studio 工具箱中,然后将其拖到您的 ASPX 页面上。您将看到类似以下内容:

<%@ Page Language="C#" AutoEventWireup="true" 
   CodeBehind="Default.aspx.cs" Inherits="testCustom._Default" %>
<%@ Register Assembly="SigmaToolBox" 
   Namespace="SigmaToolBox.TextEditor" TagPrefix="sigma" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Untitled Page</title>
</head>
<body id="body1">
<%=htmlCode %>
<form id="form1" runat="server">
<div>
<sigma:RichTextBox ID="RichTextBox1" runat="server" />
</div>
<div id="div1">
<asp:Button Style="border-style: groove" 
ID="Button1" runat="server" OnClientClick="sendValue()"
Text="Get Data" OnClick="Button1_Click" />
</div>
</form>
</body>
</html>

如果您想获取创建的文档,首先您应该通过 OnClientClick="sendValue()" 将其发送到服务器,然后通过在您的事件触发器控件(如按钮)上设置 OnClick 事件来获取安全数据,并编写您的代码,如下所示:

public partial class _Default : System.Web.UI.Page
{
    public string htmlCode = string.Empty;
    protected void Page_Load(object sender, EventArgs e)
    {
        //To DO
    }
    protected void Button1_Click(object sender, EventArgs e)
    {
        RichTextBox1.SetHighLevelSecurityForHtmlTags = false;
        htmlCode =RichTextBox1.GetValue();
    }
} 

浏览器测试

本文讨论的控件已在 Firefox 5+、IE 8、Opera、Safari 和 Chrome 上进行测试。

IE 9:IE 9 中没有错误,但外观存在一些不足

  • 两个多余的滚动条
  • 对话框栏在表格或文本后面消失

如何用 Visual Studio 2010 或更低版本打开此项目

上次我用 Visual Studio 2012 打开此项目检查其可靠性以来,您可能在用低于 VS2012 的版本打开此项目时遇到问题。要解决此问题,请按以下步骤操作

  • 用记事本之类的文本编辑器打开项目 SLN 文件。在第二行,您会看到这行:Microsoft Visual Studio Solution File, Format Version 12.00。将版本更改为 11.00 或更低
  • 保存文本并用您的 VS 打开项目。享受吧!

结论 

我努力让这个控件对用户友好,总而言之,我相信使用它非常简单。它并非完全没有错误,但在我开发它的所有 6 个月里,对我来说这是一次很好的经历,我希望这个源代码能让您学到一些关于创建 ASP.NET 自定义控件和基于 Web 的 HTML 编辑器的宝贵知识。

你将学到很多小知识点,比如:从 CSS 文件中将图像地址化到自定义控件项目,以及 Assemblyinfo 和 JavaScript 源代码中的许多其他内容。希望有所帮助。

历史

2012 年 11 月 5 日

  • Firefox 上关于颜色选择器操作的 onclick JavaScript 错误已修复
  • Chrome 和 Opera 上创建的表格外观已改进

2012 年 10 月 26 日

  • 修复 Safari 浏览器问题

2009 年 11 月 23 日

  • GetValue 和 SetValue 方法已添加

2009 年 4 月 10 日

新增功能

  • 支持 SWF
  • 支持特殊字符
  • 支持预览
© . All rights reserved.