предыдущая статья: Обзор архитектуры Umbraco CMS

05.12.2013

Использование Razor при разработке сайтов на Umbraco

В версии 4.7 CMS Umbraco, в систему были внесены серьезные изменения, касающиеся подсистемы исполнения макросов.

В дополнение к имеющемуся механизму реализации макросов на языке трансформаций XSLT, для разработки сайтов теперь доступен «движок», использующий новейшую разработку Microsoft – Razor View Engine, являющийся частью платформы MVC 3.0. Мы не будем подробно останавливаться на деталях самого Razor’а – информации в сети достаточно. Покажем, как работать с Razor’ом в Umbraco.

Идеологически доступ к данным времени исполнения осуществляется в этом «движке» по механизмам, очень схожим с доступом из XSLT. Там мы получали через встроенный параметр CurrentPage текущий узел документа – прямо в виде фрагмента XML-кэша времени исполнения – и далее могли, используя оси XPath, «добраться» до любых нужных данных. Также имели возможность пользоваться библиотеками расширения XSL для выполнения процедур, не свойственных ядру преобразователя.

В макросе на Razor, попадая в тело макроса, написанного, на C# или VB, мы всегда получаем в распоряжение «встроенный» объект Model, который является отражением текущего документа.

Рассмотрим, что это за объект. Краеугольным камнем нового движка является базовый класс, доступный в .NET Framework версии 4, под названием DynamicObject и его наследник – класс DynamicNode, предоставляющий механизм get- и set-методов, способных давать доступ к свойствам реального объекта через синтаксис obj.PropertyName. Таким образом, в объекте Model, мы имеем возможность обращаться к свойствам документа непосредственно по именам псевдонимов (alias-ов) полей документа. Вторым важнейшим моментом, на который обратим внимание, является реализация другого основанного на DynamicObject класса – DynamicNodeList, который кроме уже упомянутого механизма доступа к свойствам, реализует типизированный интерфейс IEnumerable, чем позволяет пользоваться всеми прелестями LINQ to Objects.

К примеру, предполагая наличие у текущего документа-галереи некоторого числа дочерних узлов типа Picture (алиас типа документа в терминах Умбрако), получить количество этих дочерних узлов можно с помощью конструкции

@Model.Pictures.Count()

или же, более обобщенно, при помощи свойства Children

@Model.Children.Count()

Класс DynamicNodeList внутри себя содержит оригинальную публикуемую коллекцию IEnumerable<DynamicNode>, что позволяет получить к ней при желании непосредственный доступ:

var list = @Model.Children;
list.Items // => IEnumerable<DynamicNode>

Ну и поскольку мы говорим о реализации интерфейса коллекции, то все нижеследующие вызовы могут быть применены на практике. Обратите внимание на «цепочные» вызовы, которые здесь вполне допустимы:

//.Take (возвращает первые X элементов списка) 
@Model.Children.Take(2) // => первые 2 элемента
  
//.Skip 
@Model.Children.Skip(2) //=> список без первых 2-х пропускаемых элементов
  
//.ElementAt 
@Model.Children.ElementAt(1) //=> Второй элемент списка. Вызов вернет null, если нет искомых элементов списка 
  
//.Count 
@Model.Children.Count() //=> Количество элементов в списке
  
//Chaining 
//Можно делать цепочные вызовы: 
@Model.Children.Skip(2).Take(2)
@Model.Children.Skip(2).Take(2).Count()

Передача параметров

В макросы с использованием Razor можно передавать параметры с помощью все того же встроенного в Умбрако механизма, которым мы пользовались в других типах макросов.

Доступ к параметрам в теле макроса осуществляется с помощью конструкции @Parameter.paramName.

Доступ к элементам словаря Umbraco упрощен конструкцией вида @Dictionary.itemKey.

Доступ к другим документам

Для доступа к другим документам, относительно текущего, существуют методы, аналогичные осям XPath в макросах на XSLT. Свойство Children мы в деле уже видели, есть также методы Ancestors (для получения списка родителей «вверх» по дереву), и AncestorOrSelf(level) для доступа к родителю указанного уровня, Descendants() и DescendantsOrSelf().

Для «прямого» доступа к документу по его идентификатору можно создавать его динамическое представление с помощью конструкторов:

// передаем строковое представление идентификатора через параметр QueryString 
new DynamicNode(HttpContext.Current.Request.QueryString["id"]) 
// передает целочисленный идентификатор документа 
new DynamicNode(1046)

Обратим внимание на то, что объявлять создаваемую переменную следует с применением типа dynamic, для того чтобы «движок» мог иметь доступ к полям и методам объекта.

dynamic node = new DynamicNode(1046)

Доступ к медиа-данным

Для простого доступа к данным медиа-библиотеки создан специальный тип DynamicMedia. Для создания объекта этого типа можно воспользоваться либо прямым вызовом конструктора:

dynamic mediaItem = new DynamicMedia(1054);
@mediaItem.umbracoFile // => /path/to/media/somepicture.jpg

либо получить доступ через вспомогательный метод .Media, имеющий две реализации. Первая – для получения всего узла с последующим обращением к свойствам его через уже упомянутые механизмы:

