From f5bf6040d74dce50911969e8c42b264e0c253473 Mon Sep 17 00:00:00 2001 From: Daria Tiurina Date: Thu, 14 Aug 2025 14:21:30 +0200 Subject: [PATCH 1/3] Fix in PrefixResolver --- .../src/FormMapping/PrefixResolver.cs | 38 ++++++++--------- .../test/Binding/PrefixResolverTests.cs | 34 +++++++++++++++ .../FormWithParentBindingContextTest.cs | 10 +++++ .../Forms/RecursiveEditFormBinding.razor | 42 +++++++++++++++++++ 4 files changed, 103 insertions(+), 21 deletions(-) create mode 100644 src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/RecursiveEditFormBinding.razor diff --git a/src/Components/Endpoints/src/FormMapping/PrefixResolver.cs b/src/Components/Endpoints/src/FormMapping/PrefixResolver.cs index 64d2a8d8f259..297662c0f4e3 100644 --- a/src/Components/Endpoints/src/FormMapping/PrefixResolver.cs +++ b/src/Components/Endpoints/src/FormMapping/PrefixResolver.cs @@ -50,47 +50,43 @@ private class FormKeyComparer(bool checkPrefix) : IComparer // When comparing values, y is the element we are trying to find. public int Compare(FormKey x, FormKey y) { - var separatorX = 0; - var separatorY = 0; - var currentXPos = 0; - var currentYPos = 0; + int separatorX = 0, separatorY = 0, currentXPos = 0, currentYPos = 0; while (separatorX != -1 && separatorY != -1) { separatorX = x.Value.Span[currentXPos..].IndexOfAny('.', '['); separatorY = y.Value.Span[currentYPos..].IndexOfAny('.', '['); + int compare; if (separatorX == -1 && separatorY == -1) { - // no more segments, compare the remaining substrings + // Both x and y have no more segments, compare the remaining segments return MemoryExtensions.CompareTo(x.Value.Span[currentXPos..], y.Value.Span[currentYPos..], StringComparison.Ordinal); } else if (separatorX == -1) { - // x has no more segments, but y does, so x is less than y - return -1; + // x has no more segments, y has remaining segments + compare = MemoryExtensions.CompareTo(x.Value.Span[currentXPos..], y.Value.Span[currentYPos..][..separatorY], StringComparison.Ordinal); + if (compare == 0 && !checkPrefix) + { + return -1; + } + return compare; } else if (separatorY == -1) { - if (!checkPrefix) + // y has no more segments, x has remaining segments + compare = MemoryExtensions.CompareTo(x.Value.Span[currentXPos..][..separatorX], y.Value.Span[currentYPos..], StringComparison.Ordinal); + if (compare == 0 && !checkPrefix) { - // We are just sorting, so x is greater than y because it has more segments. return 1; } - - var match = MemoryExtensions.CompareTo( - x.Value.Span[currentXPos..][..separatorX], - y.Value.Span[currentYPos..], StringComparison.Ordinal); - - return match; + return compare; } - // both have segments, compare the segments - var segmentX = x.Value.Span[currentXPos..][..separatorX]; - var segmentY = y.Value.Span[currentYPos..][..separatorY]; - var compareResult = MemoryExtensions.CompareTo(segmentX, segmentY, StringComparison.Ordinal); - if (compareResult != 0) + compare = MemoryExtensions.CompareTo(x.Value.Span[currentXPos..][..separatorX], y.Value.Span[currentYPos..][..separatorY], StringComparison.Ordinal); + if (compare != 0) { - return compareResult; + return compare; } currentXPos += separatorX + 1; diff --git a/src/Components/Endpoints/test/Binding/PrefixResolverTests.cs b/src/Components/Endpoints/test/Binding/PrefixResolverTests.cs index 0b710c020706..479a621e5797 100644 --- a/src/Components/Endpoints/test/Binding/PrefixResolverTests.cs +++ b/src/Components/Endpoints/test/Binding/PrefixResolverTests.cs @@ -79,6 +79,40 @@ public void ContainsPrefix_HasEntries_NoMatch(string prefix) Assert.False(result); } + [Theory] + [InlineData("Model")] + [InlineData("Model.Model")] + [InlineData("Model[0].Model")] + public void ContainsPrefix_HasEntries(string prefix) + { + // Arrange - Simulating the exact scenario from debugging + var keys = new string[] { "__RequestVerificationToken", "_handler", "Model.Name", "Model.Model.Name", "Model[0].Model.Name", "Model[0].Name" }; + var container = new PrefixResolver(GetKeys(keys), keys.Length); + + // Act + var result = container.HasPrefix(prefix.AsMemory()); + + // Assert + Assert.True(result); + } + + [Theory] + [InlineData("Model")] + [InlineData("Model.Model")] + [InlineData("Model[0].Model")] + public void ContainsPrefix_DoesNotHaveEntries(string prefix) + { + // Arrange - Simulating the exact scenario from debugging + var keys = new string[] { "__RequestVerificationToken", "_handler" }; + var container = new PrefixResolver(GetKeys(keys), keys.Length); + + // Act + var result = container.HasPrefix(prefix.AsMemory()); + + // Assert + Assert.False(result); + } + [Theory] [InlineData("a")] [InlineData("b")] diff --git a/src/Components/test/E2ETest/ServerRenderingTests/FormHandlingTests/FormWithParentBindingContextTest.cs b/src/Components/test/E2ETest/ServerRenderingTests/FormHandlingTests/FormWithParentBindingContextTest.cs index d6dc438db468..fa3e1a287915 100644 --- a/src/Components/test/E2ETest/ServerRenderingTests/FormHandlingTests/FormWithParentBindingContextTest.cs +++ b/src/Components/test/E2ETest/ServerRenderingTests/FormHandlingTests/FormWithParentBindingContextTest.cs @@ -1422,6 +1422,16 @@ void AssertUiState(string expectedStringValue, bool expectedBoolValue) } } + [Fact] + public void EditFormRecursiveBinding() + { + GoTo("forms/recursive-edit-form"); + Browser.Equal("", () => Browser.Exists(By.Id("result-form")).Text); + Browser.Exists(By.Id("text-input")).SendKeys("John"); + Browser.Exists(By.Id("submit-button")).Click(); + Browser.Equal("John", () => Browser.Exists(By.Id("result-form")).Text); + } + [Fact] public void RadioButtonGetsResetAfterSubmittingEnhancedForm() { diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/RecursiveEditFormBinding.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/RecursiveEditFormBinding.razor new file mode 100644 index 000000000000..29b074e41d04 --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms/RecursiveEditFormBinding.razor @@ -0,0 +1,42 @@ +@page "/forms/recursive-edit-form" +@using Microsoft.AspNetCore.Components.Forms + +Home + + + + + + +

@ModelName

+ +@code { + string ModelName; + + [SupplyParameterFromForm(FormName = "RequestForm")] + private MyModel? Model { get; set; } + + private EditContext FormContext = null!; + + public class MyModel + { + public string? Name { get; set; } + + public MyModel? Parent { get; set; } + } + + protected override void OnInitialized() + { + if (Model == null) + { + Model = new(); + } + + FormContext = new EditContext(Model); + } + + private void Submit() + { + ModelName = Model?.Name ?? string.Empty; + } +} From 4aa4eb3372b368d9ad89c3a7f0364d9a83f81947 Mon Sep 17 00:00:00 2001 From: Daria Tiurina Date: Wed, 20 Aug 2025 09:41:11 +0200 Subject: [PATCH 2/3] Fix small errors --- .../Endpoints/src/FormMapping/PrefixResolver.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Components/Endpoints/src/FormMapping/PrefixResolver.cs b/src/Components/Endpoints/src/FormMapping/PrefixResolver.cs index 297662c0f4e3..902a507b60d9 100644 --- a/src/Components/Endpoints/src/FormMapping/PrefixResolver.cs +++ b/src/Components/Endpoints/src/FormMapping/PrefixResolver.cs @@ -50,7 +50,10 @@ private class FormKeyComparer(bool checkPrefix) : IComparer // When comparing values, y is the element we are trying to find. public int Compare(FormKey x, FormKey y) { - int separatorX = 0, separatorY = 0, currentXPos = 0, currentYPos = 0; + var separatorX = 0; + var separatorY = 0; + var currentXPos = 0; + var currentYPos = 0; while (separatorX != -1 && separatorY != -1) { separatorX = x.Value.Span[currentXPos..].IndexOfAny('.', '['); @@ -65,7 +68,9 @@ public int Compare(FormKey x, FormKey y) else if (separatorX == -1) { // x has no more segments, y has remaining segments - compare = MemoryExtensions.CompareTo(x.Value.Span[currentXPos..], y.Value.Span[currentYPos..][..separatorY], StringComparison.Ordinal); + var segmentX = x.Value.Span[currentXPos..]; + var segmentY = y.Value.Span[currentYPos..][..separatorY]; + compare = MemoryExtensions.CompareTo(segmentX, segmentY, StringComparison.Ordinal); if (compare == 0 && !checkPrefix) { return -1; @@ -75,7 +80,9 @@ public int Compare(FormKey x, FormKey y) else if (separatorY == -1) { // y has no more segments, x has remaining segments - compare = MemoryExtensions.CompareTo(x.Value.Span[currentXPos..][..separatorX], y.Value.Span[currentYPos..], StringComparison.Ordinal); + var segmentX = x.Value.Span[currentXPos..][..separatorX]; + var segmentY = y.Value.Span[currentYPos..]; + compare = MemoryExtensions.CompareTo(segmentX, segmentY, StringComparison.Ordinal); if (compare == 0 && !checkPrefix) { return 1; @@ -83,6 +90,8 @@ public int Compare(FormKey x, FormKey y) return compare; } + var segmentX = x.Value.Span[currentXPos..][..separatorX]; + var segmentY = y.Value.Span[currentYPos..][..separatorY]; compare = MemoryExtensions.CompareTo(x.Value.Span[currentXPos..][..separatorX], y.Value.Span[currentYPos..][..separatorY], StringComparison.Ordinal); if (compare != 0) { From d0c0ebaa804a8e8233ba2276d800e41283c69384 Mon Sep 17 00:00:00 2001 From: Daria Tiurina Date: Wed, 20 Aug 2025 10:08:06 +0200 Subject: [PATCH 3/3] Fix --- .../Endpoints/src/FormMapping/PrefixResolver.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/Components/Endpoints/src/FormMapping/PrefixResolver.cs b/src/Components/Endpoints/src/FormMapping/PrefixResolver.cs index 902a507b60d9..6b6775af4be3 100644 --- a/src/Components/Endpoints/src/FormMapping/PrefixResolver.cs +++ b/src/Components/Endpoints/src/FormMapping/PrefixResolver.cs @@ -68,9 +68,7 @@ public int Compare(FormKey x, FormKey y) else if (separatorX == -1) { // x has no more segments, y has remaining segments - var segmentX = x.Value.Span[currentXPos..]; - var segmentY = y.Value.Span[currentYPos..][..separatorY]; - compare = MemoryExtensions.CompareTo(segmentX, segmentY, StringComparison.Ordinal); + compare = MemoryExtensions.CompareTo(x.Value.Span[currentXPos..], y.Value.Span[currentYPos..][..separatorY], StringComparison.Ordinal); if (compare == 0 && !checkPrefix) { return -1; @@ -80,9 +78,7 @@ public int Compare(FormKey x, FormKey y) else if (separatorY == -1) { // y has no more segments, x has remaining segments - var segmentX = x.Value.Span[currentXPos..][..separatorX]; - var segmentY = y.Value.Span[currentYPos..]; - compare = MemoryExtensions.CompareTo(segmentX, segmentY, StringComparison.Ordinal); + compare = MemoryExtensions.CompareTo(x.Value.Span[currentXPos..][..separatorX], y.Value.Span[currentYPos..], StringComparison.Ordinal); if (compare == 0 && !checkPrefix) { return 1; @@ -90,8 +86,6 @@ public int Compare(FormKey x, FormKey y) return compare; } - var segmentX = x.Value.Span[currentXPos..][..separatorX]; - var segmentY = y.Value.Span[currentYPos..][..separatorY]; compare = MemoryExtensions.CompareTo(x.Value.Span[currentXPos..][..separatorX], y.Value.Span[currentYPos..][..separatorY], StringComparison.Ordinal); if (compare != 0) {