Параметры WebPart (Property Pane)
Параметры — настройки экземпляра WebPart: какой список читать, сколько строк показать, какой заголовок. Редактор задаёт их в панели «Настройки». Схема объявляется в manifest.json, значения читаются в C# через ctx.Properties.
Эта страница — полный справочник: сценарии выбора данных, каждый тип параметра, dependsOn, методы чтения.
Краткий список типов в манифесте.
1. Зачем параметры
Заголовок раздела «1. Зачем параметры»Схема (manifest.json → properties) | Значения экземпляра | |
|---|---|---|
| Где | В пакете .portalpart | В разметке страницы (zones_content) или в form_config формы списка |
| Что задаёт | Какие поля показать в панели | Что выбрал редактор для этой вставки |
| Одинаково для всех вставок? | Да | Нет — у каждого экземпляра свои значения |
Параметры ≠ поля элемента списка.
listId — «из какого списка читать» (настройка виджета).
title элемента — данные записи (см. Кастомная форма: типы полей).
2. Быстрый старт
Заголовок раздела «2. Быстрый старт»1. Объявите параметр в manifest.json:
"properties": { "listId": { "type": "listPicker", "label": "Список", "required": true }}2. Пересоберите и переустановите пакет (Админка → WebPart).
3. На странице: Редактировать → WebPart → Настроить → выберите список → Применить → сохраните страницу.
4. В коде:
var listId = ctx.Properties.GetGuid("listId");if (listId == Guid.Empty) return new WebPartResult("""<p class="webpart-hint">Укажите список в настройках WebPart.</p>""");
var items = await ctx.Lists.GetItemsAsync(listId, limit: 20, ct: ct);Этого достаточно, если список лежит в том же узле, что и страница. Список из другого узла — см. сценарий 3.2.
3. Сценарии выбора данных
Заголовок раздела «3. Сценарии выбора данных»Ниже — готовые шаблоны для Property Pane. Подставьте свои имена ключей (listId, sourceNodeId…).
3.1. Список текущего узла страницы
Заголовок раздела «3.1. Список текущего узла страницы»Редактор видит списки того узла, на странице которого стоит WebPart.
"listId": { "type": "listPicker", "label": "Список", "required": true}var listId = ctx.Properties.GetGuid("listId");dependsOn не указывайте.
3.2. Список из другого узла портала
Заголовок раздела «3.2. Список из другого узла портала»Нужен список, который живёт не в узле текущей страницы (например, адресная книга в отдельном разделе, а виджет — на главной).
Сделайте два параметра:
nodePickerс"scope": "subtree"— выбрать любой узел портала;listPickerс"dependsOn"на этот параметр — подгрузятся списки выбранного узла.
"sourceNodeId": { "type": "nodePicker", "label": "Узел со списком", "scope": "subtree", "required": true, "description": "Узел, в котором лежит нужный список"},"listId": { "type": "listPicker", "label": "Список", "required": true, "dependsOn": "sourceNodeId"}var sourceNodeId = ctx.Properties.GetGuid("sourceNodeId"); // откуда брали списки в панелиvar listId = ctx.Properties.GetGuid("listId"); // достаточно для GetItemsAsync / CreateItemAsyncДля чтения/записи элементов обычно нужен только listId. sourceNodeId полезен для ссылок на узел или повторной настройки.
Эталон: пакет korport.base.birthdays (sourceNodeId + employeesListId).
3.3. Список из дочернего узла
Заголовок раздела «3.3. Список из дочернего узла»Если источник данных — один из дочерних узлов относительно узла страницы (не весь портал):
"sourceNodeId": { "type": "nodePicker", "label": "Подраздел", "required": true},"listId": { "type": "listPicker", "label": "Список", "required": true, "dependsOn": "sourceNodeId"}Без "scope": "subtree" в nodePicker показываются только дочерние узлы; пустое значение в UI подписано как «Текущий узел» (списки при пустом родителе не подгрузятся, пока родитель не выбран — поле с dependsOn скрыто).
3.4. Список + представление
Заголовок раздела «3.4. Список + представление»"listId": { "type": "listPicker", "label": "Список", "required": true },"viewId": { "type": "viewPicker", "label": "Представление", "dependsOn": "listId"}var listId = ctx.Properties.GetGuid("listId");var viewId = ctx.Properties.GetGuid("viewId"); // Guid.Empty = представление по умолчаниюGuid? viewRef = viewId == Guid.Empty ? null : viewId;var items = await ctx.Lists.GetItemsAsync(listId, viewRef, limit: 50, ct);viewPicker загружает представления через GET /lists/{listId}/views — узел списка не важен. Можно сочетать с сценарием 3.2.
3.5. Узел → список → представление (цепочка)
Заголовок раздела «3.5. Узел → список → представление (цепочка)»"sourceNodeId": { "type": "nodePicker", "label": "Узел", "scope": "subtree", "required": true},"listId": { "type": "listPicker", "label": "Список", "required": true, "dependsOn": "sourceNodeId"},"viewId": { "type": "viewPicker", "label": "Представление", "dependsOn": "listId"}Порядок в properties лучше такой же: родитель выше потомка.
3.6. Несколько списков
Заголовок раздела «3.6. Несколько списков»Каждый список — отдельный параметр со своим ключом:
"tasksListId": { "type": "listPicker", "label": "Список задач", "required": true },"bookingsListId": { "type": "listPicker", "label": "Список бронирований", "required": true }Если оба списка в другом узле, заведите один sourceNodeId и два listPicker с одним и тем же dependsOn:
"sourceNodeId": { "type": "nodePicker", "label": "Узел данных", "scope": "subtree", "required": true},"tasksListId": { "type": "listPicker", "label": "Задачи", "required": true, "dependsOn": "sourceNodeId"},"bookingsListId": { "type": "listPicker", "label": "Бронирования", "required": true, "dependsOn": "sourceNodeId"}3.7. Библиотека текущего узла
Заголовок раздела «3.7. Библиотека текущего узла»"libraryId": { "type": "libraryPicker", "label": "Библиотека", "required": true}var libraryId = ctx.Properties.GetGuid("libraryId");Показываются библиотеки узла страницы (как у listPicker без dependsOn).
3.8. Чего нельзя сделать через Property Pane
Заголовок раздела «3.8. Чего нельзя сделать через Property Pane»| Задача | Поддержка |
|---|---|
Библиотека другого узла (libraryPicker + dependsOn на nodePicker) | Нет. Платформа не перезагружает библиотеки по узлу. Обход: хранить UUID библиотеки в text / задать список через модуль provisioner |
| Произвольный UUID списка без выбора в панели | Да, тип text — редактор вводит GUID вручную (удобно только админам) |
| Выбор элемента списка как параметра | Нет отдельного типа; используйте text (номер/GUID) или логику в UI WebPart |
4. Схема в manifest.json
Заголовок раздела «4. Схема в manifest.json»Секция properties — объект: ключ = имя в коде, значение = описание поля панели.
{ "id": "contoso.demo-widget", "title": "Демо", "version": "1.0.0", "category": "Списки", "icon": "fa-sliders", "runtime": "dotnet", "entry": "dist/DemoWidget.dll", "entryType": "Contoso.Demo.DemoWebPart", "styles": ["dist/main.css"], "scripts": ["dist/main.js"], "properties": { "title": { "type": "text", "label": "Заголовок", "default": "Мой виджет", "description": "Показывается над списком" }, "listId": { "type": "listPicker", "label": "Список", "required": true }, "viewId": { "type": "viewPicker", "label": "Представление", "dependsOn": "listId" }, "pageSize": { "type": "number", "label": "Записей", "default": 10, "min": 1, "max": 100 }, "showTitle": { "type": "boolean", "label": "Показывать заголовок", "default": true }, "mode": { "type": "choice", "label": "Режим", "default": "week", "choices": [ { "value": "week", "label": "Неделя" }, { "value": "month", "label": "Месяц" } ] } }}Имена ключей — латиница, camelCase (listId, не ListId). Тот же ключ передаёте в GetGuid("listId").
После изменения схемы: пересборка + переустановка пакета. Уже сохранённые значения не сбрасываются, если ключи не переименовали.
Атрибуты поля схемы
Заголовок раздела «Атрибуты поля схемы»| Атрибут | Где применим | Назначение |
|---|---|---|
type | все | Тип контрола (обязателен) |
label | все | Подпись в панели |
description | все | Подсказка под полем |
required | все кроме boolean (для boolean пустота не проверяется) | Нельзя нажать «Применить» с пустым значением |
default | все | Значение до первого сохранения редактором |
dependsOn | обычно listPicker, viewPicker | Имя родительского параметра (см. §5) |
choices | только choice | [{ "value", "label" }] |
min / max | только number | Ограничения ввода в панели |
scope | только nodePicker | "subtree" — все узлы портала; иначе — дочерние узла страницы |
5. dependsOn
Заголовок раздела «5. dependsOn»dependsOn связывает два параметра.
Поведение панели:
- Пока у родителя пустое значение — дочернее поле скрыто.
- Когда родитель выбран — дочернее поле показывается.
- Для
listPicker: вызываетсяGET /api/v1/webparts/context?nodeId={значение родителя}→ в select попадают списки этого узла. - Для
viewPicker: вызываетсяGET /api/v1/lists/{listId}/views→ представления выбранного списка. - При смене родителя список опций дочернего поля перезагружается (старое значение может стать невалидным — редактор выбирает заново).
Рабочие пары:
Дочерний type | Родитель | Смысл |
|---|---|---|
listPicker | nodePicker (или параметр с UUID узла) | Списки выбранного узла |
viewPicker | listPicker (или параметр с UUID списка) | Представления выбранного списка |
Не работает:
| Дочерний | Родитель | Почему |
|---|---|---|
libraryPicker | nodePicker | Код панели не обновляет библиотеки по dependsOn |
listPicker | listPicker | Родителем должен быть узел, не список |
Значение dependsOn — имя ключа родителя в том же объекте properties (строка "sourceNodeId", не путь).
6. Справочник типов
Заголовок раздела «6. Справочник типов»Для каждого типа: UI → схема → значение → чтение в C# → нюансы.
6.1. text
Заголовок раздела «6.1. text»| UI | Однострочное поле |
| Схема | label, description, required, default |
| Значение | строка |
| Чтение | ctx.Properties.GetString("title", "По умолчанию") |
Подходит для заголовков, произвольного GUID, номера элемента.
6.2. multiline
Заголовок раздела «6.2. multiline»| UI | Многострочное (textarea) |
| Схема | label, description, required, default |
| Значение | строка |
| Чтение | GetString("intro", "") |
Особый случай в UI: если в label или description есть подстрока JSON, панель при сохранении пытается разобрать текст как JSON (массив/объект). Для обычного текста этого лучше не указывать.
6.3. number
Заголовок раздела «6.3. number»| UI | input type="number" |
| Схема | default, min, max, required, … |
| Значение | число |
| Чтение | ctx.Properties.GetInt32("pageSize", 10) |
Пустое/нечисловое в панели часто сохраняется как 0 — всегда задавайте default в схеме и fallback во втором аргументе GetInt32.
6.4. boolean
Заголовок раздела «6.4. boolean»| UI | Флажок |
| Схема | label, default (true/false) |
| Значение | true / false |
| Чтение | ctx.Properties.GetBoolean("showTitle", true) |
Для required пустота не считается ошибкой (флажок всегда «есть»).
6.5. choice
Заголовок раздела «6.5. choice»| UI | Выпадающий список |
| Схема | обязательно choices: [{ "value", "label" }], плюс default |
| Значение | строка value выбранного пункта |
| Чтение | GetString("mode", "week") |
"mode": { "type": "choice", "label": "Режим", "default": "week", "choices": [ { "value": "week", "label": "Неделя" }, { "value": "month", "label": "Месяц" } ]}В коде сравнивайте с value, не с label.
6.6. color
Заголовок раздела «6.6. color»| UI | Выбор цвета |
| Схема | default (например "#2563eb") |
| Значение | строка #rrggbb |
| Чтение | GetString("accent", "#2563eb") |
6.7. listPicker
Заголовок раздела «6.7. listPicker»| UI | Select со списками |
| Схема | required, dependsOn (опционально) |
| Значение | UUID списка (строка) |
| Чтение | ctx.Properties.GetGuid("listId") → Guid.Empty, если не выбран |
Варианты:
| Конфиг | Откуда списки |
|---|---|
без dependsOn | Узел текущей страницы (/webparts/context?nodeId= страницы) |
"dependsOn": "sourceNodeId" | Узел из значения родителя (другой или дочерний — см. §3.2, §3.3) |
6.8. libraryPicker
Заголовок раздела «6.8. libraryPicker»| UI | Select с библиотеками узла страницы |
| Схема | required, default |
| Значение | UUID библиотеки |
| Чтение | GetGuid("libraryId") |
Ограничение: только библиотеки узла страницы. Связка с nodePicker через dependsOn не поддерживается (§3.8).
6.9. nodePicker
Заголовок раздела «6.9. nodePicker»| UI | Select с узлами |
| Схема | scope, required, description |
| Значение | UUID узла (строка); может быть пустым |
| Чтение | GetGuid("sourceNodeId") |
Варианты scope:
scope | Список в панели | Подпись пустого пункта |
|---|---|---|
| не указан | Дочерние узлы узла страницы | «Текущий узел» |
"subtree" | Узлы портала (дерево от корня, с путём в названии) | «Выберите узел» |
Для выбора списка из произвольного раздела почти всегда нужен "scope": "subtree" + listPicker с dependsOn.
6.10. viewPicker
Заголовок раздела «6.10. viewPicker»| UI | Select представлений |
| Схема | обычно dependsOn на параметр со списком |
| Значение | UUID представления или пусто («по умолчанию») |
| Чтение | GetGuid("viewId") — Guid.Empty = default view |
Без выбранного родителя-списка поле скрыто / пустое. Работает и со списком текущего узла, и со списком из другого узла.
6.11. linksEditor
Заголовок раздела «6.11. linksEditor»| UI | Таблица: название, URL, иконка FA + кнопки добавить/удалить |
| Схема | label, description, required |
| Значение | массив объектов { "title", "url", "icon" } |
| Чтение | TryGetValue + разбор JsonElement (не GetString) |
if (ctx.Properties.TryGetValue("links", out var linksEl) && linksEl.ValueKind == JsonValueKind.Array){ foreach (var link in linksEl.EnumerateArray()) { var linkTitle = link.TryGetProperty("title", out var t) ? t.GetString() ?? "" : ""; var url = link.TryGetProperty("url", out var u) ? u.GetString() ?? "" : ""; var icon = link.TryGetProperty("icon", out var i) ? i.GetString() ?? "" : ""; }}7. Методы чтения ctx.Properties
Заголовок раздела «7. Методы чтения ctx.Properties»Контекст: IWebPartContext ctx в RenderAsync и HandleActionAsync.
Тип: IReadOnlyDictionary<string, JsonElement>.
Хелперы: WebPartPropertiesExtensions в Portal.WebPart.Sdk.
| Метод | Типы параметров | Если ключа нет / значение невалидно |
|---|---|---|
GetString(key, defaultValue = "") | text, multiline, choice, color; также приводит number/bool к тексту | defaultValue |
GetGuid(key) | listPicker, libraryPicker, nodePicker, viewPicker | Guid.Empty |
GetInt32(key, defaultValue = 0) | number | defaultValue |
GetBoolean(key, defaultValue = false) | boolean | defaultValue |
TryGetValue(key, out JsonElement) | linksEditor, любой нестандартный JSON | false |
Полный пример:
using System.Text.Json;using Portal.WebPart.Sdk;
public async Task<WebPartResult> RenderAsync(IWebPartContext ctx, CancellationToken ct){ var title = ctx.Properties.GetString("title", "Мой виджет"); var mode = ctx.Properties.GetString("mode", "week"); var listId = ctx.Properties.GetGuid("listId"); var viewId = ctx.Properties.GetGuid("viewId"); var libraryId = ctx.Properties.GetGuid("libraryId"); var pageSize = ctx.Properties.GetInt32("pageSize", 10); var showTitle = ctx.Properties.GetBoolean("showTitle", true);
if (listId == Guid.Empty) return new WebPartResult("""<p class="webpart-hint">Укажите список в настройках WebPart.</p>""");
// дальше — Lists / Libraries API await Task.CompletedTask; return new WebPartResult($"<p>{System.Net.WebUtility.HtmlEncode(title)}</p>");}В HandleActionAsync те же ctx.Properties — платформа передаёт актуальные настройки экземпляра вместе с action.
Сырой доступ:
if (ctx.Properties.TryGetValue("pageSize", out var raw)){ // raw.ValueKind — Number / String / …}8. Где задаются и хранятся значения
Заголовок раздела «8. Где задаются и хранятся значения»Страница
Заголовок раздела «Страница»- Редактировать страницу → на WebPart Настроить.
- Заполнить панель → Применить.
- Сохранить страницу (иначе значения пропадут при уходе).
Хранилище: properties блока webpart в zones_content.
Форма списка
Заголовок раздела «Форма списка»Настройки списка → Формы → WebPart на форме → те же поля схемы; значения в form_config → webParts[].properties.
listPicker без dependsOn берёт списки узла списка (контекст формы). Для списка из другого раздела снова используйте §3.2.
После обновления пакета
Заголовок раздела «После обновления пакета»| Изменение в схеме | Эффект |
|---|---|
| Добавили новый ключ | У старых экземпляров ключа нет → сработает default / fallback в Get* |
| Переименовали ключ | Старое значение остаётся под старым именем; новый ключ пустой |
| Удалили ключ | Значение может остаться в JSON страницы, код его больше не читает |
9. Частые ошибки
Заголовок раздела «9. Частые ошибки»| Симптом | Причина | Что сделать |
|---|---|---|
| В панели нет полей | Нет properties в манифесте или пакет не переустановлен | Проверить manifest.json, переустановить .portalpart |
В listPicker только списки «этой» страницы, а нужен другой узел | Нет связки с nodePicker | §3.2: scope: "subtree" + dependsOn |
GetGuid → Empty | Не нажали «Применить» / не сохранили страницу; опечатка в ключе | listId vs ListId; сохранить страницу |
viewPicker пустой | Нет dependsOn на параметр списка или список ещё не выбран | Как в §3.4 |
libraryPicker не зависит от выбранного узла | Так устроена панель | §3.8 |
| После обновления пакета «пропали» настройки | Переименовали ключи в properties | Оставить старые имена или мигрировать значения на страницах |
GetInt32 всегда default | В панели пусто / не число | default в схеме + fallback в коде |
| Дочернее поле не видно | Родитель пуст — так работает dependsOn | Сначала выбрать узел/список-родитель |
См. также
Заголовок раздела «См. также»- Манифест — полный
manifest.json - SDK WebPart —
IWebPartContext - Интерфейс и API — рендер и actions
- Кастомная форма: все типы полей — поля элемента списка, не Property Pane
- API WebPart —
GET /webparts/context