How to Create an HTML Editor for ASP.NET AJAX
Introduction
Most blog, forum and Wiki applications use an HTML editor as the
primary authoring tool for site content. With this type of control, an online user
can create and edit an HTML document. The user is able to modify the text, including
its format, fonts and colors, as well as add links and images. Often, the user
may also view and/or edit the HTML source.
Microsoft AJAX (ASP.NET AJAX) introduces
a new implementation model for server controls with related script. This article
discusses how to create an HTML editor server control specifically for the Microsoft
AJAX environment. The reader can also download the source
code, including a sample web page, and view an online demo.
Background
DesignMode
Most modern browsers now support the "editing" of displayed HTML content by the
user. When the document designMode property is set to true,
the rendered HTML on the page can be edited by the user. While in designMode,
the document's execCommand method supports additional commands that
enable "programmatic" modification of document contents. For example, passing the
command string bold as the first parameter to execCommand
causes the selected text to appear bold by adding appropriate HTML tags and/or attributes.
The resources listed at the end of this article discuss
designMode and execCommand in more detail and describe
how to implement a basic HTML editor. Also, the source code can be examined for implementation examples.
Microsoft AJAX Model for Server Controls with Related Script
As part of ASP.NET AJAX, Microsoft introduced a new "model" for extending the capabilities
of a server control with client-side script. That model is described in an ASP.NET
AJAX tutorial
that should be read along with this article. In general, we create two related controls:
- A server control that implements the
IScriptControl interface
- A related client control derived from
Sys.UI.Control (part of the client-side AJAX library)
In order for ScriptManager to know how to create and initialize the
related client control, we implement the new IScriptControl interface,
adding two callback methods. Then we add a few lines of code to OnPreRender
and Render to trigger those callbacks at appropriate times in the page
life cycle. The specifics are described in the tutorial and again in the discussion
of HtmlEditor.cs below.
We also encapsulate our client-side behavior in a JavaScript class, implemented
as a Microsoft AJAX client control. This permits the client control object to be
created and initialized in a standard way by the client-side AJAX code. The specifics
are described in the tutorial and again in the discussion of HtmlEditor.js
below.
Source Code
Click here to download the source code, including a sample web page.
System Requirements
- ASP.NET 4.0 or higher
- ASP.NET AJAX
Component Parts
- HtmlEditor.cs (C# file for our server control)
- HtmlEditor.js (JavaScript file for our client control)
- Images/*.gif (image files for our toolbar images)
UI Elements
Component appearance:
- 2 toolbar rows across the top
- 2 tabs along the bottom
- An editor area that displays and/or edits the document in either mode
Click here
for an online demo.
Overview
HTML Schema
Div container for the control
Div container for each Toolbar
Select elements for dropdown lists
Img elements for buttons
Div container for the Design editor
IFrame element for the Design mode document
Div container for the HTML editor
IFrame element for the HTML mode document
Textarea for HTML mode editing
Div container for the Tabbar
Div element for each Tab
Img element for the Tab icon
Span element for the Tab text
Server Control
- Creates child controls for each HTML element required
- Provides public property methods for configuration properties (colors, etc.)
- Implements property methods for properties passed to the client control on initialization
- Implements default property values
- Implements
IScriptControl methods
Client Control
- Provides
get and set property methods for properties passed
from the server control on initialization
- Dynamically creates the
IFrame documents
- Sets
designMode to true for the Design mode document
- Provides appropriate handlers for
Toolbar and Tabbar mouse
events
- Converts to and from XHTML when appropriate
- Converts deprecated syntax inserted by the
designMode browser to a
standards-based equivalent
- Filters tags and attributes to those allowed
- Removes/Restores extraneous default tags inserted by
designMode browsers
HtmlEditor.cs
The component itself contains many different HTML elements, so the server control
class is derived from CompositeControl. In addition, the class must
implement the IScriptControl methods:
public class HtmlEditor : CompositeControl, IScriptControl
In CompositeControl, child controls are added in the CreateChildControls
method:
protected override void CreateChildControls()
{
...
CreateToolbars(...);
this.Controls.Add(CreateHtmlArea());
this.Controls.Add(CreateDesignArea());
this.Controls.Add(CreateTabbar());
this.Controls.Add(CreateUpdateArea());
base.CreateChildControls();
}
To implement the IScriptControl interface, two callback methods are
required. The first, GetScriptReferences, tells ScriptManager
what related script file(s) to load and from where. For this server control, we
have chosen to embed the HtmlEditor.js file in our assembly resources.
We tell ScriptManager the full resource path and assembly name so that
it can load it from there, simplifying deployment:
protected virtual IEnumerable<ScriptReference>
GetScriptReferences()
{
ScriptReference htmlEditorReference =
new ScriptReference(
"Winthusiasm.HtmlEditor.Scripts.HtmlEditor.js",
"Winthusiasm.HtmlEditor");
...
return new ScriptReference[] { htmlEditorReference, ... };
}
The second callback method, GetScriptDescriptors, "maps" properties
in the client control(s) to properties in the server control. ScriptManager
uses this information to set the appropriate values in the client control as part
of its client-side creation:
protected virtual IEnumerable<ScriptDescriptor>
GetScriptDescriptors()
{
ScriptControlDescriptor descriptor =
new ScriptControlDescriptor("Winthusiasm.HtmlEditor",
this.ClientID);
descriptor.AddProperty("htmlencodedTextID",
this.HtmlEncodedTextID);
...
return new ScriptDescriptor[] { descriptor };
}
Although we have now implemented the IScriptControl methods, there
are two remaining modifications to make so that the IScriptControl
callbacks get called. First, OnPreRender must be modified to call
RegisterScriptControl, as follows:
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);
}
Then Render must be modified to call RegisterScriptDescriptors:
protected override void Render(HtmlTextWriter writer)
{
if (!this.DesignMode)
sm.RegisterScriptDescriptors(this);
base.Render(writer);
}
HtmlEditor.js
Because the client-side behaviors for an HTML editor are fairly extensive, there
is a fairly extensive amount of JavaScript required in the client control. Most
of this is typical designMode client-side programming. More to the
point of this article, the entire JavaScript code structure has been formatted to
follow the client-side coding model recommended by Microsoft for all client controls
built upon the AJAX client-side libraries. This includes:
- Namespace registration
- Constructor
- Prototype block with all methods comma-separated
- Prototype
get and set methods for each property passed
from the server control
- Prototype
initialize method
- Prototype
dispose method
Descriptor method
- Class registration
Namespace registration:
Type.registerNamespace("Winthusiasm");
Constructor:
Winthusiasm.HtmlEditor = function(element)
{
Winthusiasm.HtmlEditor.initializeBase(this, [element]);
this._htmlencodedTextID = "";
...
}
Prototype block with methods comma-separated:
Winthusiasm.HtmlEditor.prototype =
{
method1: function()
{
...
},
method2: function()
{
...
},
...
}
Prototype get and set methods for each property passed
from the server control:
get_htmlencodedTextID: function()
{
return this._htmlencodedTextID;
},
set_htmlencodedTextID: function(value)
{
this._htmlencodedTextID = value;
},
...
Prototype initialize method:
initialize: function()
{
Winthusiasm.HtmlEditor.callBaseMethod(this, 'initialize');
...
},
Prototype dispose method:
dispose: function()
{
...
Winthusiasm.HtmlEditor.callBaseMethod(this, 'dispose');
},
Descriptor method:
Winthusiasm.HtmlEditor.descriptor =
{
properties: [ {name: 'htmlencodedTextID', type: String },
... ]
}
Class registration:
Winthusiasm.HtmlEditor.registerClass("Winthusiasm.HtmlEditor",
Sys.UI.Control);
Using the Code
Download the appropriate zip file and unzip it to a new directory. It includes:
- The
Winthusiasm.HtmlEditor control project
- A sample website that uses the
HtmlEditor control
- A solution that contains both
Double-click the solution file to start Visual Studio and then select Build/Rebuild
Solution from the menu. This will build the project and copy the project DLL to
the Bin folder of the sample website. Set the sample website as the Start Project, Demo.aspx as the Start Page, and press F5.
Use Model
- Create the website as an "ASP.NET AJAX-enabled Web Site"
- Copy the assembly Winthusiasm.HtmlEditor.dll to the Bin folder
- Add a
Register statement to the page
- Add a custom control Tag(s) to the page
- Use the Text property to set the editor HTML
- Save the HTML when appropriate
- Use the Text property to get the "saved" HTML
Example Register statement:
<%@ Register TagPrefix="cc"
Namespace="Winthusiasm.HtmlEditor"
Assembly="Winthusiasm.HtmlEditor" %>
Example custom control Tag:
<cc:HtmlEditor ID="Editor"
runat="server"
Height="400px"
Width="600px" />
Example set Text:
Editor.Text = initialText;
Use Model Details: Saving the HTML when Appropriate
The editor's "client-side" Save method instructs the editor to store
the current HTML (converting to XHTML if appropriate) and clears the modified
flag. When the editor property AutoSave is set to true
(the default), the client-side Save method is called automatically
as part of the client-side ASP.NET validation process before the form is submitted.
All controls with a CausesValidation property set to true
(the default) trigger the behavior.
If the AutoSave implementation is not appropriate or sufficient, the
client script to trigger the client-side Save can be attached through
the optional SaveButtons property or manually.
Retrieving the Saved Text
In the server-side event handler, the Text property is used to retrieve
the "saved" text:
DataStore.StoreHtml(Editor.Text);
Points of Interest
Embedded Resources
To simplify deployment, the HtmlEditor.js file and the image files for
the Toolbar buttons are embedded as resources
within the HtmlEditor.dll assembly.
HtmlEncode and HtmlDecode
Storing unencoded HTML in a form control, such as a text area or a hidden input
element, is problematic when submit behavior is triggered on the client:
This implementation uses HiddenField to store the edited HTML and therefore
always stores the text in an HtmlEncoded state.
Setting the Text Property on PostBack
The HiddenField used to store the HTML text is created within UpdatePanel.
If the Text property is set during an "asynchronous" PostBack, the
server control responds by calling Update on UpdatePanel
and registers DataItem with ScriptManager. Because the
client control uses an
endRequest handler to monitor all
PageRequestManager updates, it detects that DataItem has
been registered and automatically updates the HTML in the editor.
XHTML Conversion
Both Internet Explorer and Firefox output "HTML" when in designMode.
To convert to "XHTML," this implementation reads the "client-side" DOM tree and
outputs the elements and attributes in XHTML format. This "client-side" conversion
of HTML to its XHTML equivalent effectively "hides" the underlying HTML implementation.
When the user switches from Design to HTML mode, the output displayed is XHTML.
In addition, the server-side Text property retrieves XHTML.
Note that the editor configuration property OutputXHTML is defaulted
to true. If set to false, no XHTML conversion takes place
and the output is browser-generated HTML.
Converting Deprecated Syntax
Internet Explorer and Firefox both output and "expect to modify" deprecated syntax
while operating in designMode. Consequently, the implementation of
this editor converts the deprecated syntax into a standards-based equivalent when
converting to XHTML, and "restores" the deprecated syntax when converting back to
designMode HTML.
Note that the editor configuration property ConvertDeprecatedSyntax
is defaulted to true. If set to false, no conversion takes
place and the output includes the deprecated syntax.
Converting Paragraphs in Internet Explorer
Internet Explorer versions less than 9 output and "expect to modify" paragraph elements while operating
in designMode. If the editor configuration property ConvertParagraphs
is set to true, the implementation of this editor "modifies" the displayed
style of paragraph elements while in designMode, converts the paragraph
elements into appropriate break and/or div elements when
converting to XHTML, and "restores" the paragraph elements when converting back
to designMode HTML.
Note that this configuration property applies ONLY to Internet Explorer versions less than 9 and is defaulted
to false. Unless set to true, no conversion takes place
and the output includes the paragraph elements.
Caveats
Script Injection
A web page containing an HTML editor most likely stores the HTML created by the
user. Later, it probably displays that HTML in another web page, perhaps for a different
user. This presents a classic exposure to Script Injection and perhaps SQL Injection,
as well.
The client of the HTML editor should take appropriate
steps to control these exposures.
Browser Testing
The control discussed in this article was tested on Firefox 2+, IE 6+ and Opera
9+.
Conclusion
Using the tutorial from Microsoft, as well as the designMode and
execCommand resources listed below, the HTML
editor server control has been implemented to work in a Microsoft AJAX environment. Possible enhancements include:
- Support for Safari
- Support for additional HTML constructs
- Additional configuration properties
designMode and execCommand
Other Tutorials and Articles
Online Documentation
History
- June 15, 2012
- Workaround for Internet Explorer 9 selection and range issues
- Constrain FormatHtmlMode to Internet Explorer versions not in standards mode
- July 1, 2009
- Reduce
ViewState size by 72%
- June 19, 2009
- Fix for client-side ASP.NET
Validator support
- June 17, 2009
- Full support for client-side ASP.NET
Validators
- June 8, 2009
- Fix
pre tag conversion issues
- June 7, 2009
- Fix
XHTML conversion issue with Internet Explorer duplicate DOM elements
- Include
valign in the default AllowedAttributes
- Convert deprecated
valign attribute to standards-based equivalent
- Fix
GetInitialHtmlEditorOuterHTML missing textarea end tag
- April 29, 2009
- Refactor how the
Link dialog reads the href attribute
- Refactor how the
Image dialog reads the src attribute
- Refactor to support class extension
- Refactor client-side
descriptor
- Workaround for
ModalPopupExtender issue
- Fix client-side
Save issue with an internal timer
- April 9, 2009
DesignModeEmulateIE7 property
- Workaround for IE 8 designMode scrollbars issue
- March 19, 2009
CLSCompliant(true) assembly attribute
- Links to FAQ, etc. on Demo page
- Force close of open dialog on
SetMode
- Workaround for Visual Studio 2008 Designer rendering
- Workaround for iframe
onload validator issue
- Fix several W3C Validator issues
- Fix
ToLower culture issue
- July 13, 2008
- Fix Firefox 3 delete/backspace key issue
- June 29, 2008
- Fix Firefox 3 initial render layout issue
- Fix Opera 9.5 issues
- May 25, 2008
- Fix initialization issue
- Fix
Color.htm issue
- Fix conversion issues
- Fix
Link.htm issue
- February 3, 2008
- Fix
Image.htm issue
- January 2, 2008
Namespace changes to client-side classes
- Client-side
GetVersion method
- Add
sub and sup to default allowed tags
- Fix
DesignModeCss absolute path issue
- Fix Designer embedded image issue
- December 3, 2007
AutoSaveValidationGroups property
- Fix
ConvertParagraphs horizontal rule issues
- November 21, 2007
AutoSave property
Modified server-side property
ValidationProperty attribute
- Fix
GetText client-side method
- Fix resource identifier
- Enforce Pixel Height and Width
- October 30, 2007
SaveButtons property
- Add Selection and Range links to the Additional Resources section
- October 18, 2007
- Toolstrips
- Separator toolbar element
- Toolstrip background images
- Color schemes: Custom, Visual Studio, Default
ColorScheme property
CreateColorSchemeInfo server-side event property
ToolstripBackgroundImage property
ToolstripBackgroundImageCustomPath property
NoToolstripBackgroundImage property
EditorInnerBorderColor property
SelectedTabTextColor property
DialogSelectedTabTextColor property
- Demo Options:
Toggle Mode, Color Scheme and Toolstrips
- Fix
ScriptReference issue
- Refactor
Color.htm
- October 9, 2007
- Dialog framework
- Text Color dialog
- Background Color dialog
- Hyperlink Properties dialog
- Image Properties dialog
- Dialog color properties
DialogFloatingBehavior property
CreateDialogInfo event property
- Include
alt and title in allowed attributes
- Fix
context issues
- Fix XHTML
font conversion issues
- September 24, 2007
Toolbars property
- Remove
ToolbarButtonsTop property
- Remove
ToolbarButtonsBottom property
- Remove
ToolbarSelectLists property
- Fix empty toolbar issue
- August 29, 2007
- Optional toolbar buttons: Save, New, Design, HTML, View
ToggleMode property
ToolbarDocked property
ToolbarClass property
EditorBorderSize property
EditorBorderColor property
SelectedTabBackColor property
ModifiedChanged client-side event handler property
ContextChanged client-side event handler property
Save server-side event handler property
- Fix
ConvertParagraphs issues
- August 7, 2007
ToolbarButtonsTop property
ToolbarButtonsBottom property
ToolbarSelectLists property
CreateToolbarInfo event property
TextDirection property
- Include
dl in allowed tags
- August 5, 2007
ConvertParagraphs property
ReplaceNoBreakSpace property
- Fix Internet Explorer Horizontal Rule issue
- Fix Internet Explorer Ordered and Unordered List issue
- Fix
$find startup issue
- May 29, 2007
- Additional fix for Internet Explorer 6 Security Context issue
EditorBackColor property
EditorForeColor property
DesignModeCss property
NoScriptAttributes property
- Fix Internet Explorer 6 Security Context issue: May 25, 2007
- Support for Opera 9+: May 15, 2007
- Add Properties: May 14, 2007
InitialMode
DesignModeEditable
HtmlModeEditable
- Fix Firefox hide/show editor issue: May 11, 2007
- May 4, 2007
- Add Internet Explorer keydown handler
- Fix
PostBack set initial Text issue
- Fix initial text issue: May 2, 2007
- May 1, 2007
- Add Modified and Save concepts
- Convert deprecated syntax for
u, blockquote and
align
- XHTML output: April 10, 2007
- Fix Internet Explorer focus issue: April 2, 2007
- Initial article: March 31, 2007