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

Значения полей списков: чтение и обновление

Руководство по программной работе с field_values элементов списка — REST API, JavaScript WebPart, C# SDK (WebPart, Event Receiver, Timer Job).

См. также: API списков (эндпоинты, типы полей, фильтрация).

  1. Схема полейGET /lists/{listId}/fields или GetFieldsAsync / getFields. У каждого поля есть id (UUID) и internal_name (title, status…).

  2. Значения элемента — объект field_values, ключи только UUID полей (не internal_name):

{
"id": 12,
"guid": "uuid-элемента",
"field_values": {
"uuid-поля-title": "Заголовок",
"uuid-поля-status": "Новая"
}
}
  1. id элемента в ответе API — публичный номер (12). Для GetItem / PATCH / UpdateItemAsync можно передать номер или guid.

  2. Обновление — merge: в fieldValues передаёте только изменённые поля, остальные не обнуляются. После merge сервер валидирует все обязательные поля схемы (is_required в GET /fields).

  3. Фильтр (filter, QueryItemsAsync) — в column допустим internal_name ("title"). В field_values при чтении/записи — только UUID.

  4. Версии — если у списка включено версионирование, у каждой версии есть полный снимок fieldValues (те же UUID-ключи). SDK: GetItemVersionsAsync / GetItemVersionAsync.

