Использование приемников событий в SharePoint Foundation 2010 (часть 2 из 2)

Обзор:  приемники событий в Microsoft SharePoint Foundation 2010 позволяют настраиваемому коду реагировать на определенные действия в объекте SharePoint. Практические примеры в этой статье показывают, как использовать события для улучшения приложений SharePoint.

Дата последнего изменения: 9 марта 2015 г.

Применимо к: Business Connectivity Services | Open XML | SharePoint Designer 2010 | SharePoint Foundation 2010 | SharePoint Online | SharePoint Server 2010 | Visual Studio

В этой статье
Практические примеры использования событий
Пример 1. Трансляция объявлений
Пример 2. Разделение элементов
Пример 3. Отслеживание источников документов
Пример 4. Отмена и перенаправление
Пример 5. Запись событий в журнал
Заключение
Дополнительные материалы

Предоставлено: Али Бадереддин (Ali Badereddin), корпорация Майкрософт; Ник Гаттучио (Nick Gattuccio), корпорация Майкрософт

Содержание

  • Практические примеры использования событий

  • Пример 1. Трансляция объявлений

  • Пример 2. Разделение элементов

  • Пример 3. Отслеживание источников документов

  • Пример 4. Отмена и перенаправление

  • Пример 5. Запись событий в журнал

  • Заключение

  • Дополнительные материалы

Данная статья является продолжением статьи Использование приемников событий в SharePoint Foundation 2010 (часть 1 из 2).

Практические примеры использования событий

Теперь, когда вы узнали о модели событий в Microsoft SharePoint Foundation 2010, можно приступить к созданию обработчиков событий в своем коде. В оставшейся части этой статьи представлено пять примеров, использующих модель событий SharePoint разными способами:

  • Трансляция объявлений   Объявления, добавленные на любом из сотен сайтов в семействе, отображаются в веб-части объявлений в корневом сайте.

  • Разделение элементов   Если элемент добавляется в список, он автоматически разделяется на два или больше элементов. В этом примере также демонстрируется отмена события без ошибки и использование контейнера свойств в приемнике события "до".

  • Отслеживание источников документов   При редактировании библиотеки документов обновляется свойство метаданных. В этом примере демонстрируется повышение и понижение свойства.

  • Отмена и перенаправление   При отмене пользовательской операции пользователь перенаправляется на страницу ошибки.

  • Запись событий в журнал   Каждое событие, возникающее в семействе веб-сайтов, записывается в журнал как элемент списка, который хранится на сайте.

Для каждого из этих примеров можно загрузить решение Microsoft Visual Studio.

Для облегчения просмотра примеров предоставляется следующий обзор модели событий SharePoint. В таблице 3 перечислены все приемники событий SharePoint Foundation 2010 по области применения, указаны классы, которым они принадлежат, и узлы событий, с которыми их можно связать.

Таблица 3. Приемники событий, сгруппированные по области

Область

Методы

Класс

Узлы

Семейство веб-сайтов

  • SiteDeleting

  • SiteDeleted

SPWebEventReceiver

  • Семейство веб-сайтов

Веб

  • WebAdding

  • WebProvisioned

  • WebDeleting

  • WebDeleted

  • WebMoving

  • WebMoved

SPWebEventReceiver

  • Семейство веб-сайтов

List

  • ListAdding

  • ListAdded

  • ListDeleting

  • ListDeleted

SPListEventReceiver

  • Семейство веб-сайтов

  • Шаблоны списка

List

  • EmailReceived

SPEmailEventReceiver

  • Семейство веб-сайтов

  • Веб

  • Шаблоны списка

  • Экземпляры списков

Поле

  • FieldAdding

  • FieldAdded

  • FieldUpdating

  • FieldUpdated

  • FieldDeleting

  • FieldDeleted

SPListEventReceiver

  • Семейство веб-сайтов

  • Веб

  • Шаблоны списка

  • Экземпляры списков

Элемент

  • ItemAdding

  • ItemAdded

  • ItemUpdating

  • ItemUpdated

  • ItemDeleting

  • ItemDeleted

  • ItemCheckingIn

  • ItemCheckedIn

  • ItemCheckingOut

  • ItemCheckedOut

  • ItemFileMoving

  • ItemFileMoved

  • ItemFileConverted

  • ItemAttachmentAdding

  • ItemAttachmentAdded

  • ItemAttachmentDeleting

  • ItemAttachmentDeleted

SPItemEventReceiver

  • Семейство веб-сайтов

  • Веб

  • Шаблоны списка

  • Экземпляры списков

  • Тип контента

Рабочий процесс

  • WorkflowStarting

  • WorkflowStarted

  • WorkflowCompleted

  • WorkflowPostponed

SPWorkflowEventReceiver

  • Семейство веб-сайтов

  • Веб

  • Шаблоны списка

  • Экземпляры списков

  • Тип контента

Пример 1. Трансляция объявлений

В этом примере есть семейство веб-сайтов с сотнями отдельных сайтов. Корневой сайт содержит веб-часть объявлений. Цель этого сценария — показать все объявления, опубликованные на всех отдельных сайтах, в веб-части объявлений, размещенной в корневом сайте семейства.

Щелкните, чтобы получить кодЗагрузить пример кода: Использование приемников событий, пример 1. Трансляция объявлений (SharePoint 2010) (Возможно, на английском языке)

Общая схема

Предположим, что корневой сайт семейства содержит список объявлений. Первое, что нужно сделать — добавить веб-часть для этого списка на домашнюю страницу корневого сайта.

Добавление веб-части объявлений

  1. Перейдите к домашней странице корневого сайта семейства веб-сайтов.

  2. Откройте вкладку Страница и нажмите кнопку Изменить страницу.

  3. Щелкните вкладку Вставить и выберите Существующий список. Появятся все списки и библиотеки документов корневого сайта.

  4. Выберите список Объявления и нажмите кнопку Добавить.

