Support ContentPlaceHolder inside CompositeControl templates (deferred master page composition)#2017
Support ContentPlaceHolder inside CompositeControl templates (deferred master page composition)#2017Copilot wants to merge 9 commits into
Conversation
…ster page composition) Agent-Logs-Url: https://github.com/riganti/dotvvm/sessions/ca479579-7532-44bd-96e4-d8638c2d5130 Co-authored-by: tomasherceg <5599524+tomasherceg@users.noreply.github.com>
Agent-Logs-Url: https://github.com/riganti/dotvvm/sessions/ca479579-7532-44bd-96e4-d8638c2d5130 Co-authored-by: tomasherceg <5599524+tomasherceg@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
|
I fixed some build errors. |
Agent-Logs-Url: https://github.com/riganti/dotvvm/sessions/7069fa2e-7910-464f-85d5-bac0a16e9d74 Co-authored-by: tomasherceg <5599524+tomasherceg@users.noreply.github.com>
Done in commit c2ec3b6. Here's what was added: Same ContentPlaceHolderID in root and nested master ( This also required a bug fix: when the same ID appears at multiple levels, the pending list contains multiple entries with the same ID. The original Default content ( Selenium tests added for both cases. |
| }); | ||
| } | ||
|
|
||
| [Fact] |
There was a problem hiding this comment.
@copilot Move these tests to a separate class (LateContentPlaceHoldersTests) to keep the current convention.
There was a problem hiding this comment.
Done in commit 84eb337. All four LateContentPlaceHolders tests have been moved to a new LateContentPlaceHoldersTests class in LateContentPlaceHoldersTests.cs, and MasterPageTests now contains only the original nested master pages test.
…rsTests class Agent-Logs-Url: https://github.com/riganti/dotvvm/sessions/d304bd6d-2bdb-4b99-97c5-89777d4241b4 Co-authored-by: tomasherceg <5599524+tomasherceg@users.noreply.github.com>
|
@copilot We want to ensure that the |
Agent-Logs-Url: https://github.com/riganti/dotvvm/sessions/c846671f-e8f5-4f1d-b690-76b7f1ef8655 Co-authored-by: tomasherceg <5599524+tomasherceg@users.noreply.github.com>
Done in commit 981956c. Here's what was implemented:
Early validation in Duplicate-composition guard in Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
|
@copilot Add tests for the scenarios we didn't cover:
|
Agent-Logs-Url: https://github.com/riganti/dotvvm/sessions/2b48d2fd-fe36-480d-bdc3-1a9c5b7c5012 Co-authored-by: tomasherceg <5599524+tomasherceg@users.noreply.github.com>
Done in commit 4dfa92c. Here's what was added: Repeater scenarios (
AuthenticatedView scenario ( Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
ContentPlaceHoldercouldn't be used insideCompositeControltemplates becauseGetContentsruns in theLoadphase, after master page composition (which runs duringBuildView, beforeInit).Changes
Framework
DefaultDotvvmViewBuilder: Instead of throwing immediately when aContentPlaceHolderID is not found duringPerformMasterPageComposition, unmatchedContentcontrols are deferred into a sharedList<PendingMasterPageComposition>stored on the rootDotvvmView. Before deferring, the master page'sControlBuilderDescriptor.ContentPlaceHolderIdsis checked — if the ID is not declared anywhere in the master page (including inside templates), an exception is thrown immediately at build time with a clear error message.ContentPlaceHolder.OnInit: AddedResolvePendingComposition()— when aContentPlaceHolderis added to the tree (including as a missed lifecycle event duringLoadorPreRender), it traverses ancestors to find the pending list and performs the deferred composition. UsesFindLastIndexso that when the sameContentPlaceHolderIDis reused at multiple master page levels, each placeholder correctly matches its corresponding content (outermost placeholder ↔ outermost content). Throws a descriptive exception if the same ID is resolved a second time (e.g.ContentPlaceHolderplaced inside aRepeaterwith more than one item).DotvvmPresenter:ValidateMasterPageComposition()is called after both theLoadphase (for postback/command requests, whereRepeatercreates children inOnLoad) and after thePreRenderphase (for GET requests, whereRepeatercreates children inOnPreRender). If any deferredContentcontrols remain unmatched at that point (e.g. aRepeaterwith zero items), a descriptive exception is thrown.ControlBuilderDescriptor: AddedContentPlaceHolderIds(ImmutableArray<string>) — populated at compile time by traversing the full resolved tree, including controls nested insideCompositeControltemplate properties. Used by the view builder to validate deferred compositions before they are queued.Internal: AddedPendingMasterPageCompositionsProperty,PendingMasterPageComposition(holds theContent, itsDataContextStack, and master page file for error messages), andResolvedMasterPageCompositionIdsProperty(HashSet<string>) which tracks which placeholder IDs have already been resolved to detect duplicate instantiation.Usage Example
Where
TemplateContainerControlis aCompositeControlthat instantiates the template inGetContents(Load phase).Samples & Tests
LateContentPlaceHoldersfeature sample with a 3-level master page hierarchy (Root.dotmaster→Nested.dotmaster→Content.dothtml) where eachContentPlaceHolderlives inside aCompositeControltemplate.NestedSameId.dotmaster+SharedIdContent.dothtml— demonstrates and tests the case where the sameContentPlaceHolderID("RootContent") is reused in both the root and the middle master page.ContentWithDefault.dothtml— tests that when noContentis provided for aContentPlaceHolder, its own default children are rendered.MismatchedContent.dothtml— a content page with aContentwhoseContentPlaceHolderIDhas no match anywhere, verifying the early build-time error is thrown.RepeaterMaster.dotmaster+ three content pages — testsContentPlaceHolderinside a server-renderedRepeater: exactly 1 item works, 0 items throws (not found), and more than 1 item throws (duplicate composition).AuthViewMaster.dotmaster+AuthViewContent.dothtml— demonstrates and tests that the sameContentPlaceHolderIDin bothAuthenticatedTemplateandNotAuthenticatedTemplateofAuthenticatedViewworks correctly because only one template is instantiated per request.LateContentPlaceHoldersTests.