ТипЗначение в fieldValues / field_values
text, multiline_text, choiceстрока
numberчисло
date"YYYY-MM-DD"
datetimeISO 8601
booleantrue / false
personмассив `[{ “principalType”: “user"
lookup{ "itemId": "uuid", "display": "Текст" }
link{ "url": "https://...", "description": "..." }

# Схема полей
GET /api/v1/lists/{listId}/fields
# Найти элемент по заголовку
GET /api/v1/lists/{listId}/items?filter={"logic":"and","conditions":[{"column":"title","operator":"eq","value":"GlobalSettings"}]}&limit=1
# Один элемент (itemId = 12 или guid)
GET /api/v1/lists/{listId}/items/12

Создать:

POST /api/v1/lists/{listId}/items
Content-Type: application/json
{
"fieldValues": {
"uuid-поля-title": "Новая задача",
"uuid-поля-status": "Новая",
"uuid-поля-due": "2026-07-15"
}
}

Обновить одно поле (merge):

PATCH /api/v1/lists/{listId}/items/12
Content-Type: application/json
{
"fieldValues": {
"uuid-поля-status": "Готово"
}
}

Сложные типы:

{
"fieldValues": {
"uuid-assignees": [
{
"principalType": "user",
"id": "uuid-пользователя",
"display": "Иван Иванов"
},
{
"principalType": "group",
"id": "uuid-группы",
"display": "Редакторы HR"
}
],
"uuid-room": {
"itemId": "uuid-элемента-справочника",
"display": "Переговорная 301"
}
}
}

// Чтение
const fields = await ctx.lists.getFields(listId);
const titleField = fields.find(f => f.internal_name === 'title');
const titleFieldId = titleField.id;
const items = await ctx.lists.getItems(listId, {
filter: { logic: 'and', conditions: [{ column: 'title', operator: 'eq', value: 'A' }] },
top: 1,
});
const item = items[0];
const title = item.field_values?.[titleFieldId];
// Запись (через REST)
await PortalApi.patch(`/lists/${listId}/items/${item.id}`, {
fieldValues: { [statusFieldId]: 'Готово' },
});

В фильтрах — column: "title" (internal_name). В fieldValues — только UUID полей.


Права проверяются от имени пользователя страницы.

using System.Text.Json;
using Portal.Contracts.Lists;
using Portal.WebPart.Sdk;
// 1. Схема → UUID полей
var fields = await ctx.Lists.GetFieldsAsync(listId, ct);
var titleFieldId = fields
.First(f => f.GetProperty("internal_name").GetString() == "title")
.GetProperty("id").GetString()!;
// 2. Прочитать элемент
var item = await ctx.Lists.GetItemAsync(listId, "12", ct);
var title = item.GetProperty("field_values").GetProperty(titleFieldId).GetString();
// 3. Найти запросом
var found = await ctx.Lists.QueryItemsAsync(listId,
ListItemQueryBuilder.ForList(listId)
.WhereField("title", f => f.Eq("GlobalSettings"))
.Take(1)
.Build(), ct);
// 4. Создать
await ctx.Lists.CreateItemAsync(listId, new Dictionary<string, object?>
{
[titleFieldId] = "Новая задача",
}, ct);
// 5. Обновить (merge — только изменённые поля)
await ctx.Lists.UpdateItemAsync(listId, "12", new Dictionary<string, object?>
{
[statusFieldId] = "Готово",
}, ct);
// 6. Значения полей в любой версии
var v2 = await ctx.Lists.GetItemVersionAsync(listId, "12", versionNumber: 2, ct);
var titleThen = PortalItemJson.GetFieldString(v2, titleFieldId);
// или вся история
var history = await ctx.Lists.GetItemVersionsAsync(listId, "12", ct);
foreach (var version in PortalItemJson.EnumerateVersions(history))
{
var n = PortalItemJson.GetVersionNumber(version);
var statusThen = PortalItemJson.GetFieldString(version, statusFieldId);
}

Типичный сценарий в HandleActionAsync:

public async Task<WebPartResult> HandleActionAsync(string action, JsonElement data, IWebPartContext ctx, CancellationToken ct)
{
if (action != "complete") return new WebPartResult("");
var listId = ctx.Properties.GetGuid("listId");
var itemId = data.GetProperty("itemId").GetString()!;
var fields = await ctx.Lists.GetFieldsAsync(listId, ct);
var statusFieldId = fields
.First(f => f.GetProperty("internal_name").GetString() == "status")
.GetProperty("id").GetString()!;
await ctx.Lists.UpdateItemAsync(listId, itemId, new Dictionary<string, object?>
{
[statusFieldId] = "Готово",
}, ct);
return await RenderAsync(ctx, ct);
}

См. SDK WebPart, Интерфейс и API.


Права — от пользователя события (context.User).

using System.Text.Json;
using Portal.Contracts.Lists;
using Portal.EventReceiver.Sdk;
public async Task<object?> HandleAsync(EventContext context, IReceiverApi api, CancellationToken ct)
{
var listId = context.Details.GetProperty("listId").GetString()!;
var itemRef = context.Details.GetProperty("item").GetProperty("id").GetRawText();
var fieldsJson = (JsonElement)await api.Lists.GetFieldsAsync(listId, ct);
var titleFieldId = fieldsJson.EnumerateArray()
.First(f => f.GetProperty("internal_name").GetString() == "title")
.GetProperty("id").GetString()!;
var statusFieldId = fieldsJson.EnumerateArray()
.First(f => f.GetProperty("internal_name").GetString() == "status")
.GetProperty("id").GetString()!;
var item = (JsonElement)await api.Lists.GetItemAsync(listId, itemRef, ct);
var currentTitle = item.GetProperty("field_values").GetProperty(titleFieldId).GetString();
// Снимок полей версии №1
var v1 = (JsonElement)await api.Lists.GetItemVersionAsync(listId, itemRef, 1, ct);
var titleV1 = v1.GetProperty("fieldValues").GetProperty(titleFieldId).GetString();
await api.Lists.UpdateItemAsync(listId, itemRef, new Dictionary<string, object?>
{
[statusFieldId] = "Готово",
}, ct);
return new { ok = true, was = currentTitle, titleV1 };
}

См. SDK Event Receiver.


Тот же API, что у Event Receiver. В config экземпляра обязателен runAsUserId:

{
"runAsUserId": "uuid-пользователя-с-edit",
"settingsListId": "uuid-списка"
}
using System.Text.Json;
using Portal.Contracts.Lists;
using Portal.TimerJob.Sdk;
public async Task<object?> ExecuteAsync(TimerJobContext context, ITimerJobApi api, CancellationToken ct)
{
var listId = context.Config.GetProperty("settingsListId").GetString()!;
var fields = (JsonElement)await api.Lists.GetFieldsAsync(listId, ct);
var valueFieldId = fields.EnumerateArray()
.First(f => f.GetProperty("internal_name").GetString() == "config_value")
.GetProperty("id").GetString()!;
var items = (JsonElement)await api.Lists.QueryItemsAsync(listId,
ListItemQueryBuilder.ForList(Guid.Parse(listId))
.WhereField("title", f => f.Eq("GlobalSettings"))
.Take(1)
.Build(), ct);
var item = items[0];
var itemId = item.GetProperty("id").GetRawText();
var oldValue = item.GetProperty("field_values").GetProperty(valueFieldId).GetString();
await api.Lists.UpdateItemAsync(listId, itemId, new Dictionary<string, object?>
{
[valueFieldId] = "новое значение",
}, ct);
return new { ok = true, oldValue };
}

См. SDK Timer Jobs.


При включённом версионировании списка (versioningEnabled / Настройки списка → Версионирование) у каждой версии хранится полный снимок fieldValues с теми же UUID-ключами, что у текущего элемента.

Подробности REST и формат ответа: API списков — Версии элементов.

GET /api/v1/lists/{listId}/items/12/versions
const res = await PortalApi.get(`/lists/${listId}/items/${itemId}/versions`);
const { versioningEnabled, versions, fields } = res.data;
if (!versioningEnabled) return;
const v2 = versions.find(v => v.versionNumber === 2);
const titleThen = v2?.fieldValues?.[titleFieldId];
// Одна версия — сразу снимок fieldValues
var v2 = await ctx.Lists.GetItemVersionAsync(listId, "12", versionNumber: 2, ct);
var titleThen = PortalItemJson.GetFieldString(v2, titleFieldId);
var dueThen = PortalItemJson.ReadDateTimeLocal(v2, dueFieldId);
// Вся история
var history = await ctx.Lists.GetItemVersionsAsync(listId, "12", ct);
if (!PortalItemJson.IsVersioningEnabled(history)) return;
foreach (var version in PortalItemJson.EnumerateVersions(history))
{
var n = PortalItemJson.GetVersionNumber(version);
var status = PortalItemJson.GetFieldString(version, statusFieldId);
}
// Версия из ответа истории без повторного запроса
if (PortalItemJson.TryFindVersion(history, 1, out var v1))
{
var createdTitle = PortalItemJson.GetFieldString(v1, titleFieldId);
}

Хелперы PortalItemJson.TryGetFieldValue / GetFieldString / ReadLookupGuid / ReadDateTimeLocal принимают и текущий элемент (field_values), и снимок версии (fieldValues).

var v1 = (JsonElement)await api.Lists.GetItemVersionAsync(listId, itemRef, 1, ct);
var titleV1 = v1.GetProperty("fieldValues").GetProperty(titleFieldId).GetString();
var history = (JsonElement)await api.Lists.GetItemVersionsAsync(listId, itemRef, ct);
var versions = history.GetProperty("versions");

Для Timer Job нужен runAsUserId в config экземпляра (как и для остальных методов Lists).


СпособЧитатьСоздатьОбновитьВерсии
REST APIPATCH (merge)GET .../versions
JS portalContextчерез PortalApi.postчерез PortalApi.patchчерез PortalApi.get
WebPart C#UpdateItemAsyncGetItemVersion(s)Async
Event Receiver
Timer Job✅ (runAsUserId)

Паттерн: загрузить fields → построить internal_name → fieldId → читать/писать field_values[fieldId] (для истории — fieldValues снимка версии).

Подробные кейсы с кодом из демо-модулей: Кейсы работы с сущностями.