Затем требуется скопировать все объявления, опубликованные на любом из дочерних сайтов в список объявлений на корневом сайте. Для этого создайте приемник событий ItemAdded, который копирует любой элемент, добавленный в список (на основе шаблона списка объявлений), в список объявлений на корневом сайте. Логика события должна инициироваться для каждого списка объявлений на всех веб-сайтах семейства (за исключением списка объявлений на корневом сайте).

Реализация

В Microsoft Visual Studio 2010 создайте проект на основе шаблона проекта приемника событий SharePoint 2010. Выберите изолированное развертывание решения в "песочнице". В мастере настройки SharePoint выберите список объявлений в качестве источника события и ItemAdded как заменяемое событие, как показано на рис. 6.

Рис. 6. Мастер настройки SharePoint

Мастер настройки SharePoint

При этом будет создан новый компонент в веб-области с элементом <Receivers>, который привязывает приемник событий ItemAdded к шаблону списка объявлений (ListTemplateId="104"), как показано в следующем фрагменте.

<Receivers ListTemplateId="104">
  <Receiver>
    <Name>EventReceiver1ItemAdded</Name>
    <Type>ItemAdded</Type>
    <Assembly>$SharePoint.Project.AssemblyFullName$</Assembly>
    <Class>BroadcastAnn.EventReceiver1.EventReceiver1</Class>
    <SequenceNumber>10000</SequenceNumber>
  </Receiver>
</Receivers>

В таблице 4 описываются различные элементы в элементе <Receiver>.

Таблица 4. Элементы Receiver

Элемент

Описание

Значение в этом примере

Name

Имя привязки приемника событий.

EventReceiver1ItemAdded. Можно указать собственное имя.

Type

Тип события, который может быть текстовым представлением любых значений перечисления SPEventType.

ItemAdded.

Assembly

Полное имя сборки библиотеки DLL, которая содержит код приемника событий.

$SharePoint.Project.AssemblyFullName$. Это имя преобразуется в полное имя сборки, когда Visual Studio 2010 создает решение.

Class

Пространство имен и класс (в формате пространство_имен.класс), наследуемый от SPEventReceiverBase и содержащий код приемника событий.

BroadcastAnn.EventReceiver1.EventReceiver1, где "BroadcastAnn.EventReceiver1" — это пространство имен, а "EventReceiver1" — имя класса.

SequenceNumber

Порядок выполнения событий. Например, если два события ItemAdding привязаны к одному узлу событий, первым инициируется событие с меньшим последовательным номером.

10000. Это значение по умолчанию.

Synchronization

Является ли событие синхронным или асинхронным. События "после" асинхронные.

Так как синхронизация не указана и ItemAdded — это событие "после", режим по умолчанию — асинхронный.

Для инициации события на всех веб-сайтах семейства область компонента необходимо изменить на сайт. Тогда функциональность компонента будет включаться при его активации в семействе веб-сайтов и его не нужно будет активировать на каждом дочернем сайте. Учтите, что область сайта компонента означает семейство сайтов (см. рис. 7), а веб-область относится к сайтам.

Рисунок 7. Установка области компонента

Задание области компонента

Затем следует открыть файл EventReceiver1.cs и добавить код в событие ItemAdded. В результате элемент, добавленный в список объявлений на дочернем сайте, копируется в список объявлений корневого сайта. Код также добавляет к заголовку имя человека, разместившего объявление, и добавляет к телу URL-адрес дочернего веб-сайта, на котором было размещено объявление.

public override void ItemAdded(SPItemEventProperties properties)
{
    // Get a reference to the site collection.
    SPSite site = properties.OpenSite();

    // Get a reference to the current site.
    SPWeb currentWeb = properties.OpenWeb();

    // Get a reference to the root site.
    SPWeb rootWeb = site.RootWeb;

    // Skip if the root web is the same as the current web.
    if (rootWeb.Url == currentWeb.Url)
    {
        return;
    }

    // Get the current list.
    SPList currentList = properties.List;

    // Get the announcement list on the root site.
    SPList rootList = rootWeb.Lists["Announcements"];

    // Get the list item that was added.
    SPListItem currentListItem = properties.ListItem;

    // Add the announcement item to the list on the root web.
    SPListItem rootListItem = rootList.Items.Add();
    foreach (SPField field in currentList.Fields)
    {
        if (!field.ReadOnlyField)
        {
            rootListItem[field.Id] = currentListItem[field.Id];
        }
    }

    // Append the user display name to the title.
    rootListItem["Title"] += " - " + properties.UserDisplayName;

    // Append the web URL to the body.
    rootListItem["Body"] += string.Format("This announcements was made by {0} on subweb {1}",
      properties.UserLoginName, properties.WebUrl);

    rootListItem.Update();
}

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

Затем создайте новый дочерний сайт на основе шаблона сайта группы. Добавьте элемент в список объявлений и убедитесь, что копия элемента показана в списке объявлений корневого сайта, как показано на рис. 8.

Рисунок 8. Элемент, добавленный в список объявлений корневого сайта

Элемент, добавленный в список "Извещения" на корневом сайте

Пример 2. Разделение элементов

Цель этого примера — добавить элемент в список и разделить его на два новых элемента. Например, если пользователь добавляет набор Xbox 360, можно предотвратить добавление этого элемента и вместо этого добавить в список два других элемента: "Xbox 360" и "Kinect". Более наглядный пример — ситуация, в которой передается ZIP-файл, а затем его содержимое извлекается и каждый файл добавляется как отдельный элемент.

Для поддержки разделения элементов в этом примере также показано, как отменить действие без ошибки в приемнике события "до".

Щелкните, чтобы получить кодЗагрузить пример кода: Использование приемников событий, пример 2. Разделение элементов (SharePoint 2010) (Возможно, на английском языке)

Общая схема

Для этого проекта требуется три типа контента для представления трех элементов: "Набор Xbox 360", "Xbox 360" и "Kinect" соответственно. В этом сценарии требуются только заголовки элементов, поэтому каждый тип контента наследуется от типа контента "Элементы" без добавления дополнительных полей.

После добавления типа контента "Набор Xbox 360" требуется удалить этот элемент или отменить его добавление, а затем создать два других элемента на основе типов контента "Xbox 360" и "Kinect".

