From 239dfdda8e54fbbbca86d2226ce2f0be33c04982 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Standa=20Luke=C5=A1?= Date: Sat, 27 Apr 2024 12:49:22 +0200 Subject: [PATCH] Fix global resource registration in master pages When the RequiredResource controls are added at the end of the page, the DefaultDotvvmViewBuilder would crash. This specialcases the RequiredResource, since it doesn't render anything, it's safe to include anywhere --- .../Runtime/DefaultDotvvmViewBuilder.cs | 39 +++++++++--- src/Framework/Testing/ControlTestHelper.cs | 4 +- .../ControlTests/ServerSideStyleTests.cs | 61 +++++++++++++++++++ ...eStyleTests.AddResourceWithMasterPage.html | 53 ++++++++++++++++ 4 files changed, 147 insertions(+), 10 deletions(-) create mode 100644 src/Tests/ControlTests/testoutputs/ServerSideStyleTests.AddResourceWithMasterPage.html diff --git a/src/Framework/Framework/Runtime/DefaultDotvvmViewBuilder.cs b/src/Framework/Framework/Runtime/DefaultDotvvmViewBuilder.cs index 1759c9412c..7e25625a11 100644 --- a/src/Framework/Framework/Runtime/DefaultDotvvmViewBuilder.cs +++ b/src/Framework/Framework/Runtime/DefaultDotvvmViewBuilder.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Linq.Expressions; using System.Reflection; using System.Threading.Tasks; using DotVVM.Framework.Binding; @@ -107,7 +108,7 @@ private void PerformMasterPageComposition(DotvvmView childPage, DotvvmView maste var placeHolders = GetMasterPageContentPlaceHolders(masterPage); // find contents - var contents = GetChildPageContents(childPage, placeHolders); + var (contents, auxControls) = GetChildPageContents(childPage, placeHolders); // perform the composition foreach (var content in contents) @@ -133,7 +134,10 @@ private void PerformMasterPageComposition(DotvvmView childPage, DotvvmView maste content.SetValue(Internal.MarkupFileNameProperty, childPage.GetValue(Internal.MarkupFileNameProperty)); content.SetValue(Internal.ReferencedViewModuleInfoProperty, childPage.GetValue(Internal.ReferencedViewModuleInfoProperty)); } - + + foreach (var control in auxControls) + masterPage.Children.Add(control); + // copy the directives from content page to the master page (except the @masterpage) masterPage.ViewModelType = childPage.ViewModelType; } @@ -160,11 +164,30 @@ private List GetMasterPageContentPlaceHolders(DotvvmControl /// /// Checks that the content page does not contain invalid content. /// - private List GetChildPageContents(DotvvmView childPage, List parentPlaceHolders) + private (List contents, List auxiliaryControls) GetChildPageContents(DotvvmView childPage, List parentPlaceHolders) { - // make sure that the body contains only whitespace and Content controls - var nonContentElements = - childPage.Children.Where(c => !((c is RawLiteral && ((RawLiteral)c).IsWhitespace) || (c is Content))); + // make sure that the body contains only Content controls (and whitespace and auxiliary controls) + var nonContentElements = new List(); + // controls which don't render anything and may be placed anywhere (RequireResource) + var auxiliaryControls = new List(); + foreach (var child in childPage.Children) + { + if (child is RawLiteral { IsWhitespace: true }) + { + // ignored + } + else if (child is RequiredResource) + { + child.Parent = null; // childPage view is discarded + auxiliaryControls.Add(child); + } + else if (child is Content) + { + // handled bellow + } + else + nonContentElements.Add(child); + } if (nonContentElements.Any()) { // show all error lines @@ -182,10 +205,10 @@ private List GetChildPageContents(DotvvmView childPage, List c.Parent != childPage) is {} invalidContent) { - throw new DotvvmControlException(invalidContent, "The control cannot be placed inside any control!"); + throw new DotvvmControlException(invalidContent, $"The control cannot be placed inside any control!"); } - return contents; + return (contents, auxiliaryControls); } } } diff --git a/src/Framework/Testing/ControlTestHelper.cs b/src/Framework/Testing/ControlTestHelper.cs index 13e1fad9b3..75ece29cce 100644 --- a/src/Framework/Testing/ControlTestHelper.cs +++ b/src/Framework/Testing/ControlTestHelper.cs @@ -134,7 +134,7 @@ public ControlTestHelper(bool debug = true, Action? config ClaimsPrincipal? user = null, CultureInfo? culture = null) { - if (!markup.Contains("\n{markup}\n{(renderResources ? "" : "")}\n"; } @@ -142,7 +142,7 @@ public ControlTestHelper(bool debug = true, Action? config { markup = "" + markup; } - if (!markup.Contains("\n{markup}"; } diff --git a/src/Tests/ControlTests/ServerSideStyleTests.cs b/src/Tests/ControlTests/ServerSideStyleTests.cs index a448cf161a..8ce8c00504 100644 --- a/src/Tests/ControlTests/ServerSideStyleTests.cs +++ b/src/Tests/ControlTests/ServerSideStyleTests.cs @@ -443,6 +443,67 @@ @viewModel int check.CheckString(r.FormattedHtml, fileExtension: "html"); } + + + [TestMethod] + public async Task AddResourceWithMasterPage() + { + var cth = createHelper(c => { + + c.Resources.Register("test-resource1", new InlineScriptResource("alert(1)")); + c.Resources.Register("test-resource2", new InlineScriptResource("alert(2)")); + c.Resources.Register("test-resource3", new InlineScriptResource("alert(3)")); + c.Resources.Register("test-resource4", new InlineScriptResource("alert(4)")); + c.Resources.Register("test-resource5", new InlineScriptResource("alert(5)")); + + c.Styles.RegisterRoot() + .AddRequiredResource(cx => { + return cx.Control.TreeRoot.Directives.TryGetValue("custom_resource_import", out var x) ? x.Select(d => d.Value).ToArray() : new string[0]; + }); + }); + + var r = await cth.RunPage(typeof(object), """ + + + real body + + + + """, + directives: """ + @masterPage master1.dotmaster + @custom_resource_import test-resource1 + """, + markupFiles: new Dictionary { + ["master1.dotmaster"] = """ + @viewModel object + @masterPage master2.dotmaster + @custom_resource_import test-resource2 + + + + + +
+ +
+
+ """, + ["master2.dotmaster"] = """ + @custom_resource_import test-resource3 + @viewModel object + + + + + + + + """ + }, renderResources: true); + check.CheckString(r.OutputString, fileExtension: "html"); + } + public class BasicTestViewModel: DotvvmViewModelBase { [Bind(Name = "int")] diff --git a/src/Tests/ControlTests/testoutputs/ServerSideStyleTests.AddResourceWithMasterPage.html b/src/Tests/ControlTests/testoutputs/ServerSideStyleTests.AddResourceWithMasterPage.html new file mode 100644 index 0000000000..d3ebdf9598 --- /dev/null +++ b/src/Tests/ControlTests/testoutputs/ServerSideStyleTests.AddResourceWithMasterPage.html @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + +
+ + real body + +
+ + + + + + + + + + + + +