diff --git a/src/Mvc/Mvc.Core/src/ModelBinding/PrefixContainer.cs b/src/Mvc/Mvc.Core/src/ModelBinding/PrefixContainer.cs index aeab151eb80f..a158522c092f 100644 --- a/src/Mvc/Mvc.Core/src/ModelBinding/PrefixContainer.cs +++ b/src/Mvc/Mvc.Core/src/ModelBinding/PrefixContainer.cs @@ -201,32 +201,40 @@ private int BinarySearch(string prefix) { Debug.Assert(candidate.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)); - // Okay, now we have a candidate that starts with the prefix. If the candidate is longer than - // the prefix, we need to look at the next character and see if it's a delimiter. - if (candidate.Length == prefix.Length) - { - // Exact match - return pivot; - } - - var c = candidate[prefix.Length]; - if (c == '.' || c == '[') + // At this point, we have identified a candidate that starts with the given prefix. + // If the candidate's length is greater than that of the prefix, we need to examine + // the character that immediately follows the prefix in the candidate string. + // This step is crucial to determine if the candidate is a valid prefix match. + // A valid prefix match occurs if the next character is either a delimiter ('.' or '['), + // indicating that the candidate is a sub-key of the prefix. If the next character + // is not a delimiter, it means the candidate contains additional characters that + // extend beyond the prefix without forming a valid hierarchical relationship, + // which should not qualify as a prefix match. Therefore, we will continue searching + // for valid matches in this case. + + if ((uint)candidate.Length > (uint)prefix.Length) { - // Match, followed by delimiter - return pivot; + var c = candidate[prefix.Length]; + if (c == '.' || c == '[') + { + // Match, followed by delimiter + return pivot; + } + + // Okay, so the candidate has some extra text. We need to keep searching. + // + // Can often assume the candidate string is greater than the prefix e.g. that works for + // prefix: product + // candidate: productId + // most of the time because "product", "product.id", etc. will sort earlier than "productId". But, + // the assumption isn't correct if "product[0]" is also in _sortedValues because that value will + // sort later than "productId". + // + // Fall back to brute force and cover all the cases. + return LinearSearch(prefix, start, end); } - // Okay, so the candidate has some extra text. We need to keep searching. - // - // Can often assume the candidate string is greater than the prefix e.g. that works for - // prefix: product - // candidate: productId - // most of the time because "product", "product.id", etc. will sort earlier than "productId". But, - // the assumption isn't correct if "product[0]" is also in _sortedValues because that value will - // sort later than "productId". - // - // Fall back to brute force and cover all the cases. - return LinearSearch(prefix, start, end); + return -1; } if (compare > 0) diff --git a/src/Mvc/Mvc.Core/test/ModelBinding/PrefixContainerTest.cs b/src/Mvc/Mvc.Core/test/ModelBinding/PrefixContainerTest.cs index 21fdd6c446ce..e8679baf72e1 100644 --- a/src/Mvc/Mvc.Core/test/ModelBinding/PrefixContainerTest.cs +++ b/src/Mvc/Mvc.Core/test/ModelBinding/PrefixContainerTest.cs @@ -180,6 +180,23 @@ public void ContainsPrefix_HasEntries_PartialAndPrefixMatch_WithSquareBrace(int Assert.True(result); } + [Theory] + [InlineData("parameter")] + [InlineData("foo")] + [InlineData("bar")] + public void ContainsPrefix_ReturnsFalse_WhenPrefixMatchesExactly_WithKeys(string prefix) + { + // Arrange + var keys = new string[] { "parameter", "foo", "bar" }; + var container = new PrefixContainer(keys); + + // Act + var result = container.ContainsPrefix(prefix); + + // Assert + Assert.False(result); + } + [Theory] [InlineData("")] [InlineData("foo")]