SharePoint Client Object Model. Управляемый код
С этого поста я начну рассказывать о клиентской объектной модели (Client Object Model) SharePoint, о том как она работает и как можно её использовать. Сегодня я покажу как работает клиентская модель в управляемом коде на примере создания веб-части, отображающей последние новости с сайта www.eon-russia.ru. Этот сайт как раз построен на базе SharePoint Foundation.
Источник данных
Новости на сайте www.eon-russia.ru представляют из себя вики-страницы, расположенные в библиотеке "Новости" на узле /ru/content/. Сразу оговорюсь, что здесь никакого криминала нет, все эти данные доступны любому и проследить структуру сайта можно, используя для этого адрес в формате http://[server]/_laouts/viewlsts.aspx, и, "спускаясь" ниже по иерархии узлов.
Итак, мы будем отбирать последние 20 новостей и отображать эти данные на своем портале (где эти данные показывать совершенно не важно, хоть в WinForms-приложении, хоть в консольном приложении). Нас интересуют следующие поля:
- ID - идентификатор новости;
- Title1 - заголовок новости;
- Summary - краткое описание новости;
- PublicationDate - дата публикации новости;
- Branch - филиал [Lookup];
- PublishPlace - категория [Lookup].
При желании можно считывать ещё и поле WikiField. В нем хранится само содержимое вики-страницы. Класс, представляющий новость Е.ОН, у меня выглядит вот так:
Выбор данных
Для работы с клиентской моделью SharePoint 2010 нам понадобятся две сборки: Microsoft.SharePoint.Client и Microsoft.SharePoint.Client.Runtime. Вот код метода, возвращающего нам коллекцию объектов из списка SharePoint:
- public IEnumerable<EonPublication> GetPublications()
- {
- var res = new List<EonPublication>();
- using (var ctx = new ClientContext(_siteUrl))
- {
- ctx.AuthenticationMode = ClientAuthenticationMode.Anonymous;
- var web = ctx.Web;
- ctx.Load(web);
-
- var list = web.Lists.GetByTitle("Новости");
- ctx.Load(web.Lists);
- ctx.Load(list);
-
- var caml = new CamlQuery
- {
- ViewXml =
- @"<View>
- <Query>
- <ViewFields>
- <FieldRef Name='Title1' />
- <FieldRef Name='Summary' />
- <FieldRef Name='Branch' />
- <FieldRef Name='PublicationDate' />
- <FieldRef Name='PublishPlace' />
- </ViewFields>
- <OrderBy>
- <FieldRef Name='PublicationDate' Ascending='FALSE' />
- </OrderBy>
- </Query>
- <RowLimit>20</RowLimit>
- </View>"
- };
- var listItems = list.GetItems(caml);
- ctx.Load(listItems);
- ctx.ExecuteQuery();
-
- foreach (var item in listItems)
- {
- var publication = new EonPublication(item.FieldValues);
- res.Add(publication);
- }
-
- }
- return res.ToList();
- }
В начале мы создаем контекст вызывая конструктор класса ClientContext и передаем ему URL-адрес сайта. Если указанный адрес не будет начинаться с http:// или https://, то мы получим исключение ArgumentException. В нашем случае доступ к содержимому анонимный, что мы и указываем. Client object model в SharePoint'е так же поддерживает аутентификацию на основе форм (FBA) либо Default-аутентификацию (берется текущие учетные данные).
Очень важным здесь является понимание того, что происходит при вызове методов Load() и ExecuteQuery(). Их я опишу подробней.
Метод Load()
Этот метод предназначен для загрузки свойств объекта. Самой загрузки данных с сервера при этом не происходит. Вместо этого ClientContext строит коллекцию запросов. Метод Load() принимает в качестве параметров объект ClientObject и набор лямбда-выражений для выборочной загрузки свойств. Т.е. мы можем загрузить все свойства нашего объекта list вызвав метод Load():
ctx.Load(list);
Или можно явно указать свойства, которые мы хотим загрузить. Следующий код указывает, что необходимо загрузить Id, Title и SchemaXml объекта list:
ctx.Load(list,
l => l.Title,
l => l.Id,
l => l.SchemaXml);
Если попытаться обратиться к свойству объекта ClientObject, значения которых не загружены с сервера, то мы получим исключение Microsoft.SharePoint.Client.PropertyOrFieldNotInitializedException .
Объектов, унаследованных от класса ClientObject здесь много. Стоит обратить внимание, что защищаемых объектов (унаследованных от SecurableObject) здесь только три (List, ListItem и Web). А вот объект Site, представляющий коллекцию сайтов защищаемым не является, т.к. это просто контейнер, в котором всегда есть минимум один сайт.
Метод ExecuteQuery()
Этот метод отвечает за загрузку данных с сервера. При это на сервер посылаются два запроса. Первый запрос обращается к службе /_vti_bin/sites.asmx и вызывает метод GetUpdatedFormDigestInformation для получения FormDigest. Второй запрос обращается к службе /_vti_bin/Client.svc для получения данных.
FormDigest
FormDigest, как следует из описания протокола, служит для обеспечения безопасности. Относится это к POST-запросам на сервер. Веб-приложение считывает FormDigest из параметра __REQUESTDIGEST или из заголовка X-RequestDigest. Полученное значение обязательно проходит валидацию (подробнее здесь).
В нашем случае в запрос добавляется заголовок X-RequestDigest:
- public override void ExecuteQuery()
- {
- this.EnsureFormDigest();
- base.PendingRequest.RequestExecutor.RequestHeaders["X-RequestDigest"] = this.m_formDigestInfo.DigestValue;
- base.ExecuteQuery();
- }
После чего на стороне клиента формируется запрос, содержащий набор действий (Actions), которые необходимо выполнить на сервере и описание свойств объектов (ObjectPaths). В ответ на сервере формируется JSON и отправляется клиенту.
Демонстрационный проект
Скачать можно здесь.