Admin /admin/assets/<siteId>/getList?path= крашился 500 NullReferenceException в AssetsJsonViewModelBuilder.cs:22 (model.ParentPath на null) для 15 sites без root AssetsFolder в Folders table (emspb.ru, pilorama98.ru, labtools.pro, kupimknigi.spb.ru, sestech.ru, aquamax.spb.ru, artmone.pro, priemka-kvartiry.ru, profund.spb.ru, ics-artmaterials.com, _voda-indigo.ru + 4 sites с NULL Domain). Root создавался lazy при first upload — sites которые никогда не использовали admin assets UI остались без root. Frontend (AssetsAppFunc.cs:66-86) делает proper null-check → 404, только admin view-model builder упустил. Fix: idempotent SQL seed (WHERE NOT EXISTS), 15 rows inserted. Inserted FolderId/OwnerId captured в .tasks/...inserted-rows.txt для atomic revert. Browser-verified user'ом на pilorama98/emspb. Долгосрочный TODO: null-guard в AssetsJsonViewModelBuilder.Build (CMS code), требует recompile DLL — отложено до восстановления build env (vds-kzntsv-bootstrap). snapshot open issue #8 → resolved. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6.8 KiB
cms-admin-assets-root-folders-seed
Goal
Для каждого Sites.SiteId в DB MoreThenCms где нет соответствующего root Folders row с Discriminator='AssetsFolder', LoweredPath='/', OwnerId=<SiteId> — добавить недостающий root row. Это unblock'нёт admin /admin/assets/<siteId>/getList?path= который сейчас crash'ит 500 NullReferenceException на пустых assets для 15+ sites (включая emspb.ru, pilorama98.ru, labtools.pro, kupimknigi.spb.ru, sestech.ru, etc.).
Root cause
MoreThenCms.Admin.ViewModels.Builders.AssetsJsonViewModelBuilder.Build (AssetsJsonViewModelBuilder.cs:22) делает new JValue(model.ParentPath) БЕЗ null-check на model. _assetsFoldersService.GetFolderByPath(ctx, ownerId, '') возвращает null когда root folder не существует. Это происходит для sites которые никогда не открывали admin assets UI (root создаётся lazy при first upload, видимо).
Frontend код (AssetsAppFunc.cs:66-86) делает proper null-check → HTTP 404. Только admin view-model builder упустил.
Двух-уровневое решение:
- Краткосрочно (этой task'и): seed missing root rows в DB. Один INSERT per site. Risk: minimal (lookup existing row pattern; используем same shape).
- Долгосрочно (отдельный task): patch
AssetsJsonViewModelBuilder.Build→ null-guard → empty response. Требует recompile DLLMoreThenCms.Admin.dll(нет полного build env, рискованно).
Key files
MoreThenCms\Assets\Services\AssetsFoldersService.cs:47-54—GetFolderByPathquery (returns null if not found).MoreThenCms.Web\ViewModels\Builders\AssetsJsonViewModelBuilder.cs:22— точка падения.MoreThenCms.Web\Admin\Controllers\AssetsController.cs:44-54— вызов GetList.- DB:
[MoreThenCms].[dbo].[Folders](polymorphic, Discriminator='AssetsFolder' для assets).
Discovery (2026-05-19)
- 15 sites без root AssetsFolder подтверждены через
SELECT s.SiteId, s.PrimaryDomain, ... FROM Sites s LEFT JOIN .... См. полный список в Decisions. - Sample existing root row:
FolderId=1591A38C-..., OwnerId=7EB313DD-F289-..., Path='/', LoweredPath='/', DateCreated=2017-03-13 07:10:15, CreatedById=E4CC416B-.... Тот же shape надо воспроизвести для missing. - Затрагивает: emspb.ru (
96EBC481-...), pilorama98.ru (37E67FC4-...), labtools.pro, kupimknigi.spb.ru, sestech.ru, aquamax.spb.ru, artmone.pro, priemka-kvartiry.ru, profund.spb.ru, ics-artmaterials.com, _voda-indigo.ru, plus 4 sites с NULLPrimaryDomain.
План
- Dry-run:
SELECT COUNT(*) FROM Sites s WHERE NOT EXISTS (root for s.SiteId)— должно быть = 15 (sanity check). - Seed: INSERT root row per missing site. Один transaction, no rollback unless count mismatch.
- Verify: повторить original probe — root count should be 0 missing.
- Test: hit
https://emspb.snolla.com/admin/assets/96EBC481-.../getList?path=(через docker exec без auth → 302→login без crash). Затем user-side browser test.
SQL
DECLARE @CreatedById uniqueidentifier = (
SELECT TOP 1 CreatedById FROM Folders
WHERE LoweredPath='/' AND Discriminator='AssetsFolder'
);
INSERT INTO Folders (FolderId, OwnerId, Path, LoweredPath, DateCreated, UtcDateCreated, CreatedById, Discriminator)
SELECT NEWID(), s.SiteId, '/', '/', SYSDATETIME(), SYSUTCDATETIME(), @CreatedById, 'AssetsFolder'
FROM Sites s
WHERE NOT EXISTS (
SELECT 1 FROM Folders f
WHERE f.OwnerId = s.SiteId
AND f.LoweredPath = '/'
AND f.Discriminator = 'AssetsFolder'
);
Atomic revert
Сохранить список inserted FolderId через OUTPUT INSERTED.FolderId в temp file. Если что:
DELETE FROM Folders WHERE FolderId IN (<list>);
Open questions
- Аналогичная проблема для ImagesFolder/StylesheetsFolder/ScriptsFolder в admin Themes UI? Проверить.
- DLL patch
AssetsJsonViewModelBuilder.Buildнужен в долгую — отдельная task. Сейчас обходим seed'ом.
Decisions log
- 2026-05-19: вынес из
cms-port-leak-fix(там был open issue #8 на emspb). User подтвердил воспроизводимость на pilorama98 → не site-specific, общая data issue. - 2026-05-19: DB seed применён — 15 rows inserted (single transaction, idempotent NOT EXISTS). Inserted FolderId/OwnerId captured в
cms-admin-assets-root-folders-seed.inserted-rows.txt. Server-side verified (probe вернул 302→login вместо 500). User browser-verified на pilorama98/emspb — admin assets открывается, empty list без ошибок. - 2026-05-19: DLL recompile отложен —
AssetsJsonViewModelBuilder.Buildнужен null-guard как defensive code, но требует build env (нет gitea/build/registry на recovery host'е). Ждёт vds-kzntsv-bootstrap для восстановления pipeline'а. На текущем seed-only fix'е admin assets работает для всех 15 sites.
Completed steps
- Diagnostic: stack trace из Application event log →
AssetsJsonViewModelBuilder.cs:22NullRef onmodel.ParentPath. Source code analysis показал отсутствие null-check (vs proper null-check во frontendAssetsAppFunc.cs:66-86). - DB probe schema:
Folderstable polymorphic с Discriminator, columns FolderId/OwnerId/Path/LoweredPath/DateCreated/CreatedById обязательные. - Dry-run: 15 sites без root AssetsFolder (включая emspb/pilorama98/labtools.pro/kupimknigi/sestech/etc), 4 sites с NULL PrimaryDomain тоже в списке.
- SQL seed через
docker exec mssql sqlcmd— INSERT 15 rows сOUTPUT INSERTED.FolderId, INSERTED.OwnerId INTO @Inserted, output saved в.tasks/cms-admin-assets-root-folders-seed.inserted-rows.txt. - Verify: missing-root count = 0. Probe
wget --header='Host: emspb.snolla.com'на getList URL → 302 Found (auth redirect — normal), нет больше 500. - User browser-verified pilorama98 admin assets открывается.
- Wiki ingest: новый concept
cms-admin-assets-root-folder-seed.md, snapshot issue #8 → resolved,cms-server-port-leak-fixsibling-link, log.md + index.md обновлены.
Notes
- Связано: cms-server-port-leak-fix (предыдущая task'а закрыта; admin URLs теперь без
:8089); recovery-architecture-snapshot (issue #8 — resolved); vds-kzntsv-bootstrap (build env для долгосрочного DLL fix'а).