Rich Text Editor для ASP.NET своими руками: iframe, designMode и execCommand

РазделASP.NETУровеньСредний Чтение8 минОпубликовано ТегиASP.NETJavaScriptHTML

Визуальный редактор для комментариев или описаний товаров можно собрать за час на встроенных возможностях браузера. Разбираем, как он устроен и где у самодельного решения пределы.

Главное. Базовый HTML-редактор строится на iframe с designMode и вызовах execCommand, а ASP.NET лишь принимает результат. Главная работа на сервере: строгая фильтрация HTML от XSS.

Не путать с RichTextBox из VB6

У разработчиков на классическом VB слово «Rich Text» ассоциируется с элементом RichTextBox и форматом RTF. В вебе всё иначе: редактор работает в браузере, а результатом его работы является HTML. Поэтому и возможности другие: нельзя просто вставить картинку из буфера как двоичные данные, зато ссылки, списки и форматирование текста поддерживаются естественно.

Важно и то, что такой редактор пишется на JavaScript, а не на ASP.NET. Серверная часть только принимает готовый HTML и сохраняет его. Значит, тот же редактор можно подключить к любой серверной платформе, а ASP.NET остаётся лишь получателем данных.

Основа: iframe в режиме редактирования

Браузеры умеют превращать документ в редактируемый: достаточно установить свойство designMode в значение "on". Чаще всего для этого используют iframe, потому что он даёт изолированный документ со своими стилями: оформление сайта не смешивается с оформлением редактируемого текста.

Уже в таком виде редактор работает: Ctrl+B, Ctrl+I и Ctrl+U включают жирный, курсив и подчёркивание, а браузер сам строит разметку. Включать designMode нужно после загрузки iframe, иначе в некоторых браузерах свойство не применится.

Editor.aspxASPX
<div class="toolbar">
  <button type="button" onclick="cmd('bold')"><b>Ж</b></button>
  <button type="button" onclick="cmd('italic')"><i>К</i></button>
  <button type="button" onclick="cmd('insertUnorderedList')">Список</button>
  <button type="button" onclick="addLink()">Ссылка</button>
</div>
<iframe id="editorFrame" title="Текст сообщения"
        style="width:100%;height:240px;border:1px solid #ccc"></iframe>

<asp:HiddenField ID="hfContent" runat="server" />
<asp:Button ID="btnSave" runat="server" Text="Сохранить"
            OnClientClick="syncEditor();" OnClick="btnSave_Click" />

Панель инструментов и execCommand

Кнопки панели вызывают метод execCommand документа внутри iframe. Он применяет команду к текущему выделению: bold, italic, underline, insertOrderedList, createLink, insertImage, justifyCenter и другие. Старые примеры для Internet Explorer работали через document.selection.createRange(), но execCommand на уровне документа поддерживают все браузеры, и отдельная ветка для IE не нужна.

Перед командой полезно вернуть фокус в iframe: если пользователь нажал кнопку, фокус ушёл на неё, и часть браузеров применит команду к пустоте.

editor.jsJavaScript
var frame = document.getElementById('editorFrame');
var doc;

frame.onload = init;
init();

function init() {
  doc = frame.contentWindow.document;
  doc.designMode = 'on';
}

function cmd(name, value) {
  frame.contentWindow.focus();      // фокус обратно в редактор
  doc.execCommand(name, false, value || null);
}

function addLink() {
  var url = prompt('Адрес ссылки', 'https://');
  if (url && /^https?:\/\//i.test(url)) cmd('createLink', url);
}

// Перед отправкой формы копируем HTML в скрытое поле
function syncEditor() {
  document.getElementById('<%= hfContent.ClientID %>').value = doc.body.innerHTML;
}

Передача текста на сервер

Содержимое iframe не входит в форму, поэтому само по себе на сервер не попадёт. Перед отправкой его копируют в скрытое поле, как в функции syncEditor выше. На сервере текст берётся из HiddenField.Value.

Здесь же вас встретит защита ASP.NET: проверка запроса (ValidateRequest) считает HTML в поле потенциальной атакой и бросает HttpRequestValidationException. Отключать её для всей страницы опасно. Варианты: кодировать текст на клиенте перед отправкой и раскодировать на сервере, либо отключить проверку только для этой страницы и обязательно очищать HTML вручную.

Безопасность: фильтрация HTML

Всё, что приходит из редактора, нужно считать враждебным. Злоумышленнику не нужен ваш редактор: он отправит в поле любой HTML, включая script, атрибуты onerror и ссылки вида javascript:. Если сохранить такой текст и показать его другим пользователям, получится хранимая XSS-уязвимость.

Надёжная защита строится по белому списку: разрешаются только известные теги (p, b, i, ul, li, a) и атрибуты (href с проверкой схемы), всё остальное удаляется. Регулярные выражения для этого не подходят, HTML нужно разбирать парсером.

  • Белый список тегов и атрибутов, а не чёрный.
  • Для ссылок разрешать только схемы http, https и mailto.
  • Очищать HTML на сервере при сохранении, а не только на клиенте.
  • Ограничивать длину текста, чтобы не принимать мегабайты мусора.

Где проходит граница самодельного решения

Простой редактор хорошо справляется с жирным текстом, ссылками и списками. Проблемы начинаются с вставкой из Word (приезжает тонна служебной разметки), загрузкой картинок на сервер, таблицами и одинаковым поведением во всех браузерах: execCommand в каждом из них генерирует немного разный HTML. Если требования выходят за базовое форматирование, разумнее взять готовый компонент, чем тратить недели на отладку.

Хороший компромисс для legacy-проекта: оставить самодельный редактор там, где он уже работает и пользователи им довольны, а на новых формах сразу подключать готовую библиотеку. Серверная часть при этом не меняется: она по-прежнему получает HTML из скрытого поля и пропускает его через тот же фильтр.

Как это делают сегодня

execCommand официально помечен как устаревший, и для новых проектов используют готовые редакторы: TipTap, Quill, Lexical, CKEditor 5 или TinyMCE. Они работают через contenteditable и собственную модель документа, поэтому выдают предсказуемую разметку. На сервере .NET для очистки HTML применяют библиотеку HtmlSanitizer (Ganss.Xss).

Вопросы и ответы

Почему при сохранении появляется ошибка «A potentially dangerous Request.Form value»?

Сработала встроенная проверка запроса ASP.NET на HTML в полях формы. Кодируйте текст перед отправкой или отключайте проверку точечно с обязательной фильтрацией на сервере.

Чем iframe лучше div с contenteditable?

iframe изолирует стили и скрипты страницы от редактируемого документа. contenteditable проще в подключении, его используют большинство современных редакторов.

Как вставлять картинки?

Команда insertImage вставляет изображение по URL. Сам файл нужно предварительно загрузить на сервер отдельным запросом и получить его адрес.

Читайте также