1. Список задач для проверки навыков.
  2. Создание задачи таймера (этот пост).
  3. Использование подходящих классов.
  4. Передача команд задаче таймера.
  5. Взаимодействие веб-фронтэнда с задачами таймера.

Ранее я приводил список задач для проверки навыков программирования для SharePoint. Сегодня напишу о решении четвертой задачи про задачу таймера для очистки библиотек документов от пустых папок.

Задача

Создать задачу таймера (Timer Job), которая буде находить в библиотеках документов пустые папки, в которых нет файлов и которые содержат пустые папки, и удалять их.

Класс задачи таймера

Чтобы создать задачу таймера необходимо создать класс, унаследованный от Microsoft.SharePoint.Administration.SPJobDefinition. Этот класс недоступен в sandbox, поэтому вам нужен farm solution.

В этом классе необходимо переопределить метод Execute и конструктор с параметрами.

public TimerJob(SPWebApplication webApp)
    : base(Constants.TimerJobName, webApp, null,
           SPJobLockType.ContentDatabase)
{
    this.Title = "Folder cleanup job";
}

Первым параметром конструктора передается строка, которая будет отличать задачу таймера от других. Title задает отображаемое в админке имя задачи таймера.

Так как ферма SharePoint может состоять из нескольких серверов, то появляется интересный вопрос – где и сколько раз будет запускаться задача таймера. Особое внимание надо уделить параметру SPJobLockType. Детальное описание по ссылке. Возможные варианты:

  • SPJobLockType.ContentDatabase  - задача таймера запускается для каждой контентной базы данных родительского приложения.
  • SPJobLockType.Job – задача таймера выполняется один раз на всю ферму. В данном режиме учитывается параметр SPServer конструктора, позволяющий указать конкретный сервер для запуска Timer Job.
  • SPJobLockType.None – запускается на каждом сервере в ферме, где развернут родительский сервис. Очень полезно если вам надо запустить некоторый некоторый процесс на каждом сервере в ферме.
Добавление и удаление задачи таймера

Для добавления задачи таймера удобно использовать фичу уровня фермы или веб-приложения с флагом Activate On Default равным true. При разветрывании решения с такой фичей она автоматически активируется в указанной области действия.

Код feature receiver:

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
    var webApp = properties.Feature.Parent as SPWebApplication;
    var job = new TimerJob(webApp);
    job.Schedule = new SPHourlySchedule()
    {
        BeginMinute = 0,
        EndMinute = 59
    };

    job.Update();
}


public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
    var webApp = properties.Feature.Parent as SPWebApplication;
    var job = webApp.JobDefinitions.GetValue<TimerJob>(Constants.TimerJobName);
    job.Delete();
}

Класс SPHourlySchedule является наследником SPSchedule и позволяет задавать расписание запуска задачи таймера.

Важно. Если вы попытаетесь добавить задачу таймера в фиче уровня Site или Web, то при деплое из Visual Studio оно сработает, а при попытке активировать фичу из веб-интерфейса упадет. Это новое ограничение SharePoint 2010, не позволяющее делать Update для классов наследников SPPersistedObject из контекста веб-приложения.

Если же у вас есть унаследованный код, который вы не можете поправить, то вам скорее всего пригодится эта статья.

Обработка элементов списков

Теперь перейдем к основной функции задачи таймера – очистка папок.

public override void Execute(Guid targetInstanceId)
{
    SPWebApplication webApplication = this.Parent as SPWebApplication;
    SPContentDatabase contentDb = webApplication.ContentDatabases[targetInstanceId];

    ProcessDatabase(contentDb);
}

Так как при создании Timer Job был указан SPJobLockType.ContentDatabase, то в качестве параметра targetInstanceId будет ID базы данных контента.

Далее циклы по SPSite и SPWeb:

private void ProcessDatabase(SPContentDatabase contentDb)
{
    foreach (SPSite site in contentDb.Sites)
    {
        try
        {
            ProcessSite(site);
        }
        finally
        {
            site.Dispose();
        }
    }
}

private void ProcessSite(SPSite site)
{
    foreach (SPWeb web in site.AllWebs)
    {
        try
        {
            ProcessWeb(web);
        }
        finally
        {
            web.Dispose();
        }
    }
}

Вот такие конструкции необходимо использовать чтобы пройтись по всем SPSite и SPWeb в базе данных. Если не напишите Dispose в циклах, то на сервере очень быстро закончится память.

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

Для того чтобы обезопасить себя и ваших пользователей необходимо использовать утилиту SPDisposeCheck. Она вам подскажет где надо совободить объекты.

Ну и наконец удаление папок:

private void ProcessWeb(SPWeb web)
{
    foreach (var lib in web.Lists.OfType<SPDocumentLibrary>())
    {
        if (!lib.Hidden)
        {
            DeleteEmptyFolders(lib.RootFolder.SubFolders);
        }
    }
}

private void DeleteEmptyFolders(SPFolderCollection folders)
{
    foreach (var folder in folders.OfType<SPFolder>().ToList())
    {
        DeleteEmptyFolder(folder);
    }

}

private void DeleteEmptyFolder(SPFolder folder)
{
    if (folder.Item != null)
    {
        DeleteEmptyFolders(folder.SubFolders);

        if (folder.ItemCount == 0)
        {
            folder.Delete();
        }
    }
}

Не копипастите код из статьи до того как прочитаете следующую часть.

Теги : SharePoint