вторник, 24 марта 2015 г.

CSR: пользовательский рендеринг для полей определённого типа

Недавно встала задача по разработке пользовательского типа поля, которое потом нужно было особым образом отобразить в стандартном CSR-представлении. Как следствие возникла задача: как сделать шаблон отображения представления, с учётом того, что мы знаем только тип поля, но не знаем имён полей?

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

Дальше самостоятельно пришел к решению, что нужно использовать обработчик OnPreRender, и в нём анализировать тип поля:

В результате получил ошибку Type Error. Дальнейшее исследование показало, что SharePoint перед вызовом OnPreRender меняет структуру данных объекта Fields, и правильно нужно писать вот так:

Суммарный код выглядит в итоге так:

Результат в SharePoint:

четверг, 13 ноября 2014 г.

SharePoint 2013 Workflow: HttpSend и DynamicValue

Сегодня я остановлюсь подробнее о ключевой активности при работе с workflow - активности HttpSend. Эта активность является ключевой, потому что приходиться постоянно работать с службами данных, так как собственно других средств для процесса, запущенного в отдельной ферме внутри Workflow Manager, не существует. Активность HttpSend разработана специально для SharePoint и появляется в Visual Studio только в проекте SharePoint 2013, хотя и является универсальной. То есть её вполне можно использовать для запросов к другим разработанным службам данных. Её особенность при работе с SharePoint состоит в том, что эта активность автоматически устанавливает заголовок Authorization: Bearer для аутентификации с помощью OData, в остальном же это обычный запрос с помощью WebRequest.

Рассмотрим работу с активностью HttpSend подробнее. Для начала рассмотрим работу со стандартным сервисом SharePoint.

В первом примере я получаю список приложенных файлов. Адрес запроса для получения списка файлов можно получить из возвращаемого свойства элемента AttachmentFiles/__deferred/uri:
Получив url, отправляем запрос с помощью HttpSend:
Сначала получаем токен S2S с помощью активности GetS2SSecurityToken и сохраняем его в переменной типа SecurityToken. Этот токен подставляем в параметр активности SecurityToken.
Так как у нас будут возвращены комплексные данные, то работать будем с ними с помощью переменной типа DynamicValue. Для этого заводим переменную этого типа attachments. Активность HttpSend имеет следующую приятную особенность: если задать на вход в параметр RequestContent переменную типа DynamicValue, то в тело запроса автоматически вставится JSON и добавится заголовок HTTP Content-Type: application/json; odata=verbose. Также, если задать в выходном параметре ResponseContent переменную типа DynamicValue, то автоматически добавится заголовок HTTP Accept: application/json; odata=verbose, а возвращённый сервером результат в виде JSON будет разобран и помещён в переменную. Поэтому в нашем примере мы получаем структуру данных со списком вложенных файлов сразу в переменной attachments типа DynamicValue.
Для контроля результатов запроса мною используется выходное свойство ResponseStatusCode, которое позволяет сохранить статус ответа HTTP в переменной типа HttpStatusCode.
Для перебора массива в сложной структуре DynamicValue удобнее всего использовать активность ForEach<T>. В нашем примере список файлов возвращается в структуре d/results, для её перебора используем конструкцию: attachments["d"]["results"].

Во втором примере я запрашиваю свой сервис данных WCF. Мне необходимо обновить несколько полей в сущности Equipment (которая фактически является таблицей в базе данных). Для формирования структуры данных, которую мы будем отсылать в сервис, будем использовать тот же самый DynamicValue. Для этого создаём переменную equipmentProperties и инициализируем её с помощью активности BuildDynamicValue:
Сама обновляемая сущность у нас адресуется примерно таким образом: /service.svc/Equipment(guid'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXXX'), где guid - это уникальный идентификатор в таблице. Обновление сущности целиком (всех полей) надо производить с помощью метода PUT. Но, так как мы ходим обновить только несколько полей, то для этого необходимо использовать специальный метод MERGE, который описан в стандарте OData. В выпадающем списке входного параметра Method активности HttpSend такого метода нет, чтобы его задать нужно просто кликнуть на кнопку с тремя точками и воспользоваться текстовым редактором.
Для успешной аутентификации в службе данных обязательно задаём заголовок HTTP Athorization: "" (пустая строка) для того, чтобы сбросить значение Bearer, которое автоматически задаётся (о чём было написано выше).
При успешном обновлении данных сервисом возвращается код 204 (No Content), что я и проверяю после вызова.

Это всё, что хотел рассказать сегодня. Всем удачных разработок!

понедельник, 17 февраля 2014 г.

Отображение всех комментариев c помощью SocialCommentControl

Для отображения и добавления комментариев в SharePoint используется элемент управления SocialCommentControl из пространства имён Microsoft.SharePoint.Portal.WebControls сборки Microsoft.SharePoint.Portal.dll. Проблема состоит в том, что этот элемент управления отображает по-умолчанию комментарии только текущего пользователя. А так как этот элемент управления в большей степени предназначен для внутреннего использования, то все свойства у него типа internal и недоступны для непосредственного изменения.

Класс SocialCommentControl имеет внутреннее свойство NeverSecurityTrim, которое как раз позволяет отобразить все комментарии. Для этого пишем вставляем такой код:

Где CommentControl - ссылка на элемент управления SocialCommentControl. Я вставил этот код в Page_Load.


среда, 15 января 2014 г.

Не про SharePoint: сравнение строк в Entity Framework (Linq)

Нужно было сравнивать строки в запросе Linq для Entity Framework. Задумался, как там будет с регистром. Поискал, для Linq to SQL нашел вроде бы ответ (использовать что-то вроде String.Equals(row.Name, "test", StringComparison.OrdinalIgnoreCase)).

Оказалось - хренушки. Ошибка выглядит так: ArgumentException: Incorrect number of arguments supplied for call to method 'Boolean Equals(System.String, System.String, System.StringComparison)'

В результате, использовал обычное сравнение и ToUpper(), получилось как надо.


четверг, 26 декабря 2013 г.

Работаем с внешними данными в Workflow с помощью WCF Data Services и OData

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

Разработка такого сервиса не вызывает проблем, если использовать технологию WCF Data Services.

Очень удобно использовать службы данных WCF с технологией Entity Framework: добавляем модель и создаём службу на её базе:

Надо сказать, что тут есть ограничение: службы данных работают исключительно со стандартными типами данных. Например, у меня в таблице БД есть колонка типа geography. Entity Framework создал мне сущность с полем типа GeographyPoint. Но вот службы данных работать с ним отказались. Пришлось делать отдельную мини-модель без этой сущности, благо работать с ней не нужно было. Может есть ещё какие-то ограничения, пока отдельно этот вопрос не исследовал, если кто-то сталкивался, пишите, пожалуйста, в комментариях.

Итак, служба данных у нас готова. (Всего-то максимум час работы, красота!) Службы данных работают через OData. Это для нас очень удобно, так как позволяют легко вызывать их из рабочего процесса с помощью активности HttpSend(я уже упоминал про работу с HttpSend тут). Для каждого действия с внешними данными (получение или обновление) я создал отдельную активность рабочего процесса.

Трудности могут вызвать формат строки URI для запроса к сервису. Но на помощь приходит документация. Например, тут описание запроса данных с фильтрацией. А здесь описание обновления отдельного свойства.

Отдельно хочу сказать про операции обновления. Выполняются они с помощью операции PUT. В IIS эта операция требует специальных разрешений на папку, в которой находится сервис. Workflow Manager делает запрос из-под своего аккаунта. Поэтому ему необходимо дать разрешения:

Адрес службы данных рабочий процесс должен откуда-то получить. Я передавал адрес в виде строки как параметр при запуске рабочего процесса. Для этого пришлось отказаться от автоматического запуска рабочего процесса и запускать его в обработчике событий SharePoint. Как это делать я уже описывал.

Про самое главное здесь - про безопасность. Как это всё друг с дружкой увязывается.