Для этого сначала требуется привязать событие "до" ItemAdding к типу контента "Набор Xbox 360". Событие ItemAdding создает два других элемента и отменяет добавление текущего элемента. Так как нам не нужно инициировать какую-либо логику при добавлении элемента "Xbox 360" или "Kinect", мы не привязываем эти типы контента к каким-либо приемникам событий. В целях упрощения обновления не обрабатываются (имеется в виду ситуация изменения типа содержимого элемента на "Набор Xbox 360" или "Kinect" с типа "Xbox 360").

При отмене добавления типа контента "Набор Xbox 360" следует избежать получения сообщения об ошибке. Так как извлекается элемент, добавляемый в событии "до" (перед внесением изменений в базу данных контента), свойство properties.ListItem все еще имеет значение null. Для доступа к полям элемента требуется использовать контейнер свойств properties.AfterProperties. Так можно получить доступ к данным, которые требуется скопировать (в этом примере — только заголовок), на основе внутренних имен полей в схеме списка.

Реализация

Создайте проект Visual Studio 2010 на основе шаблона пустого проекта SharePoint. Выберите изолированное развертывание решения в "песочнице". Щелкните правой кнопкой проект Добавить новый элемент. Выберите пункт Тип контента, укажите имя Xbox360Bundle и нажмите кнопку Добавить. Visual Studio создаст тип контента с именем "имя_проекта - Xbox360Bundle".

Важное примечание.Важно!

Не забудьте изменить имя на Xbox360Bundle в файле Element.xml.

Повторите эти действия для двух других типов контента, указав имена Xbox360 и Kinect.

Затем щелкните правой кнопкой проект и добавьте новый элемент приемника событий с именем BundleEventReceiver. Выберите параметр Добавляется элемент и нажмите кнопку Готово. На этом этапе создается класс приемника событий, который заменяет событие ItemAdding. Однако по умолчанию он также привязывает событие к шаблону списка. Нам нужно привязать событие к типу контента Xbox360Bundle вместо шаблона списка. Поэтому требуется удалить привязку события Elements.xml (показано с помощью выделения на рис. 9) и добавить привязку в типе контента Xbox360Bundle файла Element.xml.

Рисунок 9. Привязка Elements.xml

Привязка Elements.xml

Перед добавлением привязки события убедитесь, что атрибут Inherits типа контента в файле Elements.xml задан как FALSE, а не TRUE. В противном случае привязка приемника событий к типу контента не будет работать. Так как наследования больше нет, требуется определить все ссылки на поля самостоятельно. Таким образом, нужно добавить ссылку в поле Title. Файл Element.xml типа контента Xbox360Bundle должен выглядеть следующим образом.

<Elements xmlns="https://schemas.microsoft.com/sharepoint/">
  <!-- Parent ContentType: Item (0x01) -->
  <ContentType ID="0x01004497ac28eb9a47fbabee43f48c3f5973"
               Name="Xbox360Bundle"
               Group="Custom Content Types"
               Description="My Content Type"
               Inherits="FALSE"
               Version="0">
    <FieldRefs>
      <FieldRef ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd247}" 
                Name="Title" 
                Required="TRUE" 
                ShowInNewForm="TRUE" 
                ShowInEditForm="TRUE"/>
    </FieldRefs>
    <XmlDocuments>
      <XmlDocument NamespaceURI="https://schemas.microsoft.com/sharepoint/events">
        <spe:Receivers xmlns:spe="https://schemas.microsoft.com/sharepoint/events">
          <Receiver>
            <Name>Item Adding Event</Name>
            <Type>ItemAdding</Type>
            <Assembly>$SharePoint.Project.AssemblyFullName$</Assembly>
            <Class>ItemSplitting.BundleEventReceiver.BundleEventReceiver</Class>
            <SequenceNumber>10000</SequenceNumber>
          </Receiver>
        </spe:Receivers>
      </XmlDocument>
    </XmlDocuments>
  </ContentType>
</Elements>

Теперь откройте файл BundleEventReceiver.cs и добавьте код в событие ItemAdding, которое отменяет добавление элемента Xbox360Bundle и создает два новых элемента на основе типов контента Xbox360 и Kinect.

public override void ItemAdding(SPItemEventProperties properties)
{
    // Get a reference to the current list.
    SPList list = properties.List;

    // Get the "Xbox360" content type.
    SPContentType xboxContentType = list.ContentTypes["XBox360"];

    // Get the "Kinect" content type.
    SPContentType kinectContentType = list.ContentTypes["Kinect"];

    // If any of the content types are null, they were not created.
    if (xboxContentType == null || kinectContentType == null)
    {
        properties.Status = SPEventReceiverStatus.CancelWithError;
        properties.ErrorMessage = "The Xbox360 and Kinect content types must be present.";
        return;
    }

    // Disable event firing so that ItemAdding is not called recursively.
    this.EventFiringEnabled = false;

    // Create the "Xbox360" item.
    SPListItem xboxItem = list.AddItem();
    xboxItem["Title"] = properties.AfterProperties["Title"] + " (Xbox 360)";
    xboxItem["ContentTypeId"] = xboxContentType.Id;
    xboxItem.Update();

    // Create the "Kinect" item.
    SPListItem kinectItem = list.AddItem();
    kinectItem["Title"] = properties.AfterProperties["Title"] + " (Kinect)";
    kinectItem["ContentTypeId"] = kinectContentType.Id;
    kinectItem.Update();

    // Re-enable event firing.
    this.EventFiringEnabled = true;

    // Cancel the creation of the "Xbox360Bundle" item but don't throw an error.
    properties.Status = SPEventReceiverStatus.CancelNoError;
}

Обратите внимание на следующие сведения о предыдущем примере кода:

  • Если какой-либо из типов контента не привязан к списку, событие ItemAdding отменяется и отображается сообщение об ошибке "The Xbox360 and Kinect content types must be present" (Необходимо указать типы контента Xbox360 и Kinect). Учтите, что добавлять оператор properties.Cancel = true не нужно.

  • Чтобы избежать ситуации, когда ItemAdding рекурсивно вызывает себя (до предела в стеке вызова), следует отключить вызов события перед добавлением элементов в событии ItemAdding. После добавления всех элементов вызов события повторно активируется.

  • Выражение properties.AfterProperties["field"] используется вместо properties.ListITem["field"], так как ItemAdding — это событие "до", т. е. элемент списка еще не создан и значение properties.ListItem равно null.

  • Нам не нужно добавлять разделяемый элемент. Поэтому мы отменяем событие без возникновения ошибки, используя оператор properties.Status = SPEventReceiverStatus.CancelNoError. Не добавляйте properties.Cancel=true, так как при этом логика будет изменена и возникнет ошибка.

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

Следующий этап — создание нового списка и привязка к нему трех типов контента. Сначала убедитесь, что типы контента разрешено добавлять в список. Это можно сделать, задав для параметра Разрешить управление типами контента значение Да на странице дополнительных параметров списка. Затем добавьте три типа контента, как показано на рис. 10.

Рисунок 10. Привязка типов контента

Привязка типов контента

Теперь можно создать новый элемент на основе типа контента Xbox360Bundle, как показано на рис. 11.

Рисунок 11. Создание нового элемента

Создание нового элемента

Если нажать кнопку Сохранить, можно увидеть, что элемент не добавлен. Вместо него добавлены два элемента: Xbox360 и Kinect. Если удалить тип контента Xbox360 или Kinect из списка, при попытке сохранения отобразится сообщение об ошибке.

Пример 3. Отслеживание источников документов

Возможность использовать приемники событий для управления свойствами документа — это очень удобная функция. В этом примере показано, как добиться этого, реализуя повышения и понижения свойств с помощью приемников событий. Используя повышение и понижение свойства, можно читать, добавлять и обновлять метаданные документа непосредственно в коде приемника событий при добавлении документа в библиотеку и даже при изменении документа в библиотеке.

В этом примере устанавливается значение свойства метаданных Source, равное заголовку библиотеки документов, при изменении документа.

Щелкните, чтобы получить кодЗагрузить пример кода: Использование приемников событий, пример 3. Отслеживание источников документов (SharePoint 2010) (Возможно, на английском языке)

Общая схема

В этом примере создается приемник событий ItemUpdating, который привязывается ко всем библиотекам документов на сайте. При сохранении документа в библиотеке код обработчика события добавляет или обновляет свойство метаданных Source значением заголовка библиотеки документов.

Для тестирования такого поведения создаются две библиотеки документов, в каждую из которых добавляется поле Source. Затем документ Microsoft Word передается в первую библиотеку документов и сохраняется. Затем этот документ Word загружается, клиент Word используется для изучения его метаданных, после чего документ передается в другую библиотеку документов. При передаче значение поля Source должно быть равно заголовку библиотеки документов, в которую документ был передан в первый раз. Однако при последующем сохранении документа в новой библиотеке документов значение его свойства Source должно измениться на заголовок новой библиотеки документов. Метаданные сохраненного документа изучаются для подтверждения ожидаемых результатов.

Реализация

В следующем коде реализовано событие ItemUpdating.

public override void ItemUpdating(SPItemEventProperties properties)
{
    string fieldInternalName = properties.List.Fields["Source"].InternalName;
    properties.AfterProperties[fieldInternalName] = properties.ListTitle;
}

Постройте решение и запустите отладчик. Это все действия, необходимые для создания решения, его развертывания в семействе веб-сайтов SharePoint и запуска браузера с домашней страницей семейства веб-сайтов.

Создайте новую библиотеку документов и добавьте в нее поле Source. Чтобы добавить поле, откройте параметры библиотеки документов, щелкните Добавить из существующих столбцов сайта, выберите поле Source, нажмите кнопку Добавить и затем нажмите кнопку ОК.

Потом отправьте документ Word в эту библиотеку. Обратите внимание на то, что значение поля Source пустое. Но если нажать кнопку Сохранить, значение поля Source изменится на заголовок библиотеки документов.

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

Теперь нажмите кнопку Save. Значение поля Source изменится на заголовок новой библиотеки документов.

Пример 4. Отмена и перенаправление

Этот пример основан на сценарии, в котором операция пользователя отменяется (при возникновении ошибки или по другой причине), а пользователь перенаправляется на настраиваемую страницу ошибки.

Щелкните, чтобы получить кодЗагрузить пример кода: Использование приемников событий, пример 4. Отмена и перенаправление (SharePoint 2010) (Возможно, на английском языке)

Общая схема

В этом примере создается приемник веб-событий и в событие WebMoving добавляется код, отменяющий переименование URL-адреса сайта и перенаправляющий пользователя на страницу параметров сайта. (Эта страница используется нами для упрощения примера. Пользователя можно перенаправить на настраиваемую страницу с сообщением об ошибке).

Реализация

Создайте проект Visual Studio 2010 на основе шаблона проекта приемника событий SharePoint 2010. Выберите изолированное развертывание решения в "песочнице". В мастере настройки выберите Веб-события в качестве источника события и Перемещается сайт как событие. Измените область компонента на сайт.

Добавьте следующий код, который отменяет переименование и перенаправляет пользователя на страницу параметров сайта.

public override void WebMoving(SPWebEventProperties properties)
{
    properties.Status = SPEventReceiverStatus.CancelWithRedirectUrl;
    properties.RedirectUrl = properties.Web.ServerRelativeUrl +
      "/_layouts/settings.aspx";
}

Постройте решение и запустите отладчик. Это все действия, необходимые для создания решения, его развертывания в семействе веб-сайтов SharePoint и запуска браузера с домашней страницей семейства веб-сайтов.

Затем создайте дочерний сайт и попытайтесь его переименовать. (Дочерний сайт необходим, потому что корневой сайт переименовать нельзя). Чтобы переименовать сайт, откройте страницу параметров сайта. В разделе Внешний вид и функции щелкните Заголовок, описание и значок. Введите новое имя сайта в поле URL-имя. Нажмите кнопку ОК.

Попытка переименовать сайт будет неуспешной и пользователь будет перенаправлен на страницу параметров сайта.

Пример 5. Запись событий в журнал

В этом примере рассматривается сценарий, в котором код записи в журнал записывает все события, происходящие в семействе веб-сайтов, и записывает каждое событие в список, размещенный на сайте.

Щелкните, чтобы получить кодЗагрузить пример кода: Использование приемников событий, пример 5. Запись событий в журнал (SharePoint 2010) (Возможно, на английском языке)

Общая схема

В таблице 5 перечислены все события SharePoint и указаны списки, в которые записываются все события. События SiteDeleting и SiteDeleted не записываются, так как они инициируются при удалении семейства веб-сайтов.

Таблица 5. События и журналы

События

Класс

Список журналов

SiteDeleting, SiteDeleted

SPWebEventReceiver

[не записывается]

WebAdding, WebProvisioned, WebDeleting, WebDeleted, WebMoving, WebMoved

SPWebEventReceiver

WebEventLogger

ListAdding, ListAdded, ListDeleting, ListDeleted

SPListEventReceiver

ListEventLogger

EmailReceived

SPEmailEventReceiver

ListEventLogger

FieldAdding, FieldAdded, FieldUpdating, FieldUpdated, FieldDeleting, FieldDeleted

SPListEventReceiver

ListEventLogger

ItemAdding, ItemAdded, ItemUpdating, ItemUpdated, ItemDeleting, ItemDeleted, ItemCheckingIn, ItemCheckedIn, ItemCheckingOut, ItemCheckedOut, ItemFileMoving, ItemFileMoved, ItemFileConverted, ItemAttachmentAdding, ItemAttachmentAdded, ItemAttachmentDeleting, ItemAttachmentDeleted

SPItemEventReceiver

ListItemEventLogger

WorkflowStarting, WorkflowStarted, WorkflowCompleted, WorkflowPostponed

SPWorkflowEventReceiver

WorkflowEventLogger

В данном примере перегружаются все методы события всех классов, наследуемых от SPEventReceiverBase. Также создается дополнительный класс в файле Common.cs, который содержит вспомогательные методы. Затем события "после" привязываются как синхронные, чтобы обновить соответствующие списки до завершения пользовательской операции. Область компонента, который позволяет привязывать события — это сайт, поэтому он обрабатывает все события, возникающие в семействе веб-сайтов.

Реализация

Создайте проект Visual Studio 2010 на основе шаблона пустого проекта SharePoint. Выберите изолированное развертывание решения в "песочнице".

Для каждого типа событий в таблице 6 щелкните правой кнопкой проект, нажмите кнопку Добавить, наведите указатель на Новый элемент и щелкните Приемник событий. Выберите свойства события, указанные в таблице, и нажмите кнопку Готово.

Таблица 6. Свойства новых приемников событий

Тип событий

Элемент

События

Веб

Нет

Все события

List

Нет

Все события

Элемент списка

Настраиваемый список

Все события кроме событий контекста

Адрес электронной почты списка

Настраиваемый список

Все события

Рабочий процесс списка

Настраиваемый список

Все события

Для обработки всех событий элемента списка, событий электронной почты и рабочего процесса, инициированных в любом списке (а не только в настраиваемых списках), удалите атрибуты ListTemplateId из элементов <Receivers>. (Измените <Receivers ListTemplateId="100"> на <Receivers>).

По умолчанию события "после" асинхронные. Все наши события "после" должны быть синхронными, поэтому добавьте элемент <Synchronization> и задайте значение "Synchronous" в каждом элементе <Receiver> всех файлов Elements.xml. Например, событие WebDeleted должно выглядеть следующим образом.

<Receiver>
  <Name>EventReceiver1WebDeleted</Name>
  <Type>WebDeleted</Type>
  <Assembly>$SharePoint.Project.AssemblyFullName$</Assembly>
  <Class>PTCEventLogger.EventReceiver1.EventReceiver1</Class>
  <SequenceNumber>10000</SequenceNumber>
  <Synchronization>Synchronous</Synchronization>
</Receiver>

Измените область компонента на "Сайт" для обработки событий, возникающих во всем семействе веб-сайтов. Помните, что при создании дочернего сайта инициируются события создания сайта и все события добавления элементов на сайт. На это может потребоваться слишком много ресурсов в решении для песочницы, что может привести к ошибке создания веб-сайта. Эту потенциальную проблему можно устранить, изменив значение параметра Изолированное решение на False в свойствах проекта или задав для компонента веб-область.

Затем добавьте класс с именем common в проект и разместите его в файле Common.cs. Добавьте следующие три вспомогательных метода в данный класс.

  • IsBeforeEvent   Указывает, является ли событие событием "до" или "после".

    /// <summary>
    /// Indicates whether an event is a Before event or an After event.
    /// </summary>
    /// <param name="eventType"></param>
    /// <returns></returns>
    private static bool IsBeforeEvent(SPEventReceiverType eventType)
    {
        return (eventType < SPEventReceiverType.ItemAdded) ? true : false;
    }
    
  • EnsureLogList   Определяет, существует ли список журналов. Если список существует, мы получаем на него ссылку. В противном случае создаем список и возвращаем ссылку на него.

    /// <summary>
    /// Ensures that the Logs list with the specified name is created.
    /// </summary>
    /// <param name="web"></param>
    /// <param name="listName"></param>
    /// <returns></returns>
    private static SPList EnsureLogList(SPWeb web, string listName)
    {
        SPList list = null;
        try
        {
            list = web.Lists[listName];
        }
        catch
        {
            // Create list.
            Guid listGuid = web.Lists.Add(listName, listName, SPListTemplateType.GenericList);
            list = web.Lists[listGuid];
            list.OnQuickLaunch = true;
    
            // Add the fields to the list.
            // No need to add "Title" because it is added by default.
            // We use it to set the event name.
            list.Fields.Add("Event", SPFieldType.Text, true);
            list.Fields.Add("Before", SPFieldType.Boolean, true);
            list.Fields.Add("Date", SPFieldType.DateTime, true);
            list.Fields.Add("Details", SPFieldType.Note, false);
    
            // Specify which fields to view.
            SPView view = list.DefaultView;
            view.ViewFields.Add("Event");
            view.ViewFields.Add("Before");
            view.ViewFields.Add("Date");
            view.ViewFields.Add("Details");
            view.Update();
    
            list.Update();
        }
    
        return list;
    }
    
  • LogEvent   Создает новый элемент в указанном списке и записывает сведения о событии в этот элемент.

    /// <summary>
    /// Log the event to the specified list.
    /// </summary>
    /// <param name="web"></param>
    /// <param name="listName"></param>
    /// <param name="eventType"></param>
    /// <param name="details"></param>
    public static void LogEvent(SPWeb web, string listName, SPEventReceiverType eventType, 
      string details)
    {
        SPList logList = Common.EnsureLogList(web, listName);
        SPListItem logItem = logList.Items.Add();
        logItem["Title"] = string.Format("{0} triggered at {1}", eventType, DateTime.Now);
        logItem["Event"] = eventType.ToString();
        logItem["Before"] = Common.IsBeforeEvent(eventType);
        logItem["Date"] = DateTime.Now;
        logItem["Details"] = details;
        logItem.Update();
    }
    

