Optimize RouteValueDictionary, expose concrete type
This change optimizes allocations by RouteValueDictionary based on usage. First, implement a struct Enumerator, and expose the concrete RVD type from all extensibility points. We wanted to try and decouple this code from RVD originally and use IDictionary everywhere. After doing that we've found that it allocates an unacceptable number of enumerators. Secondly, optimize copies of RVD for the case where you're copying an RVC to another (common case). When doing this we can copy the count to get the right capacity, and copy the entries without allocating an enumerator. Lastly, optimize RVD for the case where it's a wrapper around a poco object. We 'upgrade' to a writable full dictionary if you try to write to it, or call one of a number of APIs that are uncommonly used. We could produce optimized versions of things like `Keys` and `CopyTo` if necessary in the future.
This commit is contained in:
parent
cad81fa608
commit
d4b96b27c0
|
|
@ -311,8 +311,8 @@ namespace Microsoft.AspNet.Routing.Tree
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void MergeValues(
|
private static void MergeValues(
|
||||||
IDictionary<string, object> destination,
|
RouteValueDictionary destination,
|
||||||
IDictionary<string, object> values)
|
RouteValueDictionary values)
|
||||||
{
|
{
|
||||||
foreach (var kvp in values)
|
foreach (var kvp in values)
|
||||||
{
|
{
|
||||||
|
|
@ -328,7 +328,7 @@ namespace Microsoft.AspNet.Routing.Tree
|
||||||
|
|
||||||
private struct TemplateMatch : IEquatable<TemplateMatch>
|
private struct TemplateMatch : IEquatable<TemplateMatch>
|
||||||
{
|
{
|
||||||
public TemplateMatch(TreeRouteMatchingEntry entry, IDictionary<string, object> values)
|
public TemplateMatch(TreeRouteMatchingEntry entry, RouteValueDictionary values)
|
||||||
{
|
{
|
||||||
Entry = entry;
|
Entry = entry;
|
||||||
Values = values;
|
Values = values;
|
||||||
|
|
@ -336,7 +336,7 @@ namespace Microsoft.AspNet.Routing.Tree
|
||||||
|
|
||||||
public TreeRouteMatchingEntry Entry { get; }
|
public TreeRouteMatchingEntry Entry { get; }
|
||||||
|
|
||||||
public IDictionary<string, object> Values { get; }
|
public RouteValueDictionary Values { get; }
|
||||||
|
|
||||||
public override bool Equals(object obj)
|
public override bool Equals(object obj)
|
||||||
{
|
{
|
||||||
|
|
@ -403,7 +403,7 @@ namespace Microsoft.AspNet.Routing.Tree
|
||||||
// required values: { id = "5", action = "Buy", Controller = "CoolProducts" }
|
// required values: { id = "5", action = "Buy", Controller = "CoolProducts" }
|
||||||
//
|
//
|
||||||
// result: { id = "5", action = "Buy" }
|
// result: { id = "5", action = "Buy" }
|
||||||
var inputValues = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
var inputValues = new RouteValueDictionary();
|
||||||
foreach (var kvp in context.Values)
|
foreach (var kvp in context.Values)
|
||||||
{
|
{
|
||||||
if (entry.RequiredLinkValues.ContainsKey(kvp.Key))
|
if (entry.RequiredLinkValues.ContainsKey(kvp.Key))
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ namespace Microsoft.AspNet.Routing
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the set of values produced by routes on the current routing path.
|
/// Gets the set of values produced by routes on the current routing path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IDictionary<string, object> Values
|
public RouteValueDictionary Values
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using Microsoft.Extensions.Internal;
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Routing
|
namespace Microsoft.AspNet.Routing
|
||||||
{
|
{
|
||||||
|
|
@ -19,14 +18,11 @@ namespace Microsoft.AspNet.Routing
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static readonly IReadOnlyDictionary<string, object> Empty = new RouteValueDictionary();
|
internal static readonly IReadOnlyDictionary<string, object> Empty = new RouteValueDictionary();
|
||||||
|
|
||||||
private readonly Dictionary<string, object> _dictionary;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates an empty <see cref="RouteValueDictionary"/>.
|
/// Creates an empty <see cref="RouteValueDictionary"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public RouteValueDictionary()
|
public RouteValueDictionary()
|
||||||
{
|
{
|
||||||
_dictionary = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -43,46 +39,63 @@ namespace Microsoft.AspNet.Routing
|
||||||
/// Only public instance non-index properties are considered.
|
/// Only public instance non-index properties are considered.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public RouteValueDictionary(object values)
|
public RouteValueDictionary(object values)
|
||||||
: this()
|
|
||||||
{
|
{
|
||||||
if (values != null)
|
var otherDictionary = values as RouteValueDictionary;
|
||||||
|
if (otherDictionary != null)
|
||||||
{
|
{
|
||||||
var keyValuePairCollection = values as IEnumerable<KeyValuePair<string, object>>;
|
if (otherDictionary.InnerDictionary != null)
|
||||||
if (keyValuePairCollection != null)
|
|
||||||
{
|
{
|
||||||
foreach (var kvp in keyValuePairCollection)
|
InnerDictionary = new Dictionary<string, object>(
|
||||||
|
otherDictionary.InnerDictionary.Count,
|
||||||
|
StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
foreach (var kvp in otherDictionary.InnerDictionary)
|
||||||
{
|
{
|
||||||
Add(kvp.Key, kvp.Value);
|
InnerDictionary[kvp.Key] = kvp.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
else if (otherDictionary.Properties != null)
|
||||||
var type = values.GetType();
|
|
||||||
var allProperties = type.GetRuntimeProperties();
|
|
||||||
|
|
||||||
// This is done to support 'new' properties that hide a property on a base class
|
|
||||||
var orderedByDeclaringType = allProperties.OrderBy(p => p.DeclaringType == type ? 0 : 1);
|
|
||||||
foreach (var property in orderedByDeclaringType)
|
|
||||||
{
|
{
|
||||||
if (property.GetMethod != null &&
|
Properties = otherDictionary.Properties;
|
||||||
property.GetMethod.IsPublic &&
|
Value = otherDictionary.Value;
|
||||||
!property.GetMethod.IsStatic &&
|
return;
|
||||||
property.GetIndexParameters().Length == 0)
|
}
|
||||||
{
|
else
|
||||||
var value = property.GetValue(values);
|
{
|
||||||
if (ContainsKey(property.Name) && property.DeclaringType != type)
|
return;
|
||||||
{
|
|
||||||
// This is a hidden property, ignore it.
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Add(property.Name, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var keyValuePairCollection = values as IEnumerable<KeyValuePair<string, object>>;
|
||||||
|
if (keyValuePairCollection != null)
|
||||||
|
{
|
||||||
|
InnerDictionary = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
foreach (var kvp in keyValuePairCollection)
|
||||||
|
{
|
||||||
|
InnerDictionary[kvp.Key] = kvp.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (values != null)
|
||||||
|
{
|
||||||
|
Properties = PropertyHelper.GetVisibleProperties(values);
|
||||||
|
Value = values;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Dictionary<string, object> InnerDictionary { get; set; }
|
||||||
|
|
||||||
|
private PropertyHelper[] Properties { get; }
|
||||||
|
|
||||||
|
private object Value { get; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public object this[string key]
|
public object this[string key]
|
||||||
{
|
{
|
||||||
|
|
@ -94,7 +107,7 @@ namespace Microsoft.AspNet.Routing
|
||||||
}
|
}
|
||||||
|
|
||||||
object value;
|
object value;
|
||||||
_dictionary.TryGetValue(key, out value);
|
TryGetValue(key, out value);
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,7 +118,8 @@ namespace Microsoft.AspNet.Routing
|
||||||
throw new ArgumentNullException(nameof(key));
|
throw new ArgumentNullException(nameof(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
_dictionary[key] = value;
|
EnsureWritable();
|
||||||
|
InnerDictionary[key] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -115,47 +129,21 @@ namespace Microsoft.AspNet.Routing
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This will always be a reference to <see cref="StringComparer.OrdinalIgnoreCase"/>
|
/// This will always be a reference to <see cref="StringComparer.OrdinalIgnoreCase"/>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public IEqualityComparer<string> Comparer
|
public IEqualityComparer<string> Comparer => StringComparer.OrdinalIgnoreCase;
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _dictionary.Comparer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public int Count
|
public int Count => InnerDictionary?.Count ?? Properties?.Length ?? 0;
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _dictionary.Count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
bool ICollection<KeyValuePair<string, object>>.IsReadOnly
|
bool ICollection<KeyValuePair<string, object>>.IsReadOnly => false;
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return ((ICollection<KeyValuePair<string, object>>)_dictionary).IsReadOnly;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Dictionary<string, object>.KeyCollection Keys
|
public ICollection<string> Keys
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return _dictionary.Keys;
|
EnsureWritable();
|
||||||
}
|
return InnerDictionary.Keys;
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
ICollection<string> IDictionary<string, object>.Keys
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _dictionary.Keys;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -163,25 +151,18 @@ namespace Microsoft.AspNet.Routing
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return _dictionary.Keys;
|
EnsureWritable();
|
||||||
|
return InnerDictionary.Keys;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Dictionary<string, object>.ValueCollection Values
|
public ICollection<object> Values
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return _dictionary.Values;
|
EnsureWritable();
|
||||||
}
|
return InnerDictionary.Values;
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
ICollection<object> IDictionary<string, object>.Values
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _dictionary.Values;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -189,14 +170,16 @@ namespace Microsoft.AspNet.Routing
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return _dictionary.Values;
|
EnsureWritable();
|
||||||
|
return InnerDictionary.Values;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
|
void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
|
||||||
{
|
{
|
||||||
((ICollection<KeyValuePair<string, object>>)_dictionary).Add(item);
|
EnsureWritable();
|
||||||
|
((ICollection<KeyValuePair<string, object>>)InnerDictionary).Add(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -207,19 +190,22 @@ namespace Microsoft.AspNet.Routing
|
||||||
throw new ArgumentNullException(nameof(key));
|
throw new ArgumentNullException(nameof(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
_dictionary.Add(key, value);
|
EnsureWritable();
|
||||||
|
InnerDictionary.Add(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
_dictionary.Clear();
|
EnsureWritable();
|
||||||
|
InnerDictionary.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
|
bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
|
||||||
{
|
{
|
||||||
return ((ICollection<KeyValuePair<string, object>>)_dictionary).Contains(item);
|
EnsureWritable();
|
||||||
|
return ((ICollection<KeyValuePair<string, object>>)InnerDictionary).Contains(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -230,7 +216,27 @@ namespace Microsoft.AspNet.Routing
|
||||||
throw new ArgumentNullException(nameof(key));
|
throw new ArgumentNullException(nameof(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
return _dictionary.ContainsKey(key);
|
if (InnerDictionary != null)
|
||||||
|
{
|
||||||
|
return InnerDictionary.ContainsKey(key);
|
||||||
|
}
|
||||||
|
else if (Properties != null)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < Properties.Length; i++)
|
||||||
|
{
|
||||||
|
var property = Properties[i];
|
||||||
|
if (Comparer.Equals(property.Name, key))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -243,31 +249,33 @@ namespace Microsoft.AspNet.Routing
|
||||||
throw new ArgumentNullException(nameof(array));
|
throw new ArgumentNullException(nameof(array));
|
||||||
}
|
}
|
||||||
|
|
||||||
((ICollection<KeyValuePair<string, object>>)_dictionary).CopyTo(array, arrayIndex);
|
EnsureWritable();
|
||||||
|
((ICollection<KeyValuePair<string, object>>)InnerDictionary).CopyTo(array, arrayIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Dictionary<string, object>.Enumerator GetEnumerator()
|
public Enumerator GetEnumerator()
|
||||||
{
|
{
|
||||||
return _dictionary.GetEnumerator();
|
return new Enumerator(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator()
|
IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator()
|
||||||
{
|
{
|
||||||
return _dictionary.GetEnumerator();
|
return GetEnumerator();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
IEnumerator IEnumerable.GetEnumerator()
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
{
|
{
|
||||||
return _dictionary.GetEnumerator();
|
return GetEnumerator();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
|
bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
|
||||||
{
|
{
|
||||||
return ((ICollection<KeyValuePair<string, object>>)_dictionary).Remove(item);
|
EnsureWritable();
|
||||||
|
return ((ICollection<KeyValuePair<string, object>>)InnerDictionary).Remove(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -278,7 +286,8 @@ namespace Microsoft.AspNet.Routing
|
||||||
throw new ArgumentNullException(nameof(key));
|
throw new ArgumentNullException(nameof(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
return _dictionary.Remove(key);
|
EnsureWritable();
|
||||||
|
return InnerDictionary.Remove(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -289,7 +298,115 @@ namespace Microsoft.AspNet.Routing
|
||||||
throw new ArgumentNullException(nameof(key));
|
throw new ArgumentNullException(nameof(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
return _dictionary.TryGetValue(key, out value);
|
if (InnerDictionary != null)
|
||||||
|
{
|
||||||
|
return InnerDictionary.TryGetValue(key, out value);
|
||||||
|
}
|
||||||
|
else if (Properties != null)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < Properties.Length; i++)
|
||||||
|
{
|
||||||
|
var property = Properties[i];
|
||||||
|
if (Comparer.Equals(property.Name, key))
|
||||||
|
{
|
||||||
|
value = property.ValueGetter(Value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
value = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
value = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureWritable()
|
||||||
|
{
|
||||||
|
if (InnerDictionary == null && Properties == null)
|
||||||
|
{
|
||||||
|
InnerDictionary = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
else if (InnerDictionary == null)
|
||||||
|
{
|
||||||
|
InnerDictionary = new Dictionary<string, object>(Properties.Length, StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
for (var i = 0; i < Properties.Length; i++)
|
||||||
|
{
|
||||||
|
var property = Properties[i];
|
||||||
|
InnerDictionary.Add(property.Property.Name, property.ValueGetter(Value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct Enumerator : IEnumerator<KeyValuePair<string, object>>
|
||||||
|
{
|
||||||
|
private readonly RouteValueDictionary _dictionary;
|
||||||
|
|
||||||
|
private int _index;
|
||||||
|
private Dictionary<string, object>.Enumerator _enumerator;
|
||||||
|
|
||||||
|
public Enumerator(RouteValueDictionary dictionary)
|
||||||
|
{
|
||||||
|
if (dictionary == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException();
|
||||||
|
}
|
||||||
|
|
||||||
|
_dictionary = dictionary;
|
||||||
|
|
||||||
|
Current = default(KeyValuePair<string, object>);
|
||||||
|
_index = -1;
|
||||||
|
_enumerator = _dictionary.InnerDictionary == null ?
|
||||||
|
default(Dictionary<string, object>.Enumerator) :
|
||||||
|
_dictionary.InnerDictionary.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyValuePair<string, object> Current { get; private set; }
|
||||||
|
|
||||||
|
object IEnumerator.Current => Current;
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
if (_dictionary?.InnerDictionary != null)
|
||||||
|
{
|
||||||
|
if (_enumerator.MoveNext())
|
||||||
|
{
|
||||||
|
Current = _enumerator.Current;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (_dictionary?.Properties != null)
|
||||||
|
{
|
||||||
|
_index++;
|
||||||
|
if (_index < _dictionary.Properties.Length)
|
||||||
|
{
|
||||||
|
var property = _dictionary.Properties[_index];
|
||||||
|
var value = property.ValueGetter(_dictionary.Value);
|
||||||
|
Current = new KeyValuePair<string, object>(property.Name, value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Current = default(KeyValuePair<string, object>);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
Current = default(KeyValuePair<string, object>);
|
||||||
|
_index = -1;
|
||||||
|
_enumerator = _dictionary?.InnerDictionary == null ?
|
||||||
|
default(Dictionary<string, object>.Enumerator) :
|
||||||
|
_dictionary.InnerDictionary.GetEnumerator();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,10 +28,9 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 1: Get the list of values we're going to try to use to match and generate this URI
|
// Step 1: Get the list of values we're going to try to use to match and generate this URI
|
||||||
public TemplateValuesResult GetValues(IDictionary<string, object> ambientValues,
|
public TemplateValuesResult GetValues(RouteValueDictionary ambientValues, RouteValueDictionary values)
|
||||||
IDictionary<string, object> values)
|
|
||||||
{
|
{
|
||||||
var context = new TemplateBindingContext(_defaults, values);
|
var context = new TemplateBindingContext(_defaults);
|
||||||
|
|
||||||
// Find out which entries in the URI are valid for the URI we want to generate.
|
// Find out which entries in the URI are valid for the URI we want to generate.
|
||||||
// If the URI had ordered parameters a="1", b="2", c="3" and the new values
|
// If the URI had ordered parameters a="1", b="2", c="3" and the new values
|
||||||
|
|
@ -171,7 +170,7 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2: If the route is a match generate the appropriate URI
|
// Step 2: If the route is a match generate the appropriate URI
|
||||||
public string BindValues(IDictionary<string, object> acceptedValues)
|
public string BindValues(RouteValueDictionary acceptedValues)
|
||||||
{
|
{
|
||||||
var context = new UriBuildingContext();
|
var context = new UriBuildingContext();
|
||||||
|
|
||||||
|
|
@ -355,15 +354,8 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
private readonly RouteValueDictionary _acceptedValues;
|
private readonly RouteValueDictionary _acceptedValues;
|
||||||
private readonly RouteValueDictionary _filters;
|
private readonly RouteValueDictionary _filters;
|
||||||
|
|
||||||
public TemplateBindingContext(
|
public TemplateBindingContext(IReadOnlyDictionary<string, object> defaults)
|
||||||
IReadOnlyDictionary<string, object> defaults,
|
|
||||||
IDictionary<string, object> values)
|
|
||||||
{
|
{
|
||||||
if (values == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(values));
|
|
||||||
}
|
|
||||||
|
|
||||||
_defaults = defaults;
|
_defaults = defaults;
|
||||||
|
|
||||||
_acceptedValues = new RouteValueDictionary();
|
_acceptedValues = new RouteValueDictionary();
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
|
|
||||||
public RouteTemplate Template { get; private set; }
|
public RouteTemplate Template { get; private set; }
|
||||||
|
|
||||||
public IDictionary<string, object> Match(PathString path)
|
public RouteValueDictionary Match(PathString path)
|
||||||
{
|
{
|
||||||
var i = 0;
|
var i = 0;
|
||||||
var pathTokenizer = new PathTokenizer(path);
|
var pathTokenizer = new PathTokenizer(path);
|
||||||
|
|
|
||||||
|
|
@ -317,8 +317,8 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void MergeValues(
|
private static void MergeValues(
|
||||||
IDictionary<string, object> destination,
|
RouteValueDictionary destination,
|
||||||
IDictionary<string, object> values)
|
RouteValueDictionary values)
|
||||||
{
|
{
|
||||||
foreach (var kvp in values)
|
foreach (var kvp in values)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Routing.Template
|
namespace Microsoft.AspNet.Routing.Template
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -13,7 +11,7 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The set of values that will appear in the URL.
|
/// The set of values that will appear in the URL.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IDictionary<string, object> AcceptedValues { get; set; }
|
public RouteValueDictionary AcceptedValues { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The set of values that that were supplied for URL generation.
|
/// The set of values that that were supplied for URL generation.
|
||||||
|
|
@ -26,6 +24,6 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
/// Implicit (ambient) values which are invalidated due to changes in values lexically earlier in the
|
/// Implicit (ambient) values which are invalidated due to changes in values lexically earlier in the
|
||||||
/// route template are excluded from this set.
|
/// route template are excluded from this set.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public IDictionary<string, object> CombinedValues { get; set; }
|
public RouteValueDictionary CombinedValues { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -8,17 +8,19 @@ namespace Microsoft.AspNet.Routing
|
||||||
{
|
{
|
||||||
public class VirtualPathContext
|
public class VirtualPathContext
|
||||||
{
|
{
|
||||||
public VirtualPathContext(HttpContext httpContext,
|
public VirtualPathContext(
|
||||||
IDictionary<string, object> ambientValues,
|
HttpContext httpContext,
|
||||||
IDictionary<string, object> values)
|
RouteValueDictionary ambientValues,
|
||||||
|
RouteValueDictionary values)
|
||||||
: this(httpContext, ambientValues, values, null)
|
: this(httpContext, ambientValues, values, null)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public VirtualPathContext(HttpContext context,
|
public VirtualPathContext(
|
||||||
IDictionary<string, object> ambientValues,
|
HttpContext context,
|
||||||
IDictionary<string, object> values,
|
RouteValueDictionary ambientValues,
|
||||||
string routeName)
|
RouteValueDictionary values,
|
||||||
|
string routeName)
|
||||||
{
|
{
|
||||||
Context = context;
|
Context = context;
|
||||||
AmbientValues = ambientValues;
|
AmbientValues = ambientValues;
|
||||||
|
|
@ -26,16 +28,16 @@ namespace Microsoft.AspNet.Routing
|
||||||
RouteName = routeName;
|
RouteName = routeName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string RouteName { get; private set; }
|
public string RouteName { get; }
|
||||||
|
|
||||||
public IDictionary<string, object> ProvidedValues { get; set; }
|
public IDictionary<string, object> ProvidedValues { get; set; }
|
||||||
|
|
||||||
public IDictionary<string, object> AmbientValues { get; private set; }
|
public RouteValueDictionary AmbientValues { get; }
|
||||||
|
|
||||||
public HttpContext Context { get; private set; }
|
public HttpContext Context { get; }
|
||||||
|
|
||||||
public bool IsBound { get; set; }
|
public bool IsBound { get; set; }
|
||||||
|
|
||||||
public IDictionary<string, object> Values { get; private set; }
|
public RouteValueDictionary Values { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using Microsoft.AspNet.Http;
|
using Microsoft.AspNet.Http;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Routing
|
namespace Microsoft.AspNet.Routing
|
||||||
|
|
@ -13,7 +12,7 @@ namespace Microsoft.AspNet.Routing
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class VirtualPathData
|
public class VirtualPathData
|
||||||
{
|
{
|
||||||
private readonly IDictionary<string, object> _dataTokens;
|
private RouteValueDictionary _dataTokens;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="VirtualPathData"/> class.
|
/// Initializes a new instance of the <see cref="VirtualPathData"/> class.
|
||||||
|
|
@ -21,7 +20,7 @@ namespace Microsoft.AspNet.Routing
|
||||||
/// <param name="router">The object that is used to generate the URL.</param>
|
/// <param name="router">The object that is used to generate the URL.</param>
|
||||||
/// <param name="virtualPath">The generated URL.</param>
|
/// <param name="virtualPath">The generated URL.</param>
|
||||||
public VirtualPathData(IRouter router, string virtualPath)
|
public VirtualPathData(IRouter router, string virtualPath)
|
||||||
: this(router, virtualPath, dataTokens: new RouteValueDictionary())
|
: this(router, virtualPath, dataTokens: null)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -34,7 +33,7 @@ namespace Microsoft.AspNet.Routing
|
||||||
public VirtualPathData(
|
public VirtualPathData(
|
||||||
IRouter router,
|
IRouter router,
|
||||||
string virtualPath,
|
string virtualPath,
|
||||||
IDictionary<string, object> dataTokens)
|
RouteValueDictionary dataTokens)
|
||||||
: this(router, CreatePathString(virtualPath), dataTokens)
|
: this(router, CreatePathString(virtualPath), dataTokens)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
@ -48,7 +47,7 @@ namespace Microsoft.AspNet.Routing
|
||||||
public VirtualPathData(
|
public VirtualPathData(
|
||||||
IRouter router,
|
IRouter router,
|
||||||
PathString virtualPath,
|
PathString virtualPath,
|
||||||
IDictionary<string, object> dataTokens)
|
RouteValueDictionary dataTokens)
|
||||||
{
|
{
|
||||||
if (router == null)
|
if (router == null)
|
||||||
{
|
{
|
||||||
|
|
@ -57,23 +56,23 @@ namespace Microsoft.AspNet.Routing
|
||||||
|
|
||||||
Router = router;
|
Router = router;
|
||||||
VirtualPath = virtualPath;
|
VirtualPath = virtualPath;
|
||||||
|
_dataTokens = dataTokens == null ? null : new RouteValueDictionary(dataTokens);
|
||||||
_dataTokens = new RouteValueDictionary();
|
|
||||||
if (dataTokens != null)
|
|
||||||
{
|
|
||||||
foreach (var dataToken in dataTokens)
|
|
||||||
{
|
|
||||||
_dataTokens.Add(dataToken.Key, dataToken.Value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the collection of custom values for the <see cref="Router"/>.
|
/// Gets the collection of custom values for the <see cref="Router"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IDictionary<string, object> DataTokens
|
public RouteValueDictionary DataTokens
|
||||||
{
|
{
|
||||||
get { return _dataTokens; }
|
get
|
||||||
|
{
|
||||||
|
if (_dataTokens == null)
|
||||||
|
{
|
||||||
|
_dataTokens = new RouteValueDictionary();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _dataTokens;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -9,21 +9,25 @@
|
||||||
"warningsAsErrors": true,
|
"warningsAsErrors": true,
|
||||||
"keyFile": "../../tools/Key.snk"
|
"keyFile": "../../tools/Key.snk"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.AspNet.Http.Extensions": "1.0.0-*",
|
"Microsoft.AspNet.Http.Extensions": "1.0.0-*",
|
||||||
"Microsoft.AspNet.Routing.DecisionTree.Sources": {
|
"Microsoft.AspNet.Routing.DecisionTree.Sources": {
|
||||||
"type": "build",
|
"type": "build",
|
||||||
"version": "1.0.0-*"
|
"version": "1.0.0-*"
|
||||||
},
|
|
||||||
"Microsoft.Extensions.HashCodeCombiner.Sources": {
|
|
||||||
"type": "build",
|
|
||||||
"version": "1.0.0-*"
|
|
||||||
},
|
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "1.0.0-*",
|
|
||||||
"Microsoft.Extensions.OptionsModel": "1.0.0-*"
|
|
||||||
},
|
},
|
||||||
|
"Microsoft.Extensions.HashCodeCombiner.Sources": {
|
||||||
|
"type": "build",
|
||||||
|
"version": "1.0.0-*"
|
||||||
|
},
|
||||||
|
"Microsoft.Extensions.Logging.Abstractions": "1.0.0-*",
|
||||||
|
"Microsoft.Extensions.OptionsModel": "1.0.0-*",
|
||||||
|
"Microsoft.Extensions.PropertyHelper.Sources": {
|
||||||
|
"type": "build",
|
||||||
|
"version": "1.0.0-*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
"net451": {},
|
"net451": { },
|
||||||
"dotnet5.4": {
|
"dotnet5.4": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"System.Text.RegularExpressions": "4.0.11-*"
|
"System.Text.RegularExpressions": "4.0.11-*"
|
||||||
|
|
|
||||||
|
|
@ -165,7 +165,11 @@ namespace Microsoft.AspNet.Routing.Tests
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
ExceptionAssert.Throws<ArgumentException>(
|
ExceptionAssert.Throws<ArgumentException>(
|
||||||
() => new RouteValueDictionary(obj),
|
() =>
|
||||||
|
{
|
||||||
|
var dictionary = new RouteValueDictionary(obj);
|
||||||
|
dictionary.Add("Hi", "There");
|
||||||
|
},
|
||||||
expected);
|
expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
public void Binding_WithEmptyAndNull_DefaultValues(
|
public void Binding_WithEmptyAndNull_DefaultValues(
|
||||||
string template,
|
string template,
|
||||||
IReadOnlyDictionary<string, object> defaults,
|
IReadOnlyDictionary<string, object> defaults,
|
||||||
IDictionary<string, object> values,
|
RouteValueDictionary values,
|
||||||
string expected)
|
string expected)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
@ -284,8 +284,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
public void GetVirtualPathWithMultiSegmentWithOptionalParam(
|
public void GetVirtualPathWithMultiSegmentWithOptionalParam(
|
||||||
string template,
|
string template,
|
||||||
IReadOnlyDictionary<string, object> defaults,
|
IReadOnlyDictionary<string, object> defaults,
|
||||||
IDictionary<string, object> ambientValues,
|
RouteValueDictionary ambientValues,
|
||||||
IDictionary<string, object> values,
|
RouteValueDictionary values,
|
||||||
string expected)
|
string expected)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
@ -1086,8 +1086,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
private static void RunTest(
|
private static void RunTest(
|
||||||
string template,
|
string template,
|
||||||
IReadOnlyDictionary<string, object> defaults,
|
IReadOnlyDictionary<string, object> defaults,
|
||||||
IDictionary<string, object> ambientValues,
|
RouteValueDictionary ambientValues,
|
||||||
IDictionary<string, object> values,
|
RouteValueDictionary values,
|
||||||
string expected)
|
string expected)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
|
||||||
|
|
@ -1658,8 +1658,8 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
}
|
}
|
||||||
|
|
||||||
private static VirtualPathContext CreateVirtualPathContext(
|
private static VirtualPathContext CreateVirtualPathContext(
|
||||||
IDictionary<string, object> values,
|
RouteValueDictionary values,
|
||||||
IDictionary<string, object> ambientValues)
|
RouteValueDictionary ambientValues)
|
||||||
{
|
{
|
||||||
var context = new Mock<HttpContext>(MockBehavior.Strict);
|
var context = new Mock<HttpContext>(MockBehavior.Strict);
|
||||||
context.Setup(m => m.RequestServices.GetService(typeof(ILoggerFactory)))
|
context.Setup(m => m.RequestServices.GetService(typeof(ILoggerFactory)))
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue