Skip to content

Instantly share code, notes, and snippets.

@koltyakov
Created February 15, 2017 16:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save koltyakov/472173fced87f2cfbf9f3927ed425043 to your computer and use it in GitHub Desktop.
Save koltyakov/472173fced87f2cfbf9f3927ed425043 to your computer and use it in GitHub Desktop.
working-with-sharepoint-mmd-in-node-js

Работа со службой управляемых метаданных SharePoint в Node.js

Программный интерфейс REST API в SharePoint решает массу задач автоматизации в SharePoint и является универсальным, платформанезависимым, однако зачастую можно "встрять" в ситацию ограничений и отсутствия нужной функциональности в REST API, которая есть в других объектных моделях (CSOM/JSOM). Не проблема, когда исполняемый код является частью клиентского приложения на странице SharePoint, т.к. отсутствующий в REST метод может быть в JSOM API. Но что, если контекст исполнения - не страница в браузере? Если код запускается не в браузере, самое очевидное использовать C# CSOM или PowerShell c SSOM или CSOM. Но что, если среда исполнения и используемый язык ограничены и .Net не вариант?

В данной статья я хочу рассмотреть возомжности Node.js и JavaScript "на сервере". А задачей будет обеспечить базовый слой функциональности для работы с Managed Metadata (Taxonomy). Необходимо реализовать, например, следующие методы и при этом не использовать дополнительного веб-сервиса на .Net:

  • Получение дочерних "термов" (term) в коллекции термов
  • Полечение дочерних термов в родительском
  • Получение экземпляра терма
  • Добавление нового и обновление существующего терма
  • Пометка терма, как устаревшего
  • Получение всего набора термов в коллекции
  • ...

Самым простым решением для реализации большинства операций может быть SOAP сервис. Да, SOAP устарели и попахивают временами динозавров, но тем не менее это как вариант, работают же.

Для работы с MMD есть сервис таксономии /_vti_bin/TaxonomyClientService.asmx. Давайте взглянем на его возможности. Сервис предоставляет следующие методы:

  • AddTerms
  • GetChildTermsInTerm
  • GetChildTermsInTermSet
  • GetKeywordTermsByGuids
  • GetTermSets
  • GetTermsByLabel

Для того, чтобы вызвать "мыльные методы" в контексте Node.js можно воспользоваться любым модулем для http запросов, получать авторизационный cookie и вставлять в заголовки запросов. К счастью, уже существуют библиотеки, которые абстрагируют нюансы аутентификации и транспортный уровень. Я говорю о sp-request с node-sp-auth.

С использованием sp-request можно легко обращаться к SOAP службе, поменяв заголовки для работы с XML и отключив JSON. Примерно так:

const baseUrl = 'https://contoso.sharepoint.com/sites/site';
let authObject = { ... }; // формат авторизации node-sp-auth, см. описание модуля
let request = require('sp-request').create(authObject);
let headers = {};

let soapBody = `
   ... // XML тело SOAP-запроса
`;

headers['Accept'] = 'application/xml, text/xml, */*; q=0.01';
headers['Content-Type'] = 'text/xml;charset=\"UTF-8\"';
headers['X-Requested-With'] = 'XMLHttpRequest';
headers['Content-Length'] = soapBody.length;

request.post(baseUrl + '/_vti_bin/TaxonomyClientService.asmx', {
  headers: headers,
  body: soapBody,
  json: false
})
  .then(response => {
    // Proceed the responce object
  })
  .catch(err => console.log(err));

Разобраться с форматом SOAP-пакета и нюансами, которые не всегда фигурируют в описании, иногда бывает непросто. Но в итоге это всего лишь SOAP и в интернете много материалов по разным методам, тут можно смело абстрагироваться от Node.js как такового.

В ходе потребностей нескольких проектов мы обернули нужные нам метода в виде библиотеки NPM с открытым исходным кодом sp-screwdriver. Данная библиотека, по большей степени пример, т.к. она не оптимизирована для массового использования и формат методов, получающих XML и транслирующих JSON может поменяться.

С SOAP разобрались. Но далее другая незадача. Интерфейс SOAP, как минимум для MMD, крайне ущербен и нефункционален, многих нужных операций просто напросто нет. Что насчет редактирования существующих термов, что насчет запроса всех термов, удаления? К счастью, дело тут решенное. Мы применяем отчасти хак, но эффективный и позволяющий безпрепятственно использовать всю мощь CSOM/JSOM. Подход следующий:

  • Пишется код на JSOM, выполняющий нужные действия
  • Код исполняется в родной для него среде в браузере и мониторятся сетевые запросы в fiddler
  • В списке запросов ищется отправленные в client.svc (/_vti_bin/client.svc/ProcessQuery)
  • Из тела запроса берется XML пакет, парсится и оборачивается в функцию на JavaScript для Node.js

Например, запрос по изменению наименования терма, выглядит так:

<Request xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009" 
      SchemaVersion="15.0.0.0" LibraryVersion="15.0.0.0" 
      ApplicationName="Javascript Library">
    <Actions>
        <SetProperty Id="166" ObjectPathId="157" Name="Name">
            <Parameter Type="String">{{ newName }}</Parameter>
        </SetProperty>
    </Actions>
    <ObjectPaths>
        <StaticMethod Id="146" 
           Name="GetTaxonomySession" 
           TypeId="{981cbc68-9edc-4f8d-872f-71146fcbb84f}" />
        <Property Id="149" ParentId="146" Name="TermStores" />
        <Method Id="151" ParentId="149" Name="GetByName">
            <Parameters>
                <Parameter Type="String">{{ serviceName }}</Parameter>
            </Parameters>
        </Method>
        <Method Id="154" ParentId="151" Name="GetTermSet">
            <Parameters>
                <Parameter Type="String">{{ termSetId }}</Parameter>
            </Parameters>
        </Method>
        <Method Id="157" ParentId="154" Name="GetTerm">
            <Parameters>
                <Parameter Type="String">{{ termId }}</Parameter>
            </Parameters>
        </Method>
    </ObjectPaths>
</Request>

Немного по формату:

  • Версия схемы изменена на 15.0.0.0 это обеспечит обратную совместимость с On-Premises SharePoint версии 2013.
  • Часть лишнего кода вычищается. Глядя на пакет, скопированный из fiddler, в принципе ясно-понятно, что лишнее.
  • ИД-шники отражают последовательность, объеденяя блоки в цепочку методов клиентской объектной модели. От запроса к запросу они, естественно разные, но, если их оставить такими же, как сформировались на момент отслеживания fiddler'ом, при повторении работать будет. За время эксплуатации месяц ошибок замечено не было. Так и порешили фиксировать статические идентификаторы.
  • Параметры в {{ фигурныхСкобках }} - это динамическая часть, которая меняется от запроса к запросу методом на JS. В отправляемом пакете, естественно, никаких {{ }} нет, это артифакт для Handlebars, который задействован в примере.

Примеры реализации getAllTerms, setTermName и deprecateTerm можно посмотреть в примере sp-screwdriver.

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

let Screwdriver = require('sp-scredriver');
let context = require('./path_to_private_settings');
let screw = new Screwdriver(context);

let data = {
    baseUrl: context.siteUrl,
    serviceName: config.mmd.serviceName,
    termSetId: config.mmd.termSetId,
    properties: [
        'Id', 'Name', 'Description', 'CustomProperties',
        'IsRoot', 'IsDeprecated', 'PathOfTerm',
        'IsAvailableForTagging', 'Parent'
    ]
};

screw.mmd.getAllTerms(data)
    .then(response => {
        let results = JSON.parse(response.body);
        console.log("Response:", results);
    })
    .catch(err => console.log('Error:', err.message));

Для себя я нашел очень полезным решением обращаться к JSOM API и изредка SOAP методам из Node.js. Такой подход на текущий момент задейтвован в ряде проектов в виде Web Job'ов, обрабатывающих срез данных, включая MMD, в ходе пакетной обработки и трансформации данных.

Так же данные подход обоснованно применен в одном из недавших решений с десктоп клиентом на Electron под macOS без необходимости дополнительных веб-сервисов.

Ни в коем случае не хочу перетягивать одеяло для решения аналогичных задач на .Net. Однако замечу, что есть ряд сценариев, где Node.js или любая другая платформа могут быть объективно необходимы, тогда подобного рода взаимодействие с SharePoint может весьма оправдано.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment