среда, 19 декабря 2012 г.

Получение коллекция хранимых свойств в SPWeb

Некоторые вещи в рабочем проекте хранятся именно в SPWeb.Properties, и довольно часто возникает необходимость быстро посмотреть, что сейчас там лежит. Иногда - изменить какое-нибудь значение. Для этого я использую Power Shell. Набираем:
$s = Get-SPSite http://имя сайта
$w = $s.OpenWeb()
$w.Properties
А можно ещё так
Get-SPWeb -Identity http://server | Select -ExpandProperty AllProperties
Или еще проще
(Get-SPWeb -Identity http://server).Properties
А для изменения свойства используем:
$web = Get-SPWeb "http://SITE_URL"
$web.SetProperty("property_name", "property_value"); 

$web.Update(); 
Restart-Service W3SVC -force

Взято  из: http://omlin.blogspot.com/2011/01/spweb.html

пятница, 14 декабря 2012 г.

Работа с XML в SharePoint из Javascript

SharePoint очень тесно связан с XML. Все эти схемы полей и списков, определения элементов Ribbon, SharePoint Batch API, запросы SPQuery/CamlQuery - и многое другое. И если на стороне сервера всё более-менее понятно, там есть XmlReader/XmlWriter, XmlSerializer, XDocument и т.д., то что делать на стороне клиента? Как обрабатывать XML, формировать или изменять его, и какие для этого есть функции в SharePoint EcmaScript  - всю эту информацию я постарался собрать в сегодняшней статье.

XmlWriter

Впрочем, XmlWriter в SharePoint всё-таки есть. Располагается он в пространстве имен SP (т.е. SP.XmlWriter), а физически - находится в SP.Core.js. Применение в целом аналогично серверной версии, но поддерживаются только следующие методы:
  • create(stringBuilder)
  • writeStartElement(tagName)
  • writeElementString(tagName, value)
  • writeEndElement()
  • writeAttributeString(localname, value)
  • writeStartAttribute(localname)
  • writeEndAttribute()
  • writeString(value)
  • writeRaw(xml)
Впрочем, всё основное тут есть, так что можно смело использовать: в случае формирования XML в цикле или по каким-то сложным правилам, код станет гораздо более читаемым, по сравнению с формированием XML в строках. Простейший пример использования:

var camlQuery = new SP.CamlQuery();
var sb = new Sys.StringBuilder();
var writer = SP.XmlWriter.create(sb);
writer.writeStartElement("Where");
writer.writeStartElement("Eq");
writer.writeStartElement("FieldRef");
writer.writeAttributeString("Name""StatusID");
writer.writeEndElement();
writer.writeStartElement("Value");
writer.writeAttributeString("Type""Integer");
writer.writeString("1");
writer.writeEndElement();
writer.writeEndElement();
writer.writeEndElement();
writer.close();
camlQuery.set_viewXml(sb.toString());
//Result: <Where><Eq><FieldRef Name='StatusID' /><Value Type='Integer'>1</Value></Eq></Where>

Еще, при формировании XML может пригодиться вот этот метод:
SP.Utilities.HttpUtility.escapeXmlText, как не трудно догадаться из названия, этот метод эскейпит строку так, чтобы её потом можно было использовать в name или value xml-элементов и атрибутов.

Конвертируем объекты в XML

Конечно, XmlWriter это здорово, но даже на стороне сервера его редко кто использует, потому что есть гораздо более интересные варианты. Например, XmlSerializer. И если немного поискать, то действительно, в файле SP.Runtime.js можно обнаружить методы SP.DataConvert.writeDictionaryToXml и SP.DataConvert.writePropertiesToXml. И они действительно сериализуют js-объекты... Но, использовать их врядли получится. Дело в том, что эти методы всегда генерируют теги Property.

Например, вот такой код:
var sb = new Sys.StringBuilder();
var writer = SP.XmlWriter.create(sb);
var context = new SP.SerializationContext();
var dict = { hello: "world", complexObject: { property1: "value1", property2: "value2"} };
var fields = ["hello""complexObject"];
SP.DataConvert.writeDictionaryToXml(writer, dict, "root", fields, context);
// Значение сгенерированного XML можно теперь получить через sb.toString()? получим:
<root>
   <Property Name="hello" Type="String">world</Property>
   <Property Name="complexObject" Type="Dictionary">
        <Property Name="property1" Type="String">value1</Property>
        <Property Name="property2" Type="String">value2</Property>
   </Property> 
</root>
Parse XML

Для этого я использую jQuery, по моему мнению - это лучшее решение. Делается это очень просто:
var jQueryObjectFromXml = $(xmlString); 
и дальше уже делаем всё что угодно с полученным jQuery-объектом...
Впрочем, по умолчанию jQuery в SharePoint отсутствует, поэтому иногда может пригодиться и метод из SharePoint, который превращает XML в DOM-объект. Этот метод называется CUI.NativeUtility.createXMLDocFromString и расположен в файле CUI.js. Пример использования ниже:
var domElement = CUI.NativeUtility.createXMLDocFromString( 
'<root><child name="hello world"/><child>test value</child></root>');
domElement.firstChild.nodeName // "root"
domElement.firstChild.firstChild.attributes[0].value // "hello world"
domElement.firstChild.lastChild.text // "test value" 
Взято из: http://omlin.blogspot.ru/2011/07/xml-sharepoint-javascript.html 

Обзор get-параметров в адресах SharePoint

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

1. Contents=1 (http://site/default.aspx?contents=1). Переход к странице обслуживания веб-частей.
2. IsDlg=1 (http://site/default.aspx?IsDlg=1). Открытие страницы с использованием главной страницы (master page) minimal.master, т.е. без панели быстрого запуска, частично без ленты, шапки и т.п. Часто используется в диалоговых окнах.

3. Filter=1 (http://site/lists/AllItems.aspx?Filter=1). Открытие представления списка с выпадающими фильтрами полей.


4. ToolPaneView=2 (http://site/defailt.aspx?ToolPaneView=2). Открытие страницы с панелью добавления веб-частей. В зависимости от значения параметра открывается различная вкладка: 2 – Обзор, 3 – Поиск, 5 – Импорт.

5. DisplayMode=Design (http://site/default.aspx?DisplayMode=Design). Открытие страницы в режиме редактирования.
6. Mobile=1 (http://site/default.aspx?Mobile=1). Отображение в виде для мобильных устройств.

Взято из: http://www.aviw.net/2012/09/get-sharepoint.html

CAML-запросы с расширенной фильтрацией по пользователям и группам

Речь сегодня пойдёт об использовании тега Membership в CAML-запросах. Этого тега нет ни в CAML Builder ни в CAML Designer, поэтому не все знают о его существовании. Тем не менее, на MSDN присутствует описание этого тега, хоть и без примеров. Итак, запрос с использованием этого тега может выглядеть так:
<Where>
  <Membership Type='CurrentUserGroups'>
    <FieldRef Name='Person' />
  </Membership>
</Where>

Тег Membership содержит атрибут Type, указывающий тип фильтрации:
  • SPWeb.AllUsers – Выбор элементов, содержащих в указанном поле пользователя
  • SPWeb.Groups - Выбор элементов, содержащих в указанном поле группу
  • SPWeb.Users - Выбор элементов, содержащих в указанном поле пользователя, не содержащегося в группе 
  • CurrentUserGroups - Выбор элементов, содержащих в указанном поле группу, в которую входит текущий пользователь
  • SPGroup - Выбор элементов, содержащих в указанном поле указанную группу; группа (ID) указывается отдельным атрибутом тега Membership: ID=’1’
Пример.
Допустим имеем такой список:

Тестовое (консольное) приложение:
static void Main(string[] args)
{
    var values = new Dictionary<string, int>
        {
            {"SPWeb.AllUsers", 7},
            {"SPWeb.Groups", 7},
            {"SPWeb.Users", 7},
            {"CurrentUserGroups", 7},
            {"SPGroup", 1073741823}
        };

    foreach (var value in values)
        RunQuery(value.Key, value.Value);

    Console.WriteLine();
    Console.WriteLine("Completed!");
    Console.ReadKey(true);
}

static void RunQuery(string membership, int userId)
{
    using (var site = new SPSite("http://x33/", GetUserToken(userId)))
    {
        using (var web = site.OpenWeb())
        {
            var list = web.Lists["CAMLTest"];

            var query = new SPQuery {Query = string.Format("<Where><Membership Type='{0}' ID='4'><FieldRef Name='Person' /></Membership></Where>", membership)};
            var items = list.GetItems(query);

            Console.WriteLine("Current user: {0}; Membership type: {1}; Items count: {2}", web.CurrentUser.Name, membership, items.Count);
            items.Cast<SPListItem>().ToList().ForEach(li => Console.WriteLine("ID - {0}; Title - {1}; Person - {2}", li.ID, li.Title, li["Person"]));
            Console.WriteLine("----------");
            Console.WriteLine();
        }
    }
}

static SPUserToken GetUserToken(int userId)
{
    using (var site = new SPSite("http://x33/"))
    {
        using (var web = site.OpenWeb())
        {
            return web.SiteUsers.GetByID(userId).UserToken;
        }
    }
}

Результат работы приложения:


Наиболее интересный тип фильтрации: CurrentUserGroups.
Допустим в списке у нас проставлены группы. С помощью данного запроса мы можем выбрать все элементы, которые содержат группу текущего пользователя, т.е. элементы, доступные участником тех же групп, что и для текущего пользователя! (аналог фильтрации по аудиториям)

Взято из: http://www.aviw.net/2012/12/caml.html

Разбиение запроса SPQuery на страницы

Ещё один способ получить большое количество данных – это использовать разбиение запроса на страницы, т.е. получение элементов списка порциями по несколько элементов.
Для этого надо указать объекту класса SPQuery значения для свойств ListItemCollectionPosition и RowLimit.
RowLimit – указывает сколько элементов списка необходимо получить.
Свойство ListItemCollectionPosition – это объект класса SPListItemCollectionPosition принимающий в конструкторе строковой параметр, в котором указано с какого элемента необходимо получить данные (точнее следующего за ним элемента). Выглядит это так: “Paged=TRUE&p_ID=5” – получить элементы после элемента с ID равным 5.

Например, сделаем такой метод:
private static SPListItemCollection GetPagedItems(SPList list, string pagingInfo, uint rowLimit)
{
    var query = new SPQuery
    {
        RowLimit = rowLimit,
        ListItemCollectionPosition = new SPListItemCollectionPosition(pagingInfo)
    };            
    return list.GetItems(query);
}

Метод возвращает заданное количество элементов (rowLimit) из указанной страницы (pagintInfo).
Если в pagingInfo передавать пустую строку, то метод вернёт первую страницу.
Возвращаемые методом данные (типа SPListItemCollection) содержат в себе данные для получения следующей страницы – свойство ListItemCollectionPosition.

Таким образом, мы можем организовать постраничную загрузку и обработку элементов списка, например следующим образом (используя вышеприведённый метод):
using (var site = new SPSite("http://mysite/"))
{
    using (var web = site.OpenWeb())
    {
        var list = web.Lists["TestList"];

        var pagingInfo = string.Empty; //Здесь храним информацию о странице
        var pageNumber = 1; //Номер страницы, для оформления вывода
        SPListItemCollection items = null; //Полученные данные

        do
        {
            if (items != null && items.ListItemCollectionPosition != null)
                pagingInfo = items.ListItemCollectionPosition.PagingInfo; //Получаем данные о странице из предыдущего запроса

            Console.WriteLine("Page {0}, Info '{1}'", pageNumber, pagingInfo);

            items = GetPagedItems(list, pagingInfo, 5); //Получаем по 5 элементов
            foreach (SPListItem item in items)
                Console.WriteLine(item.Title);

            Console.WriteLine();
            pageNumber++;
        } while (items.ListItemCollectionPosition != null);
    }
}

Для моего списка результат выглядит следующим образом (консольное приложение):
Взято из: http://www.aviw.net/2012/08/spquery.html

Получение большого количества данных из SharePoint или использование ContentIterator

В этом посте я хочу рассказать о ContentIterator – полезном классе, который вы можете использовать в повседневной разработке для Sharepoint. Он определен в сборке Microsoft.Office.Server.dll и помогает работать с такими общими задачами, как итерация по списку элементов списка Sharepoint (SPListItem), файлов (SPFile), веб сайтов (SPWeb) и др. Итерация по элементам и файлам реализована через SPQuery, что значит он имеет очень хорошую производительность (близкую к максимальной из того, что предлагает Sharepoint object model). ContentIterator – новый класс, который появился в Sharepoint 2010. Как сказано в документации к классу:
Sharepoint сервер предоставляет новый API, ContentIterator, для помощи при работе с большими списками, содержащими более 5000 элементов, позволяющий обойти throttling-предел и генерации SPQueryThrottleException.
Например, представим, что у нас есть ссылка на объект типа SPFolder, представляющий папку в Sharepoint списке, и мы хотим пройтись в цикле по всем элементам внутри этой папки. Для этого мы можем использовать метод ContentIterator.ProcessFilesInFolder:
SPFolder folder = web.GetFolder(...);
SPList doclib = web.Lists[...];
ContentIterator contentIterator = new ContentIterator();
 
bool isFound = false;
contentIterator.ProcessFilesInFolder(doclib, folder, false,
    f =>
        {
            // this is item iteration handler
            ...
        },
    (f, e) =>
        {
            // error handler
            ...
        });
Работа с ContentIterator основана на использовании делегатов. В параметрах метода мы передаем ссылку на родительскую библиотеку документов (либо список) и папку в ней, в которой нужно проитерировать элементы. Третий параметр указывает, нужно ли делать обход рекурсивным если внутри нашей папки есть подпапки. Четвертый параметр – это делегат, который вызывается из ContentIterator-а и который получает непосредственно каждый файл (объект типа SPFile). Последний параметр – тоже делегат, представляющий обработчик ошибок.
Давайте посмотрим как реализован этот метод. Внутри он использует другой метод ContentIterator.ProcessListItems:

public void ProcessListItems(SPList list, string strQuery, uint rowLimit,
    bool fRecursive, SPFolder folder, ItemsProcessor itemsProcessor,
    ItemsProcessorErrorCallout errorCallout)
{
    ...
    SPQuery query = new SPQuery();
    if (!string.IsNullOrEmpty(strQuery))
    {
        query.Query = strQuery;
    }
    query.RowLimit = rowLimit;
    if (folder != null)
    {
        query.Folder = folder;
    }
    if (fRecursive)
    {
        query.ViewAttributes = "Scope="RecursiveAll"";
    }
    this.ProcessListItems(list, query, itemsProcessor, errorCallout);
}
 
Для начала создается объект SPQuery. Непосредственный CAML-запрос формируется выше по стеку:

public static string ItemEnumerationOrderByPath
{
    get
    {
        return "<OrderBy Override='TRUE'><FieldRef Name='FileDirRef' /><FieldRef Name='FileLeafRef' /></OrderBy>";
    }
}

И наиболее интересная реализация перегруженного метода ProcessListItems:

public void ProcessListItems(SPList list, SPQuery query, ItemsProcessor itemsProcessor,
    ItemsProcessorErrorCallout errorCallout)
{
    string str2;
    SPListItemCollection items;
    ...
    if (!list.get_HasExternalDataSource() && (list.ItemCount == 0))
    {
        return;
    }
    if (list.get_HasExternalDataSource() && (query.RowLimit == 0))
    {
        query.RowLimit = 0x7fffffff;
    }
    else if ((query.RowLimit == 0) || (query.RowLimit == 0x7fffffff))
    {
        query.RowLimit = string.IsNullOrEmpty(query.ViewFields) ? 200 : 0x7d0;
    }
    if (!list.get_HasExternalDataSource() && this.StrictQuerySemantics)
    {
        query.set_QueryThrottleMode(2);
    }
    string strListId = list.ID.ToString("B");
    this.ResumeProcessListItemsBatch(strListId, out str2);
    if (!string.IsNullOrEmpty(str2))
    {
        query.ListItemCollectionPosition = new SPListItemCollectionPosition(str2);
    }
    int batchNo = 0;
Label_012B:
    items = list.GetItems(query);
    int count = items.Count;
    batchNo++;
    try
    {
        itemsProcessor(items);
        this.OnProcessedListItemsBatch(strListId, items, batchNo, count);
    }
    catch (Exception exception)
    {
        if ((errorCallout == null) || errorCallout(items, exception))
        {
            throw;
        }
    }
    if (!this.ShouldCancel(IterationGranularity.Item))
    {
        query.ListItemCollectionPosition = items.ListItemCollectionPosition;
        if (query.ListItemCollectionPosition != null)
        {
            goto Label_012B;
        }
    }
}
 
Внутри метода проверяется свойство RowLimit и, если оно не установлено, устанавливается в значения по умолчанию (в 200 или 2000 в зависимости от того, установлено ли свойство ViewFields или нет). Затем свойство SPQuery.QueryThrottleMode устанавливается в Strict. Документация говорит про это следующее:
Throttling запросов будет определяться числом элементов, а также полями Lookup, Person/Group и Workflow, вне зависимости от прав пользователей.
Также используется SPQuery.ListItemCollectionPosition для выборки элементов за один запрос (число элементов, возвращаемых за один запрос определяется свойством RowLimit).
Как видно из этих примеров ContentIterator делает много инфраструктурной работы за вас. Это сэкономит ваше время, позволив сконцентрироваться на непосредственной бизнес-задачей. И на последок хороший пример из MSDN тут

Взято из: http://www.aviw.net/2012/08/sharepoint-contentiterator.html

четверг, 13 декабря 2012 г.

Upload Control – Работа с элементом управления загрузки множества файлов

Сразу покажу скриншоты, о чём идёт речь:
Позволяет загружать файлы в папку или библиотеку документов на веб-сервере. В работе не сложен, но есть много нюансов.
Итак, исходный код.

<script type="text/jscript">
    function DocumentUpload() {
        var uploadCtl = document.getElementById("idUploadCtl");
        uploadCtl.MultipleUpload();
    }
</script>

<input type="hidden" name="Cmd" value="Save" />
<input type="hidden" name="putopts" value="true" /> <!-- Overwrite files -->
<input type="hidden" name="Confirmation-URL" value="<%= Page.Request.Url.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped) %>" />
<input type="hidden" name="PostURL" value="" />
<input type="hidden" name="VTI-GROUP" value="0" />
<input type="hidden" name="destination" id="destination" value="/Documents"> <!-- Files destination path, must already exist -->

<asp:Panel runat="server" Width="100%">
    <script>
        try {
            if (new ActiveXObject("STSUpld.UploadCtl"))
                document.write("<OBJECT id=\"idUploadCtl\" name=\"idUploadCtl\" CLASSID=\"CLSID:07B06095-5687-4d13-9E32-12B4259C9813\" WIDTH=\"100%\" HEIGHT=\"350px\"></OBJECT>");
        }
        catch (error) {
        }
    </script>
    <asp:Button runat="server" accesskey="O" id="OKButton" CssClass="ms-ButtonHeightWidth" Text="Загрузить файлы" UseSubmitBehavior="False" OnClientClick="DocumentUpload(); return false;"/>
    <asp:Button runat="server" accesskey="C" id="CancelButton" CssClass="ms-ButtonHeightWidth" Text="Отмена" UseSubmitBehavior="False" style="display: none;"/>
</asp:Panel>
Что мы имеем:
  1. Скрипт запуска загрузки файлов
  2. Набор параметров, организованных как скрытые поля с атрибутом name
  3. Панель с ActiveX и объектом, отображающими основной интерфейс
  4. Две кнопки – ok и отмена
Все пункты являются обязательными. Если чего-то будет не хватать, то вы получите неприятное окно с ошибкой:

1. Скрипт. Тут всё понятно. Находим сгенерированный позже объект и вызываем его метод. Начинается загрузка файлов.
2. Параметры.
  • putopts – указывает необходимость перезаписи файлов, при совпадении имён. Иначе файл не будет загружен
  • destination – указывает путь к папке или библиотеке документов куда необходимо загружать файлы
  • PostURL – теоретически, сюда нужно указать url страницы, которой нужно пересылать файлы, чтобы страница сама определила дальнейшую их судьбу; но на практике это не заработало (компонент сам загружает файлы, по пути указанному в destination)
Параметры можно изменять на лету (JavaScript’ом) перед непосредственным вызовом метода загрузки.
3. ActiveX. Единственный момент тут – данный ActiveX компонент (“STSUpld.UploadCtl”) не должен быть отключен в браузере.
4. Кнопки. Данные кнопки должны существовать. Вы можете их не использовать, скрыть и т.п., но они должны быть. Они должны быть с type=”button” (не “submit”) и у них должны быть проставлены свойства AccessKey.
Ещё несколько особенностей (скажем спасибо Wictor Wilén):
  • Все кнопки на странице должны иметь атрибут AccessKey
  • Должна быть кнопка с AccessKey=”O” (для OK) и AccessKey=”С” (для Cancel)
  • Значение для destination изначально не должно быть пустым, при необходимости его можно изменить позже
Взято из: http://www.aviw.net/2012/05/upload-control.html