Поделиться через


Создание удаленных заданий таймера в SharePoint

Создавайте удаленные задания таймера, чтобы управлять SharePoint, отслеживая данные SharePoint и выполняя с ними действия. Удаленные задания таймера не выполняются на вашем сервере SharePoint. Они выполняются по графику на другом сервере.

Например, с помощью заданий таймера можно:

  • выполнять задачи по управлению (например, отображать сообщение на сайте в случае невыполнения определенных условий или принудительно применять политики хранения);
  • выполнять запланированные процессы, требующие значительных ресурсов процессора.

До начала работы

Чтобы приступить к работе, скачайте пример надстройки Core.TimerJobs.Samples из проекта Office 365 Developer Patterns and Practices (Шаблоны и методики разработки для Office 365) на портале GitHub.

Примечание.

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

Чтобы начать использовать решение Core.TimerJobs.Samples, нужно выбрать запускаемый проект, например проект SimpleJob. Для этого откройте контекстное меню надстройки Core.TimerJobs.Samples.SimpleJob, щелкнув по ней правой кнопкой мыши, а затем выберите Назначить запускаемым проектом.

Примечание.

Чтобы после создания нового проекта в Visual Studio приступить к созданию нового удаленного задания таймера, добавьте в свой проект пакет NuGet под названием OfficeDevPnP.Core. В Visual Studio выберите СРЕДСТВА>Диспетчер пакетов NuGet>Управление пакетами NuGet для решения.

Планирование выполнения удаленного задания таймера

Можно запланировать однократное или повторяющееся выполнение задания таймера. Чтобы запланировать выполнение удаленного задания таймера в рабочей среде, нужно компилировать код в EXE-файл и запустить его с помощью планировщика заданий Windows или веб-задания Microsoft Azure. Дополнительные сведения см. в статье Параметры развертывания задания таймера.

Использование надстройки Core.TimerJobs.Samples.SimpleJob

В надстройке Core.TimerJobs.Samples.SimpleJob оператор Main файла Program.cs выполняет такие действия:

  1. Создает объект SimpleJob, который наследует от базового класса OfficeDevPnP.Core.Framework.TimerJobs.TimerJob.

  2. С помощью оператора TimerJob.UseOffice365Authentication устанавливает учетные данные пользователя Office 365, используемые при выполнении задания таймера. Учетные данные пользователя должны обеспечивать необходимый доступ к семействам веб-сайтов. Подробнее об этом в статье Проверка подлинности.

  3. С помощью оператора TimerJob.AddSite добавляет сайты, на которых задание таймера должно выполнить задачи. При необходимости вы можете повторить оператор TimerJob.AddSite, чтобы добавить несколько сайтов, или добавить все сайты по определенному URL-адресу с помощью подстановочного знака *. Например, если указать http://contoso.sharepoint.com/sites/*, задания таймера будут выполнены на всех сайтах по управляемому пути sites (сайты).

  4. Печатает сведения о задании таймера и выполняет его с помощью оператора PrintJobSettingsAndRunJob.

     static void Main(string[] args)
             {
                 SimpleJob simpleJob = new SimpleJob();
    
                 // The user credentials must have access to the site collections you supply.
                 simpleJob.UseOffice365Authentication(User, Password);
    
                 // Use the following code if you are using SharePoint Server on-premises. 
                 //simpleJob.UseNetworkCredentialsAuthentication(User, Password, Domain);
    
                 // Add one or more sites that the timer job should work with.
                 simpleJob.AddSite("https://contoso.sharepoint.com/sites/dev");
    
                 // Prints timer job information and then calls Run().
                 PrintJobSettingsAndRunJob(simpleJob);
             }
    

После создания экземпляра объекта SimpleJob конструктор SimpleJob:

  1. Вызывает конструктор базового класса TimerJob.

  2. Назначает обработчик событий SimpleJob_TimerJobRun для обработки событий TimerJobRun . SimpleJob_TimerJobRun выполняется при вызове TimerJob.Run, который более подробно описан далее в этой статье.

     public SimpleJob() : base("SimpleJob")
             {
                 TimerJobRun += SimpleJob_TimerJobRun;
             }
    

Во время работы оператора PrintJobSettingsAndRunJob вывод из оператора TimerJob записывается в окно консоли, а затем происходит вызов оператора TimerJob.Run.

 private static void PrintJobSettingsAndRunJob(TimerJob job)
        {
            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.WriteLine("************************************************");
            Console.WriteLine("Job name: {0}", job.Name);
            Console.WriteLine("Job version: {0}", job.Version);
            Console.WriteLine("Use threading: {0}", job.UseThreading);
            Console.WriteLine("Maximum threads: {0}", job.MaximumThreads);
            Console.WriteLine("Expand sub sites: {0}", job.ExpandSubSites);
            Console.WriteLine("Authentication type: {0}", job.AuthenticationType);
            Console.WriteLine("Manage state: {0}", job.ManageState);
            Console.WriteLine("SharePoint version: {0}", job.SharePointVersion);
            Console.WriteLine("************************************************");
            Console.ForegroundColor = ConsoleColor.Gray;

            // Run job.
            job.Run();
        }