Для каждого класса, наследуемого от SPWebEventReceiver, SPListEventReceiver, SPEmailEventReceiver, SPItemEventReceiver и SPWorkflowEventReceiver, необходимо создать метод ведения журнала для записи свойств события в соответствующий список. Необходимо также изменить все перегруженные методы для вызова данных методов ведения журнала. В следующих фрагментах кода показано, как это сделать для каждого приемника событий.

  • SPWebEventReceiver   Изменение всех перегруженных событий. Вот пример изменения одного из таких событий.

    public override void WebProvisioned(SPWebEventProperties properties)
    {
        this.LogWebEventProperties(properties);
    }
    

    Добавьте метод ведения журнала, как показано в следующем примере кода.

    private void LogWebEventProperties(SPWebEventProperties properties)
    {
        // Specify the log list name.
        string listName = "WebEventLogger";
    
        // Create string builder object.
        StringBuilder sb = new StringBuilder();
    
        // Add properties that do not throw exceptions.
        sb.AppendFormat("Cancel: {0}\n", properties.Cancel);
        sb.AppendFormat("ErrorMessage: {0}\n", properties.ErrorMessage);
        sb.AppendFormat("EventType: {0}\n", properties.EventType);
        sb.AppendFormat("FullUrl: {0}\n", properties.FullUrl);
        sb.AppendFormat("NewServerRelativeUrl: {0}\n", properties.NewServerRelativeUrl);
        sb.AppendFormat("ParentWebId: {0}\n", properties.ParentWebId);
        sb.AppendFormat("ReceiverData: {0}\n", properties.ReceiverData);
        sb.AppendFormat("RedirectUrl: {0}\n", properties.RedirectUrl);
        sb.AppendFormat("ServerRelativeUrl: {0}\n", properties.ServerRelativeUrl);
        sb.AppendFormat("SiteId: {0}\n", properties.SiteId);
        sb.AppendFormat("Status: {0}\n", properties.Status);
        sb.AppendFormat("UserDisplayName: {0}\n", properties.UserDisplayName);
        sb.AppendFormat("UserLoginName: {0}\n", properties.UserLoginName);
        sb.AppendFormat("WebId: {0}\n", properties.WebId);
        sb.AppendFormat("Web: {0}\n", properties.Web);
    
        // Log the event to the list.
        this.EventFiringEnabled = false;
        Common.LogEvent(properties.Web, listName, properties.EventType, sb.ToString());
        this.EventFiringEnabled = true;
    }
    
  • SPListEventReceiver   Изменение всех перегруженных событий. Вот пример изменения одного из таких событий.

    public override void FieldAdded(SPListEventProperties properties)
    {
        this.LogListEventProperties(properties);
    }
    

    Добавьте метод ведения журнала, как показано в следующем примере кода.

    private void LogListEventProperties(SPListEventProperties properties)
    {
        // Specify the log list name.
        string listName = "ListEventLogger";
    
        // Create the string builder object.
        StringBuilder sb = new StringBuilder();
    
        // Add properties that do not throw exceptions.
        sb.AppendFormat("Cancel: {0}\n", properties.Cancel);
        sb.AppendFormat("ErrorMessage: {0}\n", properties.ErrorMessage);
        sb.AppendFormat("EventType: {0}\n", properties.EventType);
        sb.AppendFormat("FeatureId: {0}\n", properties.FeatureId);
        sb.AppendFormat("FieldName: {0}\n", properties.FieldName);
        sb.AppendFormat("FieldXml: {0}\n", properties.FieldXml);
        sb.AppendFormat("ListId: {0}\n", properties.ListId);
        sb.AppendFormat("ListTitle: {0}\n", properties.ListTitle);
        sb.AppendFormat("ReceiverData: {0}\n", properties.ReceiverData);
        sb.AppendFormat("RedirectUrl: {0}\n", properties.RedirectUrl);
        sb.AppendFormat("SiteId: {0}\n", properties.SiteId);
        sb.AppendFormat("Status: {0}\n", properties.Status);
        sb.AppendFormat("TemplateId: {0}\n", properties.TemplateId);
        sb.AppendFormat("UserDisplayName: {0}\n", properties.UserDisplayName);
        sb.AppendFormat("UserLoginName: {0}\n", properties.UserLoginName);
        sb.AppendFormat("WebId: {0}\n", properties.WebId);
        sb.AppendFormat("WebUrl: {0}\n", properties.WebUrl);
        sb.AppendFormat("Web: {0}\n", properties.Web);
        sb.AppendFormat("List: {0}\n", properties.List);
    
        // Add properties that might throw exceptions.
        try
        {
            sb.AppendFormat("Field: {0}\n", properties.Field);
        }
        catch (Exception e)
        {
            sb.AppendFormat("\nError accessing properties.Field:\n\n {0}", e);
        }
    
        // Log the event to the list.
        this.EventFiringEnabled = false;
        Common.LogEvent(properties.Web, listName, properties.EventType, sb.ToString());
        this.EventFiringEnabled = true;
    }
    
  • SPEmailEventReceiver   Изменение события EmailReceived следующим образом.

    public override void EmailReceived(SPList list, SPEmailMessage emailMessage, String receiverData)
    {
        // Specify the log list name.
        string listName = "ListEmailEventLogger";
    
        // Create the string builder object.
        StringBuilder sb = new StringBuilder();
    
        // Add the email message properties.
        sb.AppendFormat("From:\t {0}\n", emailMessage.Sender);
        sb.AppendFormat("To:\t {0}\n", emailMessage.Headers["To"]);
        sb.AppendFormat("Subject:\t {0}\n", emailMessage.Headers["Subject"]);
        sb.AppendFormat("Body:\t {0}\n", emailMessage.PlainTextBody);
    
        // Log the event to the list.
        Common.LogEvent(list.ParentWeb, listName, SPEventReceiverType.EmailReceived, sb.ToString());
    }
    
  • SPItemEventReceiver   Изменение всех перегруженных событий. Вот пример изменения одного из таких событий.

    public override void ItemAttachmentAdded(SPItemEventProperties properties)
    {
        this.LogItemEventProperties(properties);
    }
    Add the log method.
    private void LogItemEventProperties(SPItemEventProperties properties)
    {
        // Specify the log list name.
        string listName = "ListItemEventLogger";
    
        // Create the string builder object.
        StringBuilder sb = new StringBuilder();
    
        // Add properties that do not throw exceptions.
        sb.AppendFormat("AfterUrl: {0}\n", properties.AfterUrl);
        sb.AppendFormat("BeforeUrl: {0}\n", properties.BeforeUrl);
        sb.AppendFormat("Cancel: {0}\n", properties.Cancel);
        sb.AppendFormat("CurrentUserId: {0}\n", properties.CurrentUserId);
        sb.AppendFormat("ErrorMessage: {0}\n", properties.ErrorMessage);
        sb.AppendFormat("EventType: {0}\n", properties.EventType);
        sb.AppendFormat("ListId: {0}\n", properties.ListId);
        sb.AppendFormat("ListItemId: {0}\n", properties.ListItemId);
        sb.AppendFormat("ListTitle: {0}\n", properties.ListTitle);
        sb.AppendFormat("ReceiverData: {0}\n", properties.ReceiverData);
        sb.AppendFormat("RedirectUrl: {0}\n", properties.RedirectUrl);
        sb.AppendFormat("RelativeWebUrl: {0}\n", properties.RelativeWebUrl);
        sb.AppendFormat("SiteId: {0}\n", properties.SiteId);
        sb.AppendFormat("Status: {0}\n", properties.Status);
        sb.AppendFormat("UserDisplayName: {0}\n", properties.UserDisplayName);
        sb.AppendFormat("UserLoginName: {0}\n", properties.UserLoginName);
        sb.AppendFormat("Versionless: {0}\n", properties.Versionless);
        sb.AppendFormat("WebUrl: {0}\n", properties.WebUrl);
        sb.AppendFormat("Web: {0}\n", properties.Web);
        sb.AppendFormat("Zone: {0}\n", properties.Zone);
        sb.AppendFormat("Context: {0}\n", properties.Context);
    
        // Add properties that might throw exceptions.
        try
        {
            sb.AppendFormat("ListItem: {0}\n", properties.ListItem);
        }
        catch (Exception e)
        {
            sb.AppendFormat("\nError accessing properties.ListItem:\n\n {0}", e);
        }
    
        // Add AfterProperties property bag.
        sb.AppendFormat("AfterProperties: {0}\n", properties.AfterProperties);
        IEnumerator afterProperties = properties.AfterProperties.GetEnumerator();
        int i = 0;
        while (afterProperties.MoveNext())
        {
            DictionaryEntry entry = (DictionaryEntry)afterProperties.Current;
            sb.AppendFormat("[{0}]: ({1}, {2})\n", i++, entry.Key, entry.Value);
        }
        sb.AppendFormat("AfterProperties.ChangedProperties: {0}\n", 
          properties.AfterProperties.ChangedProperties);
        IEnumerator changedAfterProperties = 
          properties.AfterProperties.ChangedProperties.GetEnumerator();
        i = 0;
        while (changedAfterProperties.MoveNext())
        {
            DictionaryEntry entry = (DictionaryEntry)changedAfterProperties.Current;
            sb.AppendFormat("[{0}]: ({1}, {2})\n", i++, entry.Key, entry.Value);
        }
    
        // Add BeforeProperties property bag.
        sb.AppendFormat("BeforeProperties: {0}\n", properties.BeforeProperties);
        IEnumerator beforeProperties = properties.BeforeProperties.GetEnumerator();
        i = 0;
        while (beforeProperties.MoveNext())
        {
            DictionaryEntry entry = (DictionaryEntry)beforeProperties.Current;
            sb.AppendFormat("[{0}]: ({1}, {2})\n", i++, entry.Key, entry.Value);
        }
        sb.AppendFormat("BeforeProperties.ChangedProperties: {0}\n", 
          properties.BeforeProperties.ChangedProperties);
        IEnumerator changedBeforeProperties = 
          properties.BeforeProperties.ChangedProperties.GetEnumerator();
        i = 0;
        while (changedBeforeProperties.MoveNext())
        {
            DictionaryEntry entry = (DictionaryEntry)changedBeforeProperties.Current;
            sb.AppendFormat("[{0}]: ({1}, {2})\n", i++, entry.Key, entry.Value);
        }
    
        // Log the event to the list.
        this.EventFiringEnabled = false;
        Common.LogEvent(properties.Web, listName, properties.EventType, sb.ToString());
        this.EventFiringEnabled = true;
    }
    
  • SPWorkflowEventReceiver   Изменение всех перегруженных событий. Вот пример изменения одного из таких событий.

    public override void WorkflowStarting(SPWorkflowEventProperties properties)
    {
        this.LogWorkflowEventProperties(properties);
    }
    

    Добавьте метод ведения журнала, как показано в следующем примере кода.

    private void LogWorkflowEventProperties(SPWorkflowEventProperties properties)
    {
        // Specify the log list name.
        string listName = "WorkflowEventLogger";
    
        // Create the string builder object.
        StringBuilder sb = new StringBuilder();
    
        // Add properties that do not throw exceptions.
        sb.AppendFormat("AssociationData: {0}\n", properties.AssociationData);
        sb.AppendFormat("Cancel: {0}\n", properties.Cancel);
        sb.AppendFormat("CompletionType: {0}\n", properties.CompletionType);
        sb.AppendFormat("ErrorMessage: {0}\n", properties.ErrorMessage);
        sb.AppendFormat("EventType: {0}\n", properties.EventType);
        sb.AppendFormat("InitiationData: {0}\n", properties.InitiationData);
        sb.AppendFormat("InstanceId: {0}\n", properties.InstanceId);
        sb.AppendFormat("PostponedEvent: {0}\n", properties.PostponedEvent);
        sb.AppendFormat("ReceiverData: {0}\n", properties.ReceiverData);
        sb.AppendFormat("RedirectUrl: {0}\n", properties.RedirectUrl);
        sb.AppendFormat("RelativeWebUrl: {0}\n", properties.RelativeWebUrl);
        sb.AppendFormat("SiteId: {0}\n", properties.SiteId);
        sb.AppendFormat("Status: {0}\n", properties.Status);
        sb.AppendFormat("TerminatedByUserId: {0}\n", properties.TerminatedByUserId);
        sb.AppendFormat("WebUrl: {0}\n", properties.WebUrl);
    
        // Get activation properties.
        SPWorkflowActivationProperties activationProperties = properties.ActivationProperties;
        if (activationProperties != null)
        {
            sb.AppendFormat("ActivationProperties.AssociationData: {0}\n", 
              activationProperties.AssociationData);
            sb.AppendFormat("ActivationProperties.HistoryListId: {0}\n", 
              activationProperties.HistoryListId);
            sb.AppendFormat("ActivationProperties.HistoryListUrl: {0}\n", 
              activationProperties.HistoryListUrl);
            sb.AppendFormat("ActivationProperties.InitiationData: {0}\n", 
              activationProperties.InitiationData);
            sb.AppendFormat("ActivationProperties.ItemId: {0}\n", 
              activationProperties.ItemId);
            sb.AppendFormat("ActivationProperties.ItemUrl: {0}\n", 
              activationProperties.ItemUrl);
            sb.AppendFormat("ActivationProperties.ListId: {0}\n", 
              activationProperties.ListId);
            sb.AppendFormat("ActivationProperties.ListUrl: {0}\n", 
              activationProperties.ListUrl);
            sb.AppendFormat("ActivationProperties.Originator: {0}\n", 
              activationProperties.Originator);
            sb.AppendFormat("ActivationProperties.OriginatorEmail: {0}\n", 
              activationProperties.OriginatorEmail);
            sb.AppendFormat("ActivationProperties.SiteId: {0}\n", 
              activationProperties.SiteId);
            sb.AppendFormat("ActivationProperties.SiteUrl: {0}\n", 
              activationProperties.SiteUrl);
            sb.AppendFormat("ActivationProperties.TaskListId: {0}\n", 
              activationProperties.TaskListId);
            sb.AppendFormat("ActivationProperties.TaskListUrl: {0}\n", 
              activationProperties.TaskListUrl);
            sb.AppendFormat("ActivationProperties.TemplateName: {0}\n", 
              activationProperties.TemplateName);
            sb.AppendFormat("ActivationProperties.WebId: {0}\n", 
              activationProperties.WebId);
            sb.AppendFormat("ActivationProperties.WebUrl: {0}\n", 
              activationProperties.WebUrl);
            sb.AppendFormat("ActivationProperties.WorkflowId: {0}\n", 
              activationProperties.WorkflowId);
    
            // Add properties that might throw exceptions.
            try
            {
                sb.AppendFormat("ActivationProperties.Context: {0}\n", 
                  activationProperties.Context);
                sb.AppendFormat("ActivationProperties.HistoryList: {0}\n", 
                  activationProperties.HistoryList);
                sb.AppendFormat("ActivationProperties.Item: {0}\n", 
                  activationProperties.Item);
                sb.AppendFormat("ActivationProperties.List: {0}\n", 
                  activationProperties.List);
                sb.AppendFormat("ActivationProperties.OriginatorUser: {0}\n", 
                  activationProperties.OriginatorUser);
                sb.AppendFormat("ActivationProperties.Site: {0}\n", 
                  activationProperties.Site);
                sb.AppendFormat("ActivationProperties.TaskList: {0}\n", 
                  activationProperties.TaskList);
                sb.AppendFormat("ActivationProperties.Web: {0}\n", 
                  activationProperties.Web);
                sb.AppendFormat("ActivationProperties.Workflow: {0}\n", 
                  activationProperties.Workflow);
            }
            catch (Exception e)
            {
                sb.AppendFormat("\nError accessing ActivationProperties property:\n\n {0}", e);
            }
        }
        else
        {
            sb.AppendFormat("ActivationProperties is null\n");
        }
    
        // Log the event to the list.
        this.EventFiringEnabled = false;
        Common.LogEvent(properties.ActivationProperties.Web, listName, 
          properties.EventType, sb.ToString());
        this.EventFiringEnabled = true;
    }
    

Заключение

Теперь вам известны все сведения о модели событий SharePoint 2010, создании приемников событий и развертывании проектов, реализующих приемники событий. (Вы также знаете, сколько времени можно сэкономить, используя шаблон проекта приемника событий в Visual Studio 2010.) И вы ознакомились с несколькими практическими примерами, которые можно адаптировать под собственные задачи. Для получения дополнительных сведений о модели событий SharePoint познакомьтесь поближе с пакетом SDK SharePoint 2010 и начните со статьи События в SharePoint Foundation 2010.

Дополнительные материалы

Дополнительные сведения см. в следующих документах: