вторник, 25 октября 2011 г.

SharePoint и XSLT: Развертывание через Visual Studio

К настоящему времени, в рамках серии статей про SharePoint и XSLT, уже были освещены многие интересные моменты, включая довольно актуальные примеры (разбор еще множества примеров - впереди), но до сих пор мы не касались развертывания XSL-преобразований через Visual Studio. Эта информация очень важна для разработчиков SharePoint, но замечу еще раз - что посредством SharePoint Designer, XSL-преобразованиями в SharePoint могут пользоваться и администраторы, и дизайнеры, и ITPro.

Итак, сегодня мы узнаем:
  1. Как подключать XSLT через CAML
  2. Как программно подключить XSL-преобразование к списку
  3. Чем отличаются свойства XslLink классов SPView и XsltListViewWebPart
  4. Ограничения при использовании XSLT в Sandboxed solutions
Способы подключения XSLT

Как всегда, SharePoint предоставляет множество различных возможностей для работы с XSLT через Visual Studio. Например, мы можем подключать XSL-преобразования как к представлениям списков, так и непосредственно к веб-частям XsltListViewWebPart; делать это через CAML или через объектную модель; разворачивать XSLT-файлы в SharePoint Mapped Folders или в библиотеки документов.

Очень важно знать разницу между всеми этими способами - а она, поверьте, есть. И иногда она огромная.

Через CAML

К слову сказать, Collaborative Application Markup Language, в просторечии CAML - это вся совокупность XML-схем, которые могут быть использованы в целях расширения или настройки SharePoint'а. И если кто-то до сих пор думает, что CAML - это только схема Query, предназначенная для формирования запросов к спискам - нет, это не так, и вы легко можете в этом убедиться, внимательно почитав MSDN.

В общем, все эти модули, Elements.xml, List Batch API - это всё CAML. Это очень мощная и развитая система, хотя и не всегда удобная. Кстати, не так давно я написал отдельную статью о том, почему CAML в SharePoint - это плохо, и когда следует опасаться его использовать.

Если вернуться к нашим "баранам", CAML позволяет развертывать XSL-преобразования двумя способами:
  1. Подключением к создаваемому шаблону списка
  2. Подключением к разворачиваемой веб-части XsltListViewWebPart
Как видите, нет возможности подключить XSLT к представлению существующего списка, точно также, как нет возможности подключить XSLT к уже существующей веб-части. Это довольно существенное ограничение, которое нужно понимать и иметь в виду.

Но давайте посмотрим наконец, как это делается!

Для подключения XSLT-файла нужно, прежде всего, создать сам XSLT-файл и развернуть его, чтобы обеспечить его доступность из SharePoint. Для этой цели, я создал SharePoint Mapped Folder "Layouts", в нем - папку проекта, и в ней - файл HelloWorld.xsl, код для которого взял из статьи SharePoint и XSLT: Hello world!

Подключение к создаваемому шаблону списка

Следующий шаг - создать новый элемент List Definition, на основе любого существующего шаблона списка:


Теперь откроем файл Schema.xml и пролистаем до списка представлений (элемент <Views>), найдем представление "по умолчанию" (DefaultView="TRUE"), и найдем в нем элемент <XslLink>:


Как видите, по умолчанию, в качестве файла с XSL-преобразованием указан файл main.xsl. Естественно, можно его подменить. Но как указать на наш файл HelloWorld.xsl?

Поскольку файл main.xsl лежит по пути 14\TEMPLATE\LAYOUTS\XSL, то очевидно, что нам нужно указать путь ..\SharePointXslt\HelloWorld.xsl (SharePointXslt - это название проекта). Обратите внимание: элемент XslLink принимает путь к файлу, а не Url. Попробовав указать Url в любом варианте, мы непременно получим ошибку. Таким образом, к элементу <View> никаким образом нельзя подключить XSLT-файл, размещенный в библиотеке документов! Поскольку элемент <View> в объектной модели представляется объектом SPView, то и для него это справедливо.

