Значения полей списков: чтение и обновление
Руководство по программной работе с field_values элементов списка — REST API, JavaScript WebPart, C# SDK (WebPart, Event Receiver, Timer Job).
См. также: API списков (эндпоинты, типы полей, фильтрация).
Как устроены данные
Заголовок раздела «Как устроены данные»-
Схема полей —
GET /lists/{listId}/fieldsилиGetFieldsAsync/getFields. У каждого поля естьid(UUID) иinternal_name(title,status…). -
Значения элемента — объект
field_values, ключи только UUID полей (неinternal_name):
{ "id": 12, "guid": "uuid-элемента", "field_values": { "uuid-поля-title": "Заголовок", "uuid-поля-status": "Новая" }}-
idэлемента в ответе API — публичный номер (12). ДляGetItem/PATCH/UpdateItemAsyncможно передать номер илиguid. -
Обновление — merge: в
fieldValuesпередаёте только изменённые поля, остальные не обнуляются. После merge сервер валидирует все обязательные поля схемы (is_requiredвGET /fields). -
Фильтр (
filter,QueryItemsAsync) — вcolumnдопустимinternal_name("title"). Вfield_valuesпри чтении/записи — только UUID. -
Версии — если у списка включено версионирование, у каждой версии есть полный снимок
fieldValues(те же UUID-ключи). SDK:GetItemVersionsAsync/GetItemVersionAsync.
Форматы значений по типам полей
Заголовок раздела «Форматы значений по типам полей»| Тип | Значение в fieldValues / field_values |
|---|---|
text, multiline_text, choice | строка |
number | число |
date | "YYYY-MM-DD" |
datetime | ISO 8601 |
boolean | true / false |
person | массив `[{ “principalType”: “user" |
lookup | { "itemId": "uuid", "display": "Текст" } |
link | { "url": "https://...", "description": "..." } |
REST API
Заголовок раздела «REST API»# Схема полей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}/itemsContent-Type: application/json
{ "fieldValues": { "uuid-поля-title": "Новая задача", "uuid-поля-status": "Новая", "uuid-поля-due": "2026-07-15" }}Обновить одно поле (merge):
PATCH /api/v1/lists/{listId}/items/12Content-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" } }}JavaScript (WebPart, клиент)
Заголовок раздела «JavaScript (WebPart, клиент)»// Чтение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 полей.
WebPart (C#)
Заголовок раздела «WebPart (C#)»Права проверяются от имени пользователя страницы.
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.
Event Receiver (C#)
Заголовок раздела «Event Receiver (C#)»Права — от пользователя события (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.
Timer Job (C#)
Заголовок раздела «Timer Job (C#)»Тот же 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/versionsconst 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];WebPart (C#)
Заголовок раздела «WebPart (C#)»// Одна версия — сразу снимок fieldValuesvar 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).
Event Receiver / Timer Job (C#)
Заголовок раздела «Event Receiver / Timer Job (C#)»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 API | ✅ | ✅ | ✅ PATCH (merge) | ✅ GET .../versions |
JS portalContext | ✅ | через PortalApi.post | через PortalApi.patch | через PortalApi.get |
| WebPart C# | ✅ | ✅ | ✅ UpdateItemAsync | ✅ GetItemVersion(s)Async |
| Event Receiver | ✅ | ✅ | ✅ | ✅ |
| Timer Job | ✅ (runAsUserId) | ✅ | ✅ | ✅ |
Паттерн: загрузить fields → построить internal_name → fieldId → читать/писать field_values[fieldId] (для истории — fieldValues снимка версии).
Подробные кейсы с кодом из демо-модулей: Кейсы работы с сущностями.