Оператор TimerJob.Run порождает события оператора TimerJobRun. TimerJob.Run вызывает SimpleJob_TimerJobRun в SimpleJob.cs, который вы задали в качестве обработчика событий для обработки событий TimerJobRun в конструкторе SimpleJob.

В SimpleJob_TimerJobRun можно добавить пользовательский код, который задание таймера будет выполнять при выполнении задания таймера. SimpleJob_TimerJobRun запускает пользовательский код на сайтах, добавленных с помощью TimerJob.AddSite в Program.cs.

В этом примере кода SimpleJob_TimerJobRun использует ClientContext из TimerJob для записи заголовка сайта в окно консоли. Если с помощью TimerJob.AddSite было добавлено несколько сайтов, для каждого сайта вызывается SimpleJob_TimerJobRun.

 void SimpleJob_TimerJobRun(object sender, TimerJobRunEventArgs e)
        {
            e.WebClientContext.Load(e.WebClientContext.Web, p => p.Title);
            e.WebClientContext.ExecuteQueryRetry();
            Console.WriteLine("Site {0} has title {1}", e.Url, e.WebClientContext.Web.Title);
        }

Пример. Задание таймера для применения политик хранения к типам контента

В проекте Core.TimerJobs.Samples.ContentTypeRetentionEnforcementJob показано, как можно использовать задания таймера для применения политик хранения к типам контента. С помощью элемента ContentTypeRetentionPolicyPeriod в app.config укажите:

  • ключ — идентификатор типа контента, на который распространяется период хранения.
  • значение — период хранения в днях. Период хранения применяется ко всем элементам списка, созданным с помощью типа контента, указанного в ключе.
<ContentTypeRetentionPolicyPeriod>
    <!-- Key is the content type ID, and value is the retention period in days -->
    <!-- Specifies an audio content type should be kept for 183 days -->
    <add key="0x0101009148F5A04DDD49cbA7127AADA5FB792B006973ACD696DC4858A76371B2FB2F439A" value="183" />
    <!-- Specifies a document content type should be kept for 365 days -->   
    <add key="0x0101" value="365" />
</ContentTypeRetentionPolicyPeriod>

ContentTypeRetentionEnforcementJob_TimerJobRun задается в качестве обработчика событий для события TimerJobRun . При вызове TimerJob.Run в Файле Program.cs ContentTypeRetentionEnforcementJob_TimerJobRun выполняет следующие действия на каждом сайте, добавленном с помощью TimerJob.AddSite в Program.cs:

  1. Получает все библиотеки документов на сайте.
  2. Считывает сведения, указанные в ContentTypeRetentionPolicyPeriod файла app.config, о конфигурации каждой библиотеки документов на сайте. Вызывает оператор ApplyRetentionPolicy для каждой пары идентификатора типа контента и периода хранения, прочитанной в файле app.config.
 void ContentTypeRetentionEnforcementJob_TimerJobRun(object sender, TimerJobRunEventArgs e)
        {
            try
            {
                Log.Info("ContentTypeRetentionEnforcementJob", "Scanning web {0}", e.Url);

                // Get all document libraries. Lists are excluded.
                var documentLibraries = GetAllDocumentLibrariesInWeb(e.WebClientContext, e.WebClientContext.Web);

                // Iterate through all document libraries.
                foreach (var documentLibrary in documentLibraries)
                {
                    Log.Info("ContentTypeRetentionEnforcementJob", "Scanning library {0}", documentLibrary.Title);

                    // Iterate through configured content type retention policies specified in app.config.
                    foreach (var contentTypeName in configContentTypeRetentionPolicyPeriods.Keys)
                    {
                        var retentionPeriods = configContentTypeRetentionPolicyPeriods.GetValues(contentTypeName as string);
                        if (retentionPeriods != null)
                        {
                            var retentionPeriod = int.Parse(retentionPeriods[0]);
                            ApplyRetentionPolicy(e.WebClientContext, documentLibrary, contentTypeName, retentionPeriod);
                        }
                    }
                }
            }
            catch(Exception ex)
            {
                Log.Error("ContentTypeRetentionEnforcementJob", "Exception processing site {0}. Exception is {1}", e.Url, ex.Message);
            }
        }

Метод ApplyRetentionPolicy применяет действия настраиваемой политики хранения. Для этого он:

  1. Вычисляет validationDate. Метод ApplyRetentionPolicy применяет действия политики хранения к документам, измененным до validationDate. Затем validationDate переводится в формат даты CAML и назначается для camlDate.

  2. Выполняет CAML-запрос, чтобы отфильтровать документы в библиотеке документов на основе идентификатора типа контента, указанного в файле app.config, и даты Modified By, значение которой не превышает camlDate.

  3. Применяет настраиваемые действия хранения к каждому документу в списке с помощью пользовательского кода.