Итак, получается, что через шаблон списка, мы сможем подключить только файл, расположенный в пределах 14 hive. Это означает, что мы не можем подключать XSLT-файлы к шаблонам списков в Sandboxed Solutions, т.к. SharePoint Mapped Folder в песочнице не работает.

Так что для Office365 остается единственный вариант: напрямую вписывать XSLT-код в тег <Xsl>. Этого тега по умолчанию в Schema.xml вы не найдете, но его можно легко добавить вместо <XslLink>. С этим подходом всё очень очевидно, надеюсь, сложностей здесь у ни у кого не возникнет.

Подключение к разворачиваемой веб-части

Второй CAML-вариант - это присоединение XslLink к разворачиваемой веб-части XsltListViewWebPart. Веб-часть эта может быть легко развернута с помощью тега <AllUsersWebPart>.

Стандартная последовательность действий в этом случае следующая:
  1. Идем в нужный нам список (на страницу AllItems.aspx), переключаем страницу в режим редактирования (Действия сайта -> Редактировать страницу), и в контекстном меню представления списка выбираем Экспорт веб-части:


     
  2. В результате, получаем xml-файл с расширением webpart, начинающийся тегом <webParts>.
  3. В наш проект в Visual Studio, добавляем новый модуль (Module).
  4. Удаляем из модуля Sample.txt.
  5. Добавляем существующий элемент, в появившемся диалоге указываем какой-нибудь файл веб-частей, например, C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\SiteTemplates\sts\default.aspx
  6. Переходим в Elements.xml, создаем внутри тега <File> тег <AllUsersWebPart>, прописывая в нем положение нашей веб-части на странице, и внутрь тега <AllUsersWebPart> наконец уже вставляем экспортированный xml из webpart-файла, заключив, естественно, его предварительно в CDATA:
    <AllUsersWebPart WebPartZoneID="Left" WebPartOrder="1">
    <![CDATA[
        <webParts>
            <!-- ... -->
        </webParts>
    ]]>
    </AllUsersWebPart>
В общем, в итоге получим довольно длинный файл Elements.xml, с тегами Module -> File -> AllUsersWebPart. Эта конструкция развернет страницу default.aspx и добавит на неё веб-часть.

Соответственно, внутри тега <webParts> сложно не заметить теги <property>, которые как раз могут быть использованы для изменения свойств Xsl и XslLink объекта XsltListViewWebPart.

Кстати, в отличие от тега XslLink в элементе View, тег XslLink в XsltListViewWebPart принимает как раз Url'ы. То есть, можно указывать на библиотеки документов.

Однако, и здесь не обошлось без неприятностей :-( Оказывается, есть довольно серьезный "known bug", который не позволяет спокойно использовать подключение XSL-преобразований через свойство XslLink: такая попытка приводит к тому, что перестает работать контекстное меню элементов списка, а также перестают работать кнопки "Изменить элемент", "Просмотреть элемент" и "Удалить" на ленте.

Баг возникает только при использовании xsl-файлов из библиотек документов. В случае с файлами, развернутыми в _layouts - все работает прекрасно. Но как сами понимаете, для того чтобы развернуть файлы в _layouts, нужны полномочия администратора фермы...

А с библиотеками документов, да, баг очень серьезный. Он возникает не на всех компьютерах, но мне удалось его воспроизвести как минимум на пяти совершенно разных машинах, и на русском, и на английском SharePoint, и даже на двух аккаунтах Office365. Есть даже ответ от парня из Microsoft, о том, что они об этом баге знают, и над ним работают...

В итоге, опять же приходим к тому, что лучше всегда использовать тег <Xsl> и прописывать преобразования напрямую в код внутри AllUsersWebPart. И выглядеть это, поверьте мне на слово, будет очень убого (потому что двойной CDATA сделать нельзя, и придется эскейпить "по старинке", через &lt; и &gt;)... :-(

Кстати, для того, чтобы использовать созданное Xslt-преобразование списка много раз, имеет смысл не пользоваться тегом AllUsersWebPart, а развернуть "преднастроенный" webpart-файл в галерею веб-частей. Тогда пользователи сами смогут добавлять эту веб-часть на нужные им страницы. Проблема абсолютной нечитаемости XSLT-кода остается, но в случае Sandboxed Solutions этот вариант вас, возможно, просто-таки спасет...

Программное подключение XSL-преобразований

Основное отличие программного развертывания от развертывания через CAML заключается в том, что программно мы можем не только создавать новые веб-части или представления списков - но также мы можем работать с уже существующими списками, представлениями, веб-частями. Это очень полезно и здорово, но есть и минусы.

Дело в том, что программный подход как минимум намного более урезан в Sandboxed Solutions. Например, веб-части недоступны из объектной модели в песочнице, также запрещен и класс SPLimitedWebPartManager. То есть работать с XsltListViewWebPart не получится совсем.

Впрочем, когда можно работать с SPView, опускаться до уровня веб-частей обычно и не требуется.

Но и c SPView тоже не все так просто. Есть неприятный баг, на этот раз, как раз при использовании свойства Xsl. Если вкратце - использовать это свойство невозможно :)

В результате какого-то внутреннего преобразования, при присваивании этого свойства осуществляется обработка входящего xsl-кода, в результате чего пропадают префиксы xsl:. При этом преобразование само по себе, как я понимаю, остается в принципе вполне рабочим - но тут в действие вступает фактор номер два! При загрузке XSL-преобразования уже в момент отображения представления списка, происходит валидация сохраненного ранее XSL, и валидация эта требует присутствия префикса... #fail

Никакие обманные пути не помогают: в свое время, убил порядка 4 часов на попытки обойти эту проблему. Так что SPView.Xsl лучше не пытаться использовать. Не получится :(

Таким образом, для Sandbox, остается только хитрый способ с подключением XSLT-файла из папки фичи, который я описывал выше.

Для farm-solutions все проще: есть возможность использовать XsltListViewWebPart, и есть возможность развертывать XSL-файлы в _layouts.

Программное подключение XSLT к представлениям списка

Давайте теперь посмотрим код:

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
    var web = properties.Feature.Parent as SPWeb;
    var list = web.Lists["Test"];
    var view = list.DefaultView;
    view.XslLink = @"..\SharePointXslt\HelloWorld.xsl";
    view.Update();
}

Да, в основном такие вещи осуществляются в Feature Receiver, но хочу сразу же заметить, что забивать класс Feature Event Receiver простынями кода - вообще говоря нехорошо. Если у вас там планируется много кода, сразу разбивайте его на классы.

Как видите, всё довольно просто: получаем какой-то существующий список (с заголовком Test), и подключаем к его представлению "по умолчанию" файл из папки _layouts/SharePointXslt (для Office365 и Sandbox по озвученным выше причинам не работает).

Важный момент! Вот такой вот код, выглядящий вроде аналогичным представленному выше:

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
    var web = properties.Feature.Parent as SPWeb;
    var list = web.Lists["Test"];
    list.DefaultView.XslLink = @"..\SharePointXslt\HelloWorld.xsl";
    list.DefaultView.Update();
}

...работать не будет!

Дело в том, что DefaultView всегда создает новый экземпляр объекта, и поэтому list.DefaultView.Update() будет пытаться сохранить только что созданный, неизмененный объект... Более подробно можно почитать в посте Антона Вишнякова.  В общем, будьте аккуратны: на эту ошибку напарываются довольно часто.

Да, еще один момент: получать список по заголовку не очень хорошо. Заголовок ведь кто угодно может изменить, поэтому более надежный способ - это метод GetListFromWebPartPageUrl.

Код изменится следующим образом:

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
    var web = properties.Feature.Parent as SPWeb;
    var list = web.GetListFromWebPartPageUrl("Lists/Test/AllItems.aspx");
    var view = list.DefaultView;
    view.XslLink = @"..\SharePointXslt\HelloWorld.xsl";
    view.Update();
}