dynamic mediaItem = @item.Media("friendPic");
@mediaItem.umbracoFile 
@mediaItem.comment //(в предположении, что comment – поле, определенное нами в этом типе медиа)

Вторая – для простого доступа к свойству медиа-узла по имени свойства

@item.Media("friendPic", "umbracoFile")

Пример использования:

@foreach(var item in @Model.Children) { 
    <IMG src='@item.Media("friendPic", "umbracoFile")'>
}

Параметры конструктора DynamicMedia аналогичны параметрам конструктора DynamicNode.

Есть два сокращенных метода для быстрого получения динамического представления документа или медиа-узла по идентификатору с последующим доступом к динамическим свойствам объектов:

@Model.NodeById(1046).Name
@Model.MediaById(1054).umbracoFile

Преобразование типов

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

If (@Model.booleanProperty){ 
    //Do something 
} 

То же касается целочисленных полей

If (@Model.friendCount > 1) { 
    //Do something 
}

Доступ к полям объекта, в которых располагаются фрагменты корректных XML-данных, осуществляется с помощью той же нотации obj.propName. Для доступа к узлам таких XML-данных с повторяющимися именами, можно использовать индексирующие скобки []:

@Model.xmlProperty.Catalog.Books[1].Genre

Отдельно обратим внимание на необходимость соблюдать регистр написания таких свойств – заглавные и строчные буквы считаются различными.

Выборки и сортировки

Важной составляющей в реализации коллекции динамических узлов является пара методов Where() и OrderBy().

С помощью первого можно осуществлять уточняющие выборки из коллекции по условиям, вторым методом – сортировать выборку.

@Model.Children.Where("shouldBeFeatured")

или

@Model.Children.Where("shouldBeFeatured == true")

А также

@Model.Children.Where("shouldBeFeatured != true")
@Model.Children.Where("friendCount > 1")
@Model.Children.Where("friendCount % 2 == 0")
@Model.Children.Where("menuType == \"Top Menu\" || menuType == \"Bottom Menu\"")

Для передачи в условие Where() параметров извне, при невозможности указать их как константы, можно применять следующую конструкцию:

var maxLevelForSitemap = 4; 
var values = new Dictionary<string,object>(); 
values.Add("maxLevelForSitemap", maxLevelForSitemap) ;         
var items = node.Children.Where("ShouldBeVisible == true && Level <= maxLevelForSitemap", values);

Бывают случаи, когда работы со свойствами текущего контекста документа для построения правильной выборки недостаточно, и нужен доступ к соседним узлам. Для таких случаев предназначен  вспомогательный метод XPath, позволяющий строить любые корректные выражения для навигации по документам:

@foreach(var item in @Model.XPath("//ChildItem[friendCount > 4 and count(.//friendPics) > 0]").Random(4)) {
    @item.Name
}

Сортировка возможна как по одному полю, так и по нескольким, с применением прямого и обратного порядка для каждого поля в списке сортировки:

@Model.Children.OrderBy("friendCount")
@Model.Children.OrderBy("friendCount, age desc")

Замечание: в официальном релизе Умбрако 4.7 есть досадный баг, не позволяющий корректно сортировать документы по «встроенным» системным полям вроде createDate или sortOrder. Исправить положение может пересобранная библиотека umbraco.MacroEngines.dll.

Работа с формами и вводом данных

В простом случае работа с формами сводится к разделению кода на две ветки по условию IsPost, сигнализирующему обработку POST-запроса: либо отрисовка формы, либо ее обработка.

@{
  if (!IsPost) {
    // здесь рисуем форму например средствами простого «чистого» HTML
    <input type="text" name="formField" id="formField" />
  }
  else {
// здесь обрабатываем POST-запрос с использованием переменных запроса
// Request["formField"]
// в качестве примера – отправка полей формы после предварительной
// обработки при помощи метода umbraco.library.Sendmail()
  }
}

Понятно, что для валидации ввода на клиенте, ничего не мешает использовать любые клиентские библиотеки, например, jQuery.

Создание макроса в среде Umbraco

Для создания макроса на Razor в административной консоли Умбрако нужно перейти в раздел «Для разработчиков» (Developer) и в узле дерева «Файлы скриптов» добавить новый скрипт через контекстное меню «Создать».

Далее необходимо указать имя файла, язык, на котором макрос будет реализован (C# или VisualBasic).

razor

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

После выбора всех параметров и подтверждения создания, система перенаправит Вас в редактор исходного текста.

// Пример макроса (реализует вывод N последних новостей с список новостной ленты)
@using System.Linq;
@using umbraco.MacroEngines;

@{
  int maxItems = int.Parse(Parameter.maxItems);
  dynamic newsNode = @Model.NodeById(1107);

  <ul class="news">
    @foreach (var item in @newsNode.Children.Where("newsdate <= DateTime.Today && umbracoNaviHide != true"
           ).OrderBy("newsdate desc").Take(maxItems)) {
      <li>
        <p class="date">@item.newsdate.ToString("dd.MM.yyyy")</p>
        <p class="intro"><a href="@item.Url" title="Читать новость">@umbraco.library.StripHtml(
          item.intro.ToString())</a></p>
      </li>
    }
  </ul>
}

следующая статья: Описание CMS Umbraco. Общие сведения