private void ApplyRetentionPolicy(ClientContext clientContext, List documentLibrary, object contentTypeId, int retentionPeriodDays)
        {
            // Calculate validation date. You need to enforce the retention policy on any document modified before validation date.
            var validationDate = DateTime.Now.AddDays(-retentionPeriodDays);
            var camlDate = validationDate.ToString("yyyy-MM-ddTHH:mm:ssZ");

            // Get old documents in the library that match the content type.
            if (documentLibrary.ItemCount > 0)
            {
                var camlQuery = new CamlQuery();
                
                camlQuery.ViewXml = String.Format(
                    @"<View>
                        <Query>
                            <Where><And>
                                <BeginsWith><FieldRef Name='ContentTypeId'/><Value Type='ContentTypeId'>{0}</Value></BeginsWith>
                                <Lt><FieldRef Name='Modified' /><Value Type='DateTime'>{1}</Value></Lt>
                            </And></Where>
                        </Query>
                    </View>", contentTypeId, camlDate);

                var listItems = documentLibrary.GetItems(camlQuery);
                clientContext.Load(listItems,
                    items => items.Include(
                        item => item.Id,
                        item => item.DisplayName,
                        item => item.ContentType));

                clientContext.ExecuteQueryRetry(); 

                foreach (var listItem in listItems)
                {
                    Log.Info("ContentTypeRetentionEnforcementJob", "Document '{0}' has been modified earlier than {1}. Retention policy will be applied.", listItem.DisplayName, validationDate);
                    Console.WriteLine("Document '{0}' has been modified earlier than {1}. Retention policy will be applied.", listItem.DisplayName, validationDate);
                    
                    // Apply your custom retention actions here. For example, archiving documents, or starting a disposition workflow.
                }
            }
        }

Пример. Задание таймера по управлению

В проекте Core.TimerJobs.Samples.GovernanceJob задания таймера используются для того, чтобы убедиться, что семейству веб-сайтов назначено два администратора. В противном случае отображается уведомление на сайте.

SiteGovernanceJob_TimerJobRun задается в качестве обработчика событий для события TimerJobRun . При вызове TimerJob.Run в Файле Program.cs SiteGovernanceJob_TimerJobRun выполняет следующие действия для каждого семейства веб-сайтов, добавленного с помощью TimerJob.AddSite в Program.cs:

  1. Определяет количество администраторов, назначенных для семейства веб-сайтов с помощью метода расширения GetAdministrators, который является частью OfficeDevPnP.Core.

  2. Отправляет файл JavaScript в список SiteAssets или библиотеки стилей с помощью оператора UploadFile, который является частью OfficeDevPnP.Core.

  3. Если на сайте меньше двух администраторов, оператор AddJSLink добавляет соответствующее уведомление на сайт с помощью JavaScript. Подробнее можно узнать в статье Настройка пользовательского интерфейса сайта SharePoint с помощью JavaScript.

  4. Если на сайте не менее двух администраторов, уведомление удаляется с помощью оператора DeleteJsLink.

void SiteGovernanceJob_TimerJobRun(object o, TimerJobRunEventArgs e)
        {
            try
            {
                string library = "";

                // Get the number of administrators.
                var admins = e.WebClientContext.Web.GetAdministrators();

                Log.Info("SiteGovernanceJob", "ThreadID = {2} | Site {0} has {1} administrators.", e.Url, admins.Count, Thread.CurrentThread.ManagedThreadId);

                // Get a reference to the list.
                library = "SiteAssets";
                List list = e.WebClientContext.Web.GetListByUrl(library);

                if (!e.GetProperty("ScriptFileVersion").Equals("1.0", StringComparison.InvariantCultureIgnoreCase))
                {
                    if (list == null)
                    {
                        // Get a reference to the list.
                        library = "Style%20Library";
                        list = e.WebClientContext.Web.GetListByUrl(library);
                    }

                    if (list != null)
                    {
                        // Upload js file to list.
                        list.RootFolder.UploadFile("sitegovernance.js", "sitegovernance.js", true);

                        e.SetProperty("ScriptFileVersion", "1.0");
                    }
                }

                if (admins.Count < 2)
                {
                    // Show notification message because you need at least two site collection administrators.
                    e.WebClientContext.Site.AddJsLink(SiteGovernanceJobKey, BuildJavaScriptUrl(e.Url, library));
                    Console.WriteLine("Site {0} marked as incompliant!", e.Url);
                    e.SetProperty("SiteCompliant", "false");
                }
                else
                {
                    // Remove the notification message because two administrators are assigned.
                    e.WebClientContext.Site.DeleteJsLink(SiteGovernanceJobKey);
                    Console.WriteLine("Site {0} is compliant", e.Url);
                    e.SetProperty("SiteCompliant", "true");
                }

                e.CurrentRunSuccessful = true;
                e.DeleteProperty("LastError");
            }
            catch(Exception ex)
            {
                Log.Error("SiteGovernanceJob", "Error while processing site {0}. Error = {1}", e.Url, ex.Message);
                e.CurrentRunSuccessful = false;
                e.SetProperty("LastError", ex.Message);
            }
        }

См. также