Программное подключение XSLT к веб-частям

Здесь тоже ничего сложного нет, главное - не забывать освобождать использованные объекты :)

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
    var web = properties.Feature.Parent as SPWeb;
    var list = web.Lists["Test"];
    using (SPLimitedWebPartManager manager = web.GetLimitedWebPartManager(list.DefaultViewUrl, PersonalizationScope.Shared))
    {
        var xlv = (manager.WebParts[0] as XsltListViewWebPart);
        xlv.XslLink = "/_layouts/SharePointXslt/HelloWorld.xsl";
        manager.SaveChanges(xlv);
    }
}

В этом примере мы изменяем страницу представления "по умолчанию" списка. Стоит отметить, что поскольку XsltListViewWebPart - это непосредственно веб-часть, которая осуществляет отображение списка, а SPView - это более абстрактный объект, то если изменить XsltListViewWebPart, то она как бы "отцепится" от своего представления, и дальнейшие изменения SPView не будут иметь эффекта.

Подводя итоги

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

Кроме перечисления способов и обсуждения отличий между ними, мы сегодня рассмотрели очень много ошибок и нюансов, и я искренне надеюсь, что эта информация поможет вам сэкономить немало времени на борьбу с, как всегда, сложноуловимыми багами SharePoint'а :)

Да, как всегда, если вам понравилась статья, не забудьте подписаться на мой блог! :) Любые комментарии приветствуются.

P.S. В следующей статье серии мы вернемся к рассмотрению "боевых" примеров использования XSLT.

6 комментариев:

  1. А Вы не пробовали для установки SPView.Xsl использовать переопределение SPView.Scheme, т.е. брать её как строку, модифицировать как нужно и сохранять модифицированное значение. Мне такой хак помогал с настройкой полей.

    ОтветитьУдалить
  2. ShurikEv, пробовал, не помогает, к сожалению. Сейчас даже не вспомню в чем именно там проблема, но то что пробовал через XML менять Xsl-свойство - помню точно :)

    ОтветитьУдалить
  3. :) Ну вобщем я вроде как вспомнил: там есть метод SetViewXml, но этот метод не работает или как-то не так работает. Проставляешь там соответствующее свойство - ничего не меняется.

    ОтветитьУдалить
  4. Андрей, уточните, пожалуйста, у Вас был установлен Service Pack 1 for Microsoft SharePoint Server 2010 при тестировании? И еще, удавалось ли Вам изменить XSLT непосредственно на Office365?

    Дело в том, что пример взятый с http://amarkeev.wordpress.com/2011/11/07/sharepoint-xslt-sandbox-office365/ у меня не работает если изменить его на Sandboxed. Кстати, в вашем варианте он поставляется как Farm solution. Фича просто не активируется на Office365. Активировать получается только, если закомментировать строку:
    view.XslLink = @"..\..\FEATURES\SandboxTest_Feature1\XsltModule\Sample.xsl"; Правда толку после этого никакого :)

    Пока мне удалось подключить XSL только для Farm Solution используя XslLink в Schema.xml.

    Но этот-же пример НЕ работает для Sandboxed - получаю "Unable to display this Web Part ..."
    Что не удивительно, т.к. фолдер 14\TEMPLATE\FEATURES\SandboxTest_Feature1 не создан.

    Еще я заметил, что папка FEATURES\SandboxTest_Feature1 не удаляется, если вначале сделать Deploy из Visual Studio когда Sandboxed = false, а после установить Sandboxed = true и сделать повторный Deploy. В этом случае Sandboxed пример может заработать, т.к. файлы не удалились.

    ОтветитьУдалить
  5. Федор, возможно я не прав, постараюсь проверить ASAP.

    ОтветитьУдалить

Внимание! Реклама и прочий спам будут беспощадно удаляться.