Служба данных работает из-под своей учётной записи. Я так и назвал её - DataService. Из-под этой учётной записи будет идти обращение к БД. В настройках IIS должна быть выключена анонимная аутентификация и включена аутентификация Windows для операций обновления. Если операций обновления нет, то в принципе можно оставить анонимность, но я бы всё равно не стал бы так делать.

Безопасность на уровне БД определяется путём заведения специальной роли, я её назвал Data Service. Выдал ей разрешения на нужные объекты БД, и добавил в неё учётную запись DataService.

Workflow Manager работает также под своей учётной записью - sp_workflowmanager. Ей нужны разрешения на папку IIS (см. выше).

HttpSend по-умолчанию посылает HTTP-запрос анонимно, без указания заголовка аутентификации. Для того, чтобы его заставить всё-таки отсылать заголовок, нужно указать пустой заголовок Authorization:

Вот и всё. Надеюсь, кому-нибудь пригодиться.


вторник, 10 декабря 2013 г.

Использование AppFabric DataCache

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

Но сдаваться не в наших традициях, поэтому сразу оформился вариант непосредственно использовать службу AppFabric DataCache. Оказалось, что использовать её вообще проще простого. Нужно, чтобы служба была установлена, и хосты SharePoint были к ней подключены. Но если служба распределенного кэша настроена и работает, то это условие уже выполняется автоматически. Так что устанавливать и настраивать дополнительно ничего не нужно. Единственно что требуется, так это создать собственный кэш (если конечно мы не хотим использовать кэш по умолчанию default). Создается кэш с помощью команды PowerShell New-Cache (подробно об администрировании кэша смотри тут). Программным путём создать кэш, судя по всему, нельзя, по крайней мере, на официальных страницах Microsoft я ничего об этом не нашёл.

На программном уровне работа идет через класс DataCache. Получить его нужно с помощью класса DataCacheFactory.

Конфигурация задаётся или с помощью конфигурационного файла:

Или программно, например, так:

Пример записи в кэш:

Чтение из кэша:

CachingManager здесь - мой класс, возвращающий DataCache по имени. Используется SPSecurity.RunWithElevatedPrivileges, так как работа с кэшом требует особых разрешений, которых у обычного пользователя может не быть.


четверг, 5 декабря 2013 г.

SharePoint 2013 Workflow: ошибка в CreateListItem: System.FormatException: Hexadecimal 0x expected in '{0}'.

Продолжаю тему рабочих процессов в SharePoint 2013. Столкнулся со странным: казалось бы, совершенно невинное activity CreateListItem выдает странное исключение: System.FormatException: Hexadecimal 0x expected in '{0}'. Покопавшись в блогах и форумах, быстро нашёл причину: ошибка происходит при выборе имени списка из выпадающего списка в свойстве ListId:

Visual Studio подставляет в свойство автоматически выражение типа: System.Guid.Parse("{$ListId:Lists/List1;}"). А среда, видимо, выражение "{$ListId:Lists/List1;}" вычисляет некорректно. В результате, ошибка при разборе Guid.

В интернетах советуют просто захардкодить в свойство ListId идентификатор (guid) списка. Но такое решение мне не показалось хорошим. Нужно было придумать, как получить идентификатор списка в процессе выполнения. Странно, но среди стандартных activity нужное действие по получению свойств произвольного списка отсутствует.

На выручку пришло событие HttpSend и REST (про REST смотри, например, тут: Working with lists and list items with REST). С помощью HttpSend посылаем запрос вида: webUrl + "/_api/web/lists/GetByTitle('" + ListTitle + "')", в ответ получаем все основные свойства списка. Для удобства результат лучше получать в виде JSON. Для этого посылаем специальный HTTP-заголовок: Accept=application/json;odata=verbose

Для удобства все эти действия оформил в виде custom activity. Результат получился примерно такой:

В результате, сначала получаем ИД списка с помощью нашей activity, сохраняем в переменной и указываем её в параметре ListId: