From d4b96b27c0a7ab051b6b90a72a131e5042e141be Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Tue, 24 Nov 2015 10:58:22 -0800 Subject: [PATCH] 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. --- .../AttributeRouting/TreeRouter.cs | 10 +- src/Microsoft.AspNet.Routing/RouteData.cs | 2 +- .../RouteValueDictionary.cs | 303 ++++++++++++------ .../Template/TemplateBinder.cs | 16 +- .../Template/TemplateMatcher.cs | 2 +- .../Template/TemplateRoute.cs | 4 +- .../Template/TemplateValuesResult.cs | 6 +- .../VirtualPathContext.cs | 24 +- .../VirtualPathData.cs | 31 +- src/Microsoft.AspNet.Routing/project.json | 30 +- .../RouteValueDictionaryTests.cs | 6 +- .../Template/TemplateBinderTests.cs | 10 +- .../Template/TemplateRouteTest.cs | 4 +- 13 files changed, 282 insertions(+), 166 deletions(-) diff --git a/src/Microsoft.AspNet.Routing/AttributeRouting/TreeRouter.cs b/src/Microsoft.AspNet.Routing/AttributeRouting/TreeRouter.cs index 2449303da1..70163d2280 100644 --- a/src/Microsoft.AspNet.Routing/AttributeRouting/TreeRouter.cs +++ b/src/Microsoft.AspNet.Routing/AttributeRouting/TreeRouter.cs @@ -311,8 +311,8 @@ namespace Microsoft.AspNet.Routing.Tree } private static void MergeValues( - IDictionary destination, - IDictionary values) + RouteValueDictionary destination, + RouteValueDictionary values) { foreach (var kvp in values) { @@ -328,7 +328,7 @@ namespace Microsoft.AspNet.Routing.Tree private struct TemplateMatch : IEquatable { - public TemplateMatch(TreeRouteMatchingEntry entry, IDictionary values) + public TemplateMatch(TreeRouteMatchingEntry entry, RouteValueDictionary values) { Entry = entry; Values = values; @@ -336,7 +336,7 @@ namespace Microsoft.AspNet.Routing.Tree public TreeRouteMatchingEntry Entry { get; } - public IDictionary Values { get; } + public RouteValueDictionary Values { get; } public override bool Equals(object obj) { @@ -403,7 +403,7 @@ namespace Microsoft.AspNet.Routing.Tree // required values: { id = "5", action = "Buy", Controller = "CoolProducts" } // // result: { id = "5", action = "Buy" } - var inputValues = new Dictionary(StringComparer.OrdinalIgnoreCase); + var inputValues = new RouteValueDictionary(); foreach (var kvp in context.Values) { if (entry.RequiredLinkValues.ContainsKey(kvp.Key)) diff --git a/src/Microsoft.AspNet.Routing/RouteData.cs b/src/Microsoft.AspNet.Routing/RouteData.cs index dbc4da9d6b..89104497e1 100644 --- a/src/Microsoft.AspNet.Routing/RouteData.cs +++ b/src/Microsoft.AspNet.Routing/RouteData.cs @@ -72,7 +72,7 @@ namespace Microsoft.AspNet.Routing /// /// Gets the set of values produced by routes on the current routing path. /// - public IDictionary Values + public RouteValueDictionary Values { get { diff --git a/src/Microsoft.AspNet.Routing/RouteValueDictionary.cs b/src/Microsoft.AspNet.Routing/RouteValueDictionary.cs index 203bcc13b0..53b55cbfad 100644 --- a/src/Microsoft.AspNet.Routing/RouteValueDictionary.cs +++ b/src/Microsoft.AspNet.Routing/RouteValueDictionary.cs @@ -4,8 +4,7 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Linq; -using System.Reflection; +using Microsoft.Extensions.Internal; namespace Microsoft.AspNet.Routing { @@ -19,14 +18,11 @@ namespace Microsoft.AspNet.Routing /// internal static readonly IReadOnlyDictionary Empty = new RouteValueDictionary(); - private readonly Dictionary _dictionary; - /// /// Creates an empty . /// public RouteValueDictionary() { - _dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); } /// @@ -43,46 +39,63 @@ namespace Microsoft.AspNet.Routing /// Only public instance non-index properties are considered. /// public RouteValueDictionary(object values) - : this() { - if (values != null) + var otherDictionary = values as RouteValueDictionary; + if (otherDictionary != null) { - var keyValuePairCollection = values as IEnumerable>; - if (keyValuePairCollection != null) + if (otherDictionary.InnerDictionary != null) { - foreach (var kvp in keyValuePairCollection) + InnerDictionary = new Dictionary( + otherDictionary.InnerDictionary.Count, + StringComparer.OrdinalIgnoreCase); + + foreach (var kvp in otherDictionary.InnerDictionary) { - Add(kvp.Key, kvp.Value); + InnerDictionary[kvp.Key] = kvp.Value; } + return; } - - 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) + else if (otherDictionary.Properties != null) { - if (property.GetMethod != null && - property.GetMethod.IsPublic && - !property.GetMethod.IsStatic && - property.GetIndexParameters().Length == 0) - { - var value = property.GetValue(values); - if (ContainsKey(property.Name) && property.DeclaringType != type) - { - // This is a hidden property, ignore it. - } - else - { - Add(property.Name, value); - } - } + Properties = otherDictionary.Properties; + Value = otherDictionary.Value; + return; + } + else + { + return; } } + + var keyValuePairCollection = values as IEnumerable>; + if (keyValuePairCollection != null) + { + InnerDictionary = new Dictionary(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 InnerDictionary { get; set; } + + private PropertyHelper[] Properties { get; } + + private object Value { get; } + /// public object this[string key] { @@ -94,7 +107,7 @@ namespace Microsoft.AspNet.Routing } object value; - _dictionary.TryGetValue(key, out value); + TryGetValue(key, out value); return value; } @@ -105,7 +118,8 @@ namespace Microsoft.AspNet.Routing throw new ArgumentNullException(nameof(key)); } - _dictionary[key] = value; + EnsureWritable(); + InnerDictionary[key] = value; } } @@ -115,47 +129,21 @@ namespace Microsoft.AspNet.Routing /// /// This will always be a reference to /// - public IEqualityComparer Comparer - { - get - { - return _dictionary.Comparer; - } - } + public IEqualityComparer Comparer => StringComparer.OrdinalIgnoreCase; /// - public int Count - { - get - { - return _dictionary.Count; - } - } + public int Count => InnerDictionary?.Count ?? Properties?.Length ?? 0; /// - bool ICollection>.IsReadOnly - { - get - { - return ((ICollection>)_dictionary).IsReadOnly; - } - } + bool ICollection>.IsReadOnly => false; /// - public Dictionary.KeyCollection Keys + public ICollection Keys { get { - return _dictionary.Keys; - } - } - - /// - ICollection IDictionary.Keys - { - get - { - return _dictionary.Keys; + EnsureWritable(); + return InnerDictionary.Keys; } } @@ -163,25 +151,18 @@ namespace Microsoft.AspNet.Routing { get { - return _dictionary.Keys; + EnsureWritable(); + return InnerDictionary.Keys; } } /// - public Dictionary.ValueCollection Values + public ICollection Values { get { - return _dictionary.Values; - } - } - - /// - ICollection IDictionary.Values - { - get - { - return _dictionary.Values; + EnsureWritable(); + return InnerDictionary.Values; } } @@ -189,14 +170,16 @@ namespace Microsoft.AspNet.Routing { get { - return _dictionary.Values; + EnsureWritable(); + return InnerDictionary.Values; } } /// void ICollection>.Add(KeyValuePair item) { - ((ICollection>)_dictionary).Add(item); + EnsureWritable(); + ((ICollection>)InnerDictionary).Add(item); } /// @@ -207,19 +190,22 @@ namespace Microsoft.AspNet.Routing throw new ArgumentNullException(nameof(key)); } - _dictionary.Add(key, value); + EnsureWritable(); + InnerDictionary.Add(key, value); } /// public void Clear() { - _dictionary.Clear(); + EnsureWritable(); + InnerDictionary.Clear(); } /// bool ICollection>.Contains(KeyValuePair item) { - return ((ICollection>)_dictionary).Contains(item); + EnsureWritable(); + return ((ICollection>)InnerDictionary).Contains(item); } /// @@ -230,7 +216,27 @@ namespace Microsoft.AspNet.Routing 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; + } } /// @@ -243,31 +249,33 @@ namespace Microsoft.AspNet.Routing throw new ArgumentNullException(nameof(array)); } - ((ICollection>)_dictionary).CopyTo(array, arrayIndex); + EnsureWritable(); + ((ICollection>)InnerDictionary).CopyTo(array, arrayIndex); } /// - public Dictionary.Enumerator GetEnumerator() + public Enumerator GetEnumerator() { - return _dictionary.GetEnumerator(); + return new Enumerator(this); } /// IEnumerator> IEnumerable>.GetEnumerator() { - return _dictionary.GetEnumerator(); + return GetEnumerator(); } /// IEnumerator IEnumerable.GetEnumerator() { - return _dictionary.GetEnumerator(); + return GetEnumerator(); } /// bool ICollection>.Remove(KeyValuePair item) { - return ((ICollection>)_dictionary).Remove(item); + EnsureWritable(); + return ((ICollection>)InnerDictionary).Remove(item); } /// @@ -278,7 +286,8 @@ namespace Microsoft.AspNet.Routing throw new ArgumentNullException(nameof(key)); } - return _dictionary.Remove(key); + EnsureWritable(); + return InnerDictionary.Remove(key); } /// @@ -289,7 +298,115 @@ namespace Microsoft.AspNet.Routing 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(StringComparer.OrdinalIgnoreCase); + } + else if (InnerDictionary == null) + { + InnerDictionary = new Dictionary(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> + { + private readonly RouteValueDictionary _dictionary; + + private int _index; + private Dictionary.Enumerator _enumerator; + + public Enumerator(RouteValueDictionary dictionary) + { + if (dictionary == null) + { + throw new ArgumentNullException(); + } + + _dictionary = dictionary; + + Current = default(KeyValuePair); + _index = -1; + _enumerator = _dictionary.InnerDictionary == null ? + default(Dictionary.Enumerator) : + _dictionary.InnerDictionary.GetEnumerator(); + } + + public KeyValuePair 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(property.Name, value); + return true; + } + } + + Current = default(KeyValuePair); + return false; + } + + public void Reset() + { + Current = default(KeyValuePair); + _index = -1; + _enumerator = _dictionary?.InnerDictionary == null ? + default(Dictionary.Enumerator) : + _dictionary.InnerDictionary.GetEnumerator(); + } } } } diff --git a/src/Microsoft.AspNet.Routing/Template/TemplateBinder.cs b/src/Microsoft.AspNet.Routing/Template/TemplateBinder.cs index ce64b9959f..183effd021 100644 --- a/src/Microsoft.AspNet.Routing/Template/TemplateBinder.cs +++ b/src/Microsoft.AspNet.Routing/Template/TemplateBinder.cs @@ -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 - public TemplateValuesResult GetValues(IDictionary ambientValues, - IDictionary values) + public TemplateValuesResult GetValues(RouteValueDictionary ambientValues, RouteValueDictionary 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. // 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 - public string BindValues(IDictionary acceptedValues) + public string BindValues(RouteValueDictionary acceptedValues) { var context = new UriBuildingContext(); @@ -355,15 +354,8 @@ namespace Microsoft.AspNet.Routing.Template private readonly RouteValueDictionary _acceptedValues; private readonly RouteValueDictionary _filters; - public TemplateBindingContext( - IReadOnlyDictionary defaults, - IDictionary values) + public TemplateBindingContext(IReadOnlyDictionary defaults) { - if (values == null) - { - throw new ArgumentNullException(nameof(values)); - } - _defaults = defaults; _acceptedValues = new RouteValueDictionary(); diff --git a/src/Microsoft.AspNet.Routing/Template/TemplateMatcher.cs b/src/Microsoft.AspNet.Routing/Template/TemplateMatcher.cs index cc32a00f04..e20bc5316c 100644 --- a/src/Microsoft.AspNet.Routing/Template/TemplateMatcher.cs +++ b/src/Microsoft.AspNet.Routing/Template/TemplateMatcher.cs @@ -33,7 +33,7 @@ namespace Microsoft.AspNet.Routing.Template public RouteTemplate Template { get; private set; } - public IDictionary Match(PathString path) + public RouteValueDictionary Match(PathString path) { var i = 0; var pathTokenizer = new PathTokenizer(path); diff --git a/src/Microsoft.AspNet.Routing/Template/TemplateRoute.cs b/src/Microsoft.AspNet.Routing/Template/TemplateRoute.cs index 48acbe6d7d..6a4c546355 100644 --- a/src/Microsoft.AspNet.Routing/Template/TemplateRoute.cs +++ b/src/Microsoft.AspNet.Routing/Template/TemplateRoute.cs @@ -317,8 +317,8 @@ namespace Microsoft.AspNet.Routing.Template } private static void MergeValues( - IDictionary destination, - IDictionary values) + RouteValueDictionary destination, + RouteValueDictionary values) { foreach (var kvp in values) { diff --git a/src/Microsoft.AspNet.Routing/Template/TemplateValuesResult.cs b/src/Microsoft.AspNet.Routing/Template/TemplateValuesResult.cs index 767b04fcd7..6aeb85d515 100644 --- a/src/Microsoft.AspNet.Routing/Template/TemplateValuesResult.cs +++ b/src/Microsoft.AspNet.Routing/Template/TemplateValuesResult.cs @@ -1,8 +1,6 @@ // 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. -using System.Collections.Generic; - namespace Microsoft.AspNet.Routing.Template { /// @@ -13,7 +11,7 @@ namespace Microsoft.AspNet.Routing.Template /// /// The set of values that will appear in the URL. /// - public IDictionary AcceptedValues { get; set; } + public RouteValueDictionary AcceptedValues { get; set; } /// /// 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 /// route template are excluded from this set. /// - public IDictionary CombinedValues { get; set; } + public RouteValueDictionary CombinedValues { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Routing/VirtualPathContext.cs b/src/Microsoft.AspNet.Routing/VirtualPathContext.cs index 8134f2fca6..ac67152c52 100644 --- a/src/Microsoft.AspNet.Routing/VirtualPathContext.cs +++ b/src/Microsoft.AspNet.Routing/VirtualPathContext.cs @@ -8,17 +8,19 @@ namespace Microsoft.AspNet.Routing { public class VirtualPathContext { - public VirtualPathContext(HttpContext httpContext, - IDictionary ambientValues, - IDictionary values) + public VirtualPathContext( + HttpContext httpContext, + RouteValueDictionary ambientValues, + RouteValueDictionary values) : this(httpContext, ambientValues, values, null) { } - public VirtualPathContext(HttpContext context, - IDictionary ambientValues, - IDictionary values, - string routeName) + public VirtualPathContext( + HttpContext context, + RouteValueDictionary ambientValues, + RouteValueDictionary values, + string routeName) { Context = context; AmbientValues = ambientValues; @@ -26,16 +28,16 @@ namespace Microsoft.AspNet.Routing RouteName = routeName; } - public string RouteName { get; private set; } + public string RouteName { get; } public IDictionary ProvidedValues { get; set; } - public IDictionary AmbientValues { get; private set; } + public RouteValueDictionary AmbientValues { get; } - public HttpContext Context { get; private set; } + public HttpContext Context { get; } public bool IsBound { get; set; } - public IDictionary Values { get; private set; } + public RouteValueDictionary Values { get; } } } diff --git a/src/Microsoft.AspNet.Routing/VirtualPathData.cs b/src/Microsoft.AspNet.Routing/VirtualPathData.cs index 326afac34d..4b056b40b8 100644 --- a/src/Microsoft.AspNet.Routing/VirtualPathData.cs +++ b/src/Microsoft.AspNet.Routing/VirtualPathData.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using Microsoft.AspNet.Http; namespace Microsoft.AspNet.Routing @@ -13,7 +12,7 @@ namespace Microsoft.AspNet.Routing /// public class VirtualPathData { - private readonly IDictionary _dataTokens; + private RouteValueDictionary _dataTokens; /// /// Initializes a new instance of the class. @@ -21,7 +20,7 @@ namespace Microsoft.AspNet.Routing /// The object that is used to generate the URL. /// The generated URL. 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( IRouter router, string virtualPath, - IDictionary dataTokens) + RouteValueDictionary dataTokens) : this(router, CreatePathString(virtualPath), dataTokens) { } @@ -48,7 +47,7 @@ namespace Microsoft.AspNet.Routing public VirtualPathData( IRouter router, PathString virtualPath, - IDictionary dataTokens) + RouteValueDictionary dataTokens) { if (router == null) { @@ -57,23 +56,23 @@ namespace Microsoft.AspNet.Routing Router = router; VirtualPath = virtualPath; - - _dataTokens = new RouteValueDictionary(); - if (dataTokens != null) - { - foreach (var dataToken in dataTokens) - { - _dataTokens.Add(dataToken.Key, dataToken.Value); - } - } + _dataTokens = dataTokens == null ? null : new RouteValueDictionary(dataTokens); } /// /// Gets the collection of custom values for the . /// - public IDictionary DataTokens + public RouteValueDictionary DataTokens { - get { return _dataTokens; } + get + { + if (_dataTokens == null) + { + _dataTokens = new RouteValueDictionary(); + } + + return _dataTokens; + } } /// diff --git a/src/Microsoft.AspNet.Routing/project.json b/src/Microsoft.AspNet.Routing/project.json index 1cdf02f18f..1164f91961 100644 --- a/src/Microsoft.AspNet.Routing/project.json +++ b/src/Microsoft.AspNet.Routing/project.json @@ -9,21 +9,25 @@ "warningsAsErrors": true, "keyFile": "../../tools/Key.snk" }, - "dependencies": { - "Microsoft.AspNet.Http.Extensions": "1.0.0-*", - "Microsoft.AspNet.Routing.DecisionTree.Sources": { - "type": "build", - "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-*" + "dependencies": { + "Microsoft.AspNet.Http.Extensions": "1.0.0-*", + "Microsoft.AspNet.Routing.DecisionTree.Sources": { + "type": "build", + "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.PropertyHelper.Sources": { + "type": "build", + "version": "1.0.0-*" + } + }, "frameworks": { - "net451": {}, + "net451": { }, "dotnet5.4": { "dependencies": { "System.Text.RegularExpressions": "4.0.11-*" diff --git a/test/Microsoft.AspNet.Routing.Tests/RouteValueDictionaryTests.cs b/test/Microsoft.AspNet.Routing.Tests/RouteValueDictionaryTests.cs index 88dd3ac4f0..34e805c88d 100644 --- a/test/Microsoft.AspNet.Routing.Tests/RouteValueDictionaryTests.cs +++ b/test/Microsoft.AspNet.Routing.Tests/RouteValueDictionaryTests.cs @@ -165,7 +165,11 @@ namespace Microsoft.AspNet.Routing.Tests // Act & Assert ExceptionAssert.Throws( - () => new RouteValueDictionary(obj), + () => + { + var dictionary = new RouteValueDictionary(obj); + dictionary.Add("Hi", "There"); + }, expected); } diff --git a/test/Microsoft.AspNet.Routing.Tests/Template/TemplateBinderTests.cs b/test/Microsoft.AspNet.Routing.Tests/Template/TemplateBinderTests.cs index 0be677f6df..b97b307800 100644 --- a/test/Microsoft.AspNet.Routing.Tests/Template/TemplateBinderTests.cs +++ b/test/Microsoft.AspNet.Routing.Tests/Template/TemplateBinderTests.cs @@ -127,7 +127,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests public void Binding_WithEmptyAndNull_DefaultValues( string template, IReadOnlyDictionary defaults, - IDictionary values, + RouteValueDictionary values, string expected) { // Arrange @@ -284,8 +284,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests public void GetVirtualPathWithMultiSegmentWithOptionalParam( string template, IReadOnlyDictionary defaults, - IDictionary ambientValues, - IDictionary values, + RouteValueDictionary ambientValues, + RouteValueDictionary values, string expected) { // Arrange @@ -1086,8 +1086,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests private static void RunTest( string template, IReadOnlyDictionary defaults, - IDictionary ambientValues, - IDictionary values, + RouteValueDictionary ambientValues, + RouteValueDictionary values, string expected) { // Arrange diff --git a/test/Microsoft.AspNet.Routing.Tests/Template/TemplateRouteTest.cs b/test/Microsoft.AspNet.Routing.Tests/Template/TemplateRouteTest.cs index f9a34e6d24..323a1a77ca 100644 --- a/test/Microsoft.AspNet.Routing.Tests/Template/TemplateRouteTest.cs +++ b/test/Microsoft.AspNet.Routing.Tests/Template/TemplateRouteTest.cs @@ -1658,8 +1658,8 @@ namespace Microsoft.AspNet.Routing.Template } private static VirtualPathContext CreateVirtualPathContext( - IDictionary values, - IDictionary ambientValues) + RouteValueDictionary values, + RouteValueDictionary ambientValues) { var context = new Mock(MockBehavior.Strict); context.Setup(m => m.RequestServices.GetService(typeof(ILoggerFactory)))