Проблема
Недавно столкнулся с необходимостью показать в отчете SSRS 2008 R2 HTML-таблицы, хранящиеся в базе данных.
И здесь на сцену статисты выносят заботливо подготовленные «грабли». Все дело в том, что Report Manager в SSRS 2008 R2 поддерживает только ограниченное количество тегов HTML, и табличные в их число не входят.
Согласно официальной документации, поддерживаются только следующие теги:
- Ссылки: A HREF
- Шрифт: FONT
- Заголовки, блочные элементы: H{n}, DIV, SPAN,P, LI
- Форматирование текста: B, I, U, S
- Списки: OL, UL, LI
Список атрибутов CSS также сильно урезан.
После продолжительного поиска в интернете, стало понятно, что ни один из вариантов, предлагаемых сообществом разработчиков, меня не устроит.
Исследование
Варианты предлагались следующие:
- Рендерить HTML в картинку на стороне сервера с помощью специально разработанного компонента (Custom Report Items)
- Заменить теги на поддерживаемые с симуляцией таблиц (нужно писать скрипт в самом отчете)
В одном из обсуждений натолкнулся на подсказку — Javascript на стороне клиента. Готового решения не было.
Начал с попыток проанализировать какой код выполняется на стороне клиента при отображении отчета. Подсмотрел в код отчета в браузере и нашел внедренный скрипт ReportingServices.js:
<script language="JScript" src="/Reports_SQLEXPRESS/js/ReportingServices.js" type="text/Javascript"></script>
Сам скрипт у меня на сервере лежал здесь:
c:Program FilesMicrosoft SQL ServerMSRS10_50.SQLEXPRESSReporting ServicesReportManagerjsReportingServices.js
Нашел пару статей на тему Javascript-инъекции с помощью данного скрипта, но основная проблема была в том, что мои модификации выполнялись только один раз при отображении первой страницы отчета, и при переходе на следующие страницы интерфейсные элементы уже не перерисовывались (AJAX).
Попытки глубже разобраться в принципах работы SSRS продолжились изучением фрагментов кода основных DLL-ей сервера с помощью DevExtras CodeReflect и препарированием отчетов в IE9 developer tool/Opera Dragonfly.
В результате нашел следующее:
- Для WebForms которые используют Ajax, есть ряд событий, определяющих жизненный цикл страницы
- Одно из таких событий — Load, вызывается каждый раз, при загрузке страницы и всех вызовов Ajax
- Подробности в статье Ajax Client Life-Cycle Events: http://msdn.microsoft.com/en-us/library/bb386417.aspx
Для того чтобы добавить свой обработчик — нужно создать JS-функцию pageLoad, которая автоматически будет выполняться при возникновении события Load:
function pageLoad(sender, args) {
//здесь наш код, например alert("Работает!");
}
Вставляю данную конструкцию в самое начало скрипта ReportingServices.js. Работает!
Решение
Собственно, дальше уже головоломка собралась и получилось следующее решение:
- Добавляем в начало скрипта %SSRS Install Dir%Reporting ServicesReportManagerjsReportingServices.js следующую функцию:
function pageLoad() { var toDecode = document.getElementsByTagName("div"); // получаем все DIVы for (var i = 0; i < toDecode.length; ++i){ // перебираем if(toDecode[i].innerHTML.indexOf("HTMLInject:") == 0){ // DIV содержит префикс? Decoded = toDecode[i].innerText || toDecode[i].textContent; // поддержка IE, Opera, FF toDecode[i].innerHTML = Decoded.slice(11); // вырезаем префикс & TXT --> HTML } } }
- Теперь, чтобы вывести поле как HTML, в свойствах placeholder'а в поле Value надо написать следующее:
="HTMLInject:"+Fields!FieldName.Value
, где FieldName — имя поля датасета, содержащего HTML.
При этом свойство «GeneralMarkup type» самого поля нужно поставить в значение "None — Plain text only".
- При необходимости изменения стилей HTML — можно внести правки в этот CSS: %SSRS Install Dir%ReportingServicesReportManagerStylesReportingServices.css Однако, мне повезло и мои HTML таблицы были отформатированы классами, которые я и добавил в CSS-файл.
А теперь примеры
Так по-умолчанию показывается поле («None — Plain text only»):
Так показывает таблицы, если тип разметки поставить в «HTML — Interpret HTML tags as styles»:
Мой вариант:
Про/контра такого решения
Достоинства:
- Работает
- Легко и быстро сделать
Недостатки:
- Работает только при отображении отчетов в веб-интерфейсе. В других форматах отчетов (Excel, PDF, Word...) отображается весь HTML с тегами. Как обходное решение можно использовать несколько столбцов для одного поля и признак видимости "Show or hide based on an expression" с формулой "=Globals!RenderFormat.Name"(то есть показывать только один столбец, в зависимости от формата отчета). Далее, в отчетах для других форматов уже можно разработать функцию для плейсхолдера, чтобы как-то причесать HTML (например заменой на поддерживаемые теги, или отображением как картинки).
- Слетает навигация по меню отчета (DocumentMap) — то есть якоря в документе расставляются некорректно и для отрендеренных в HTML элементов может происходить смещение на величину, составляющую разницу между тем, как собирался показать SSRS наше поле, и как в итоге показал.
- Быстродействие на сложных отчетах и медленных клиентах
- XSS
Выводы
Вот так, с помощью нехитрых приспособлений, можно научить SSRS показывать HTML… Но ЗАЧЕМ?!
Сотни постов, от растерянных до гневных, переполняют интернеты из-за такой мелочи.
Даже BIRT это уже давно умеет, а новейшая версия SSRS2012 еще нет (пруфлинк: http://technet.microsoft.com/en-us/library/ff519562.aspx).
Есть мысли, что MS реализовала показ HTML в таком ограниченном виде чтобы не пострадала безопасность/стабильность системы отчетности, но похоже, что все альтернативные решения напоминают пресловутый «троллейбус».
Автор: Cyr