Speed up MSD.GetNode
This commit is contained in:
parent
8b99611c28
commit
a80d7d744a
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
|
|
@ -21,7 +22,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
/// The default value for <see cref="MaxAllowedErrors"/> of <c>200</c>.
|
/// The default value for <see cref="MaxAllowedErrors"/> of <c>200</c>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly int DefaultMaxAllowedErrors = 200;
|
public static readonly int DefaultMaxAllowedErrors = 200;
|
||||||
private static readonly char[] Delimiters = new char[] { '.', '[' };
|
|
||||||
|
private const char DelimiterDot = '.';
|
||||||
|
private const char DelimiterOpen = '[';
|
||||||
|
|
||||||
private readonly ModelStateNode _root;
|
private readonly ModelStateNode _root;
|
||||||
private int _maxAllowedErrors;
|
private int _maxAllowedErrors;
|
||||||
|
|
@ -502,18 +505,34 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ModelStateNode GetNode(string key) => GetNode(key, createIfNotExists: false);
|
private ModelStateNode GetNode(string key)
|
||||||
|
|
||||||
private ModelStateNode GetOrAddNode(string key) => GetNode(key, createIfNotExists: true);
|
|
||||||
|
|
||||||
private ModelStateNode GetNode(string key, bool createIfNotExists)
|
|
||||||
{
|
{
|
||||||
Debug.Assert(key != null);
|
Debug.Assert(key != null);
|
||||||
if (key.Length == 0)
|
|
||||||
|
var current = _root;
|
||||||
|
if (key.Length > 0)
|
||||||
{
|
{
|
||||||
return _root;
|
var match = default(MatchResult);
|
||||||
|
do
|
||||||
|
{
|
||||||
|
var subKey = FindNext(key, ref match);
|
||||||
|
current = current.GetNode(subKey);
|
||||||
|
|
||||||
|
// Path not found, exit early
|
||||||
|
if (current == null)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
} while (match.Type != Delimiter.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ModelStateNode GetOrAddNode(string key)
|
||||||
|
{
|
||||||
|
Debug.Assert(key != null);
|
||||||
// For a key of the format, foo.bar[0].baz[qux] we'll create the following nodes:
|
// For a key of the format, foo.bar[0].baz[qux] we'll create the following nodes:
|
||||||
// foo
|
// foo
|
||||||
// -> bar
|
// -> bar
|
||||||
|
|
@ -522,44 +541,58 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
// -> [qux]
|
// -> [qux]
|
||||||
|
|
||||||
var current = _root;
|
var current = _root;
|
||||||
var previousIndex = 0;
|
if (key.Length > 0)
|
||||||
int index;
|
|
||||||
while ((index = key.IndexOfAny(Delimiters, previousIndex)) != -1)
|
|
||||||
{
|
{
|
||||||
var keyStart = previousIndex == 0 || key[previousIndex - 1] == '.'
|
var match = default(MatchResult);
|
||||||
? previousIndex
|
do
|
||||||
: previousIndex - 1;
|
|
||||||
var subKey = new StringSegment(key, keyStart, index - keyStart);
|
|
||||||
current = current.GetNode(subKey, createIfNotExists);
|
|
||||||
if (current == null)
|
|
||||||
{
|
{
|
||||||
// createIfNotExists is set to false and a node wasn't found. Exit early.
|
var subKey = FindNext(key, ref match);
|
||||||
return null;
|
current = current.GetOrAddNode(subKey);
|
||||||
|
|
||||||
|
} while (match.Type != Delimiter.None);
|
||||||
|
|
||||||
|
if (current.Key == null)
|
||||||
|
{
|
||||||
|
// New Node - Set key
|
||||||
|
current.Key = key;
|
||||||
}
|
}
|
||||||
|
|
||||||
previousIndex = index + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (previousIndex < key.Length)
|
|
||||||
{
|
|
||||||
var keyStart = previousIndex == 0 || key[previousIndex - 1] == '.'
|
|
||||||
? previousIndex
|
|
||||||
: previousIndex - 1;
|
|
||||||
var subKey = new StringSegment(key, keyStart, key.Length - keyStart);
|
|
||||||
current = current.GetNode(subKey, createIfNotExists);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current != null && current.Key == null)
|
|
||||||
{
|
|
||||||
// Don't update the key if it's been previously assigned. This is to prevent change in key casing
|
|
||||||
// e.g. modelState.SetModelValue("foo", .., ..);
|
|
||||||
// var value = modelState["FOO"];
|
|
||||||
current.Key = key;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shared function factored out for clarity, force inlining to put back in
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static StringSegment FindNext(string key, ref MatchResult currentMatch)
|
||||||
|
{
|
||||||
|
var index = currentMatch.Index;
|
||||||
|
var matchType = Delimiter.None;
|
||||||
|
|
||||||
|
for (; index < key.Length; index++)
|
||||||
|
{
|
||||||
|
var ch = key[index];
|
||||||
|
if (ch == DelimiterDot)
|
||||||
|
{
|
||||||
|
matchType = Delimiter.Dot;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (ch == DelimiterOpen)
|
||||||
|
{
|
||||||
|
matchType = Delimiter.OpenBracket;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var keyStart = currentMatch.Type == Delimiter.OpenBracket
|
||||||
|
? currentMatch.Index - 1
|
||||||
|
: currentMatch.Index;
|
||||||
|
|
||||||
|
currentMatch.Type = matchType;
|
||||||
|
currentMatch.Index = index + 1;
|
||||||
|
|
||||||
|
return new StringSegment(key, keyStart, index - keyStart);
|
||||||
|
}
|
||||||
|
|
||||||
private static ModelValidationState? GetValidity(ModelStateNode node)
|
private static ModelValidationState? GetValidity(ModelStateNode node)
|
||||||
{
|
{
|
||||||
if (node == null)
|
if (node == null)
|
||||||
|
|
@ -757,6 +790,19 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
return new PrefixEnumerable(this, prefix);
|
return new PrefixEnumerable(this, prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct MatchResult
|
||||||
|
{
|
||||||
|
public Delimiter Type;
|
||||||
|
public int Index;
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum Delimiter
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Dot,
|
||||||
|
OpenBracket
|
||||||
|
}
|
||||||
|
|
||||||
[DebuggerDisplay("SubKey={SubKey}, Key={Key}, ValidationState={ValidationState}")]
|
[DebuggerDisplay("SubKey={SubKey}, Key={Key}, ValidationState={ValidationState}")]
|
||||||
private class ModelStateNode : ModelStateEntry
|
private class ModelStateNode : ModelStateEntry
|
||||||
{
|
{
|
||||||
|
|
@ -804,42 +850,63 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
Errors.Clear();
|
Errors.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ModelStateNode GetNode(StringSegment subKey, bool createIfNotExists)
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public ModelStateNode GetNode(StringSegment subKey)
|
||||||
{
|
{
|
||||||
|
ModelStateNode modelStateNode = null;
|
||||||
if (subKey.Length == 0)
|
if (subKey.Length == 0)
|
||||||
{
|
{
|
||||||
return this;
|
modelStateNode = this;
|
||||||
}
|
}
|
||||||
|
else if (ChildNodes != null)
|
||||||
var index = BinarySearch(subKey);
|
|
||||||
ModelStateNode modelStateNode = null;
|
|
||||||
if (index >= 0)
|
|
||||||
{
|
{
|
||||||
modelStateNode = ChildNodes[index];
|
var index = BinarySearch(subKey);
|
||||||
}
|
if (index >= 0)
|
||||||
else if (createIfNotExists)
|
|
||||||
{
|
|
||||||
if (ChildNodes == null)
|
|
||||||
{
|
{
|
||||||
ChildNodes = new List<ModelStateNode>(1);
|
modelStateNode = ChildNodes[index];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return modelStateNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public ModelStateNode GetOrAddNode(StringSegment subKey)
|
||||||
|
{
|
||||||
|
ModelStateNode modelStateNode;
|
||||||
|
if (subKey.Length == 0)
|
||||||
|
{
|
||||||
|
modelStateNode = this;
|
||||||
|
}
|
||||||
|
else if (ChildNodes == null)
|
||||||
|
{
|
||||||
|
ChildNodes = new List<ModelStateNode>(1);
|
||||||
modelStateNode = new ModelStateNode(subKey);
|
modelStateNode = new ModelStateNode(subKey);
|
||||||
ChildNodes.Insert(~index, modelStateNode);
|
ChildNodes.Add(modelStateNode);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var index = BinarySearch(subKey);
|
||||||
|
if (index >= 0)
|
||||||
|
{
|
||||||
|
modelStateNode = ChildNodes[index];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
modelStateNode = new ModelStateNode(subKey);
|
||||||
|
ChildNodes.Insert(~index, modelStateNode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return modelStateNode;
|
return modelStateNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override ModelStateEntry GetModelStateForProperty(string propertyName)
|
public override ModelStateEntry GetModelStateForProperty(string propertyName)
|
||||||
=> GetNode(new StringSegment(propertyName), createIfNotExists: false);
|
=> GetNode(new StringSegment(propertyName));
|
||||||
|
|
||||||
private int BinarySearch(StringSegment searchKey)
|
private int BinarySearch(StringSegment searchKey)
|
||||||
{
|
{
|
||||||
if (ChildNodes == null)
|
Debug.Assert(ChildNodes != null);
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var low = 0;
|
var low = 0;
|
||||||
var high = ChildNodes.Count - 1;
|
var high = ChildNodes.Count - 1;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue