diff --git a/src/Microsoft.AspNet.Routing/RouteValueDictionary.cs b/src/Microsoft.AspNet.Routing/RouteValueDictionary.cs index e17b1298cc..7dfb83d4d5 100644 --- a/src/Microsoft.AspNet.Routing/RouteValueDictionary.cs +++ b/src/Microsoft.AspNet.Routing/RouteValueDictionary.cs @@ -1,6 +1,7 @@  using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; namespace Microsoft.AspNet.Routing @@ -17,10 +18,25 @@ namespace Microsoft.AspNet.Routing { if (obj != null) { - foreach (var property in obj.GetType().GetTypeInfo().DeclaredProperties) + var type = obj.GetType(); + var allProperties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); + + // 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) { - var value = property.GetValue(obj); - Add(property.Name, value); + if (property.GetMethod != null && property.GetIndexParameters().Length == 0) + { + var value = property.GetValue(obj); + if (ContainsKey(property.Name) && property.DeclaringType != type) + { + // This is a hidden property, ignore it. + } + else + { + Add(property.Name, value); + } + } } } } diff --git a/test/Microsoft.AspNet.Routing.Tests/Template/Assert.cs b/test/Microsoft.AspNet.Routing.Tests/Assert.cs similarity index 84% rename from test/Microsoft.AspNet.Routing.Tests/Template/Assert.cs rename to test/Microsoft.AspNet.Routing.Tests/Assert.cs index c5c02593b2..e4677c8f0e 100644 --- a/test/Microsoft.AspNet.Routing.Tests/Template/Assert.cs +++ b/test/Microsoft.AspNet.Routing.Tests/Assert.cs @@ -1,7 +1,8 @@ using System; -namespace Microsoft.AspNet.Routing.Template.Tests +namespace Microsoft.AspNet.Routing { + // Placeholder until we get our 'real' rich support for these asserts. public class Assert : Xunit.Assert { public static T Throws(Assert.ThrowsDelegate action, string message) where T : Exception diff --git a/test/Microsoft.AspNet.Routing.Tests/RouteValueDictionaryTests.cs b/test/Microsoft.AspNet.Routing.Tests/RouteValueDictionaryTests.cs new file mode 100644 index 0000000000..51bd41fd3e --- /dev/null +++ b/test/Microsoft.AspNet.Routing.Tests/RouteValueDictionaryTests.cs @@ -0,0 +1,222 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Dynamic; +using Xunit; + +namespace Microsoft.AspNet.Routing.Tests +{ + public class RouteValueDictionaryTests + { + [Fact] + public void CreateEmpty_UsesOrdinalIgnoreCase() + { + // Arrange + // Act + var dict = new RouteValueDictionary(); + + // Assert + Assert.Same(StringComparer.OrdinalIgnoreCase, dict.Comparer); + } + + [Fact] + public void CreateFromDictionary_UsesOrdinalIgnoreCase() + { + // Arrange + // Act + var dict = new RouteValueDictionary(new Dictionary(StringComparer.Ordinal)); + + // Assert + Assert.Same(StringComparer.OrdinalIgnoreCase, dict.Comparer); + } + + [Fact] + public void CreateFromObject_UsesOrdinalIgnoreCase() + { + // Arrange + // Act + var dict = new RouteValueDictionary(new { cool = "beans" }); + + // Assert + Assert.Same(StringComparer.OrdinalIgnoreCase, dict.Comparer); + } + + [Fact] + public void CreateFromObject_CopiesPropertiesFromAnonymousType() + { + // Arrange + var obj = new {cool = "beans", awesome = 123}; + + // Act + var dict = new RouteValueDictionary(obj); + + // Assert + Assert.Equal(2, dict.Count); + Assert.Equal("beans", dict["cool"]); + Assert.Equal(123, dict["awesome"]); + } + + [Fact] + public void CreateFromObject_CopiesPropertiesFromRegularType() + { + // Arrange + var obj = new RegularType() { CoolnessFactor = 73}; + + // Act + var dict = new RouteValueDictionary(obj); + + // Assert + Assert.Equal(2, dict.Count); + Assert.Equal(false, dict["IsAwesome"]); + Assert.Equal(73, dict["CoolnessFactor"]); + } + + [Fact] + public void CreateFromObject_CopiesPropertiesFromRegularType_PublicOnly() + { + // Arrange + var obj = new Visibility() { IsPublic = true, ItsInternalDealWithIt = 5 }; + + // Act + var dict = new RouteValueDictionary(obj); + + // Assert + Assert.Equal(1, dict.Count); + Assert.Equal(true, dict["IsPublic"]); + } + + [Fact] + public void CreateFromObject_CopiesPropertiesFromRegularType_IgnoresStatic() + { + // Arrange + var obj = new StaticProperty(); + + // Act + var dict = new RouteValueDictionary(obj); + + // Assert + Assert.Equal(0, dict.Count); + } + + [Fact] + public void CreateFromObject_CopiesPropertiesFromRegularType_IgnoresSetOnly() + { + // Arrange + var obj = new SetterOnly() {CoolSetOnly = false}; + + // Act + var dict = new RouteValueDictionary(obj); + + // Assert + Assert.Equal(0, dict.Count); + } + + [Fact] + public void CreateFromObject_CopiesPropertiesFromRegularType_IncludesInherited() + { + // Arrange + var obj = new Derived() {TotallySweetProperty = true, DerivedProperty = false}; + + // Act + var dict = new RouteValueDictionary(obj); + + // Assert + Assert.Equal(2, dict.Count); + Assert.Equal(true, dict["TotallySweetProperty"]); + Assert.Equal(false, dict["DerivedProperty"]); + } + + [Fact] + public void CreateFromObject_CopiesPropertiesFromRegularType_WithHiddenProperty() + { + // Arrange + var obj = new DerivedHiddenProperty() { DerivedProperty = 5 }; + + // Act + var dict = new RouteValueDictionary(obj); + + // Assert + Assert.Equal(1, dict.Count); + Assert.Equal(5, dict["DerivedProperty"]); + } + + [Fact] + public void CreateFromObject_CopiesPropertiesFromRegularType_WithIndexerProperty() + { + // Arrange + var obj = new IndexerProperty(); + + // Act + var dict = new RouteValueDictionary(obj); + + // Assert + Assert.Equal(0, dict.Count); + } + + [Fact] + public void CreateFromObject_MixedCaseThrows() + { + // Arrange + var obj = new { controller = "Home", Controller = "Home" }; + + // Act & Assert + Assert.Throws( + () => new RouteValueDictionary(obj), + "An item with the same key has already been added."); + } + + + private class RegularType + { + public bool IsAwesome { get; set; } + + public int CoolnessFactor { get; set; } + } + + private class Visibility + { + private string PrivateYo { get; set; } + + internal int ItsInternalDealWithIt { get; set; } + + public bool IsPublic { get; set; } + } + + private class StaticProperty + { + public static bool IsStatic { get; set; } + } + + private class SetterOnly + { + private bool _coolSetOnly; + + public bool CoolSetOnly { set { _coolSetOnly = value; }} + } + + private class Base + { + public bool DerivedProperty { get; set; } + } + + private class Derived : Base + { + public bool TotallySweetProperty { get; set; } + } + + private class DerivedHiddenProperty : Base + { + public new int DerivedProperty { get; set; } + } + + private class IndexerProperty + { + public bool this[string key] + { + get { return false; } + set {} + } + } + } +}