Перейти к содержимому

SDK (Portal.TimerJob.Sdk)

C# SDK для разработки Timer Job.

Настройка SDK: Extension SDK с korport.ru/developers.

using Portal.TimerJob.Sdk;
public sealed class ContosoHeartbeatTimerJob : ITimerJob
{
public Task<object?> ExecuteAsync(
TimerJobContext context,
ITimerJobApi api,
CancellationToken cancellationToken = default)
{
var message = context.Config.ValueKind == JsonValueKind.Object
&& context.Config.TryGetProperty("message", out var msgEl)
? msgEl.GetString() ?? "heartbeat"
: "heartbeat";
api.Log.Info("Contoso heartbeat:", message,
"job=", context.JobType,
"timerJobId=", context.TimerJobId,
"scheduledAt=", context.ScheduledAt);
return Task.FromResult<object?>(new { ok = true, message });
}
}

В отличие от Event Receiver, Timer Job не наследует пользователя события. Операции со списками и библиотеками всегда выполняются от системной учётки portal-system (администратор портала, полный доступ).

ПолеТипОписание
TimerJobIdGuidID экземпляра из timer_jobs
JobTypestringmanifest.id (например contoso.heartbeat)
ConfigJsonElementПараметры экземпляра из админки (timer_jobs.config)
ScheduledAtDateTimeOffsetВремя планового (или ручного) запуска

При постановке в очередь Worker получает JSON:

{
"source": "timerJob",
"timerJobId": "uuid-экземпляра",
"jobType": "contoso.heartbeat",
"scheduledAt": "2026-07-06T09:30:00.0000000Z",
"manual": false,
"config": {
"message": "hello"
}
}
  • manual: true — ручной запуск из админки (POST /timer-jobs/{id}/run)
  • manual: false — срабатывание по расписанию
ОбъектМетоды
LogInfo / Warn / Error (params object[])
JobsEnqueueAsync(serviceKey, jobType, jobPayload?)
ListsGetListItemAsync / NewListItemAsync / QueryListItemsAsync (PortalListItem); также GetAsync, GetFieldsAsync, GetItemAsync, GetItemVersionsAsync, GetItemVersionAsync, GetItemsAsync, QueryItemsAsync, CreateItemAsync, UpdateItemAsync, DeleteItemAsync. Нет «текущего элемента» как у WebPart ctx.Host / ER context.GetListItemAsync — id списка и фильтр берите из context.Config
LibrariesGetAsync, GetFilesAsync, QueryFilesAsync
UsersGetProfileAsync, GetOrgChainAsync, GetOrgChildrenAsync, ListActiveDirectoryUsersAsync, AvatarUrl
PermissionsForListAsync / ForListItemAsync / GrantToGroupAsync / RevokeAsync / …
KedoCreatePackageAsync / GetPackageStatusAsync
api.Log.Info("Старт обработки", context.JobType);
api.Log.Warn("Пропущена запись", itemId);
api.Log.Error("Ошибка синхронизации:", ex.Message);

Сообщения попадают в лог Worker с префиксом [timer-job]. Просмотр:

Окно терминала
docker compose logs -f worker

Timer Job может поставить другое задание в portal_jobs:

var result = await api.Jobs.EnqueueAsync(
serviceKey: "audit",
jobType: "audit.logEvent",
jobPayload: new
{
action = "timer.heartbeat",
timerJobId = context.TimerJobId,
message = "tick",
},
cancellationToken);
// result: { id = Guid задания, status = "pending" }

В payload автоматически добавляются:

{
"triggeredBy": "timerJob",
"sourceTimerJobId": "uuid-экземпляра"
}

Поля из jobPayload объединяются с этими служебными полями.

МетодОписание
GetListItemAsync / NewListItemAsync / QueryListItemsAsyncSharePoint-подобный PortalListItem: item["title"], CreateAsync / UpdateAsync. Частые фильтры: list-queries
GetAsyncМетаданные списка
GetFieldsAsyncСхема полей
GetItemAsyncОдин элемент (сырой JSON)
GetItemVersionsAsyncИстория версий (fieldValues в каждой версии)
GetItemVersionAsyncОдна версия по номеру
GetItemsAsync / QueryItemsAsyncВыборка элементов (сырой JSON)
CreateItemAsync / UpdateItemAsyncНизкоуровневое создание / изменение

Права — всегда от системной учётки portal-system.

МетодОписание
GetAsyncМетаданные библиотеки
GetFilesAsync / QueryFilesAsyncВыборка файлов

Также от имени portal-system.

В config экземпляра достаточно параметров задания (например settingsListId). Отдельный пользователь для выполнения не задаётся.

{
"settingsListId": "uuid-списка"
}

Пример — найти и обновить элемент настроек (PortalListItem):

using Portal.Contracts.Lists;
using Portal.TimerJob.Sdk;
public async Task<object?> ExecuteAsync(TimerJobContext context, ITimerJobApi api, CancellationToken ct)
{
var listId = Guid.Parse(context.Config.GetProperty("settingsListId").GetString()!);
var items = await api.Lists.QueryListItemsAsync(listId,
ListItemQueryBuilder.ForList(listId)
.WhereField("title", f => f.Eq("GlobalSettings"))
.Take(1)
.Build(), ct);
var item = items.First();
var oldValue = item.GetString("config_value");
item["config_value"] = "новое значение";
await item.UpdateAsync(ct);
api.Log.Info("Updated settings", oldValue, "->", item.GetString("config_value"));
return new { ok = true, oldValue };
}

Пример — один элемент по ref:

var item = await api.Lists.GetListItemAsync(listId, "12", ct); // PortalListItem
var title = item.GetString("title"); // string?
item["status"] = "Обработано";
await item.UpdateAsync(ct);

Типы геттеров (GetDate, GetLookup, …): Кастомная форма §1.

Пример — выборка PDF из библиотеки:

using Portal.Contracts.Libraries;
using Portal.TimerJob.Sdk;
var libraryId = context.Config.GetProperty("libraryId").GetString()!;
var files = await api.Libraries.QueryFilesAsync(libraryId,
LibraryFileQueryBuilder.ForLibrary(Guid.Parse(libraryId))
.Where(b => b.Eq("item_type", "file").Eq("mime_type", "application/pdf"))
.Take(100)
.Build(), ct);

Низкоуровневый UUID-путь и версии: Значения полей списков.

Типичный паттерн для тяжёлой работы: Timer Job по расписанию ставит пакетное задание (api.Jobs.EnqueueAsync), а обработка выполняется асинхронно в очереди.

public async Task<object?> ExecuteAsync(
TimerJobContext context,
ITimerJobApi api,
CancellationToken cancellationToken)
{
for (var i = 0; i < 100; i++)
{
cancellationToken.ThrowIfCancellationRequested();
await ProcessBatchAsync(i, cancellationToken);
}
return new { processed = 100 };
}

При остановке Worker активные задания получают CancellationToken от хоста.

ExecuteAsync возвращает object? — результат сохраняется в portal_jobs (поле результата задания). Используйте для диагностики:

return new { ok = true, processed = count, skipped = skipped };

При необработанном исключении задание переходит в failed (до 3 попыток с интервалом 30 с). См. Job-модули.

public async Task<object?> ExecuteAsync(TimerJobContext context, ITimerJobApi api, CancellationToken ct)
{
try
{
await DoWorkAsync(context, ct);
return new { ok = true };
}
catch (Exception ex)
{
api.Log.Error("Timer job failed:", ex.Message);
throw; // задание будет повторено / помечено failed
}
}

Для «мягких» ошибок (пропуск итерации без fail) верните объект с ok: false и не бросайте исключение.

Окно терминала
dotnet new install ./portal-sdk-1.0.0/templates/portal-timerjob
dotnet new portal-timerjob -n MyTimerJob -o ./my-timerjob

Пример в SDK ZIP: examples/heartbeat. См. Extension SDK.

ДоступноНедоступно напрямую
api.Logapi.Nodes
api.Jobs.EnqueueAsyncHTTP к внешним API (реализуйте в C# сами)
api.Lists.* / api.Libraries.* (всегда от portal-system)

Если нужна реакция на изменение элемента в реальном времени — используйте Event Receiver вместо опроса списка по расписанию.