diff --git a/src/Components/Endpoints/src/FormMapping/PrefixResolver.cs b/src/Components/Endpoints/src/FormMapping/PrefixResolver.cs
index 64d2a8d8f259..6b6775af4be3 100644
--- a/src/Components/Endpoints/src/FormMapping/PrefixResolver.cs
+++ b/src/Components/Endpoints/src/FormMapping/PrefixResolver.cs
@@ -58,39 +58,38 @@ public int Compare(FormKey x, FormKey y)
{
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
+
+
@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; + } +}