From 2ad1aebcefec35951b1f1c966dd670c139ce5dd0 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 14 Oct 2018 19:25:56 +0000 Subject: [PATCH 1/5] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 56 ++++++++++++++++++++-------------------- korebuild-lock.txt | 4 +-- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 5ea63d4e8f..3c93a7647d 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -4,34 +4,34 @@ 0.10.13 - 2.2.0-preview2-20181011.2 - 2.2.0-preview3-35457 - 2.2.0-preview3-35457 - 2.2.0-preview3-35457 - 2.2.0-preview3-35457 - 2.2.0-preview3-35457 - 2.2.0-preview3-35457 - 2.2.0-preview3-35457 - 2.2.0-preview3-35457 - 2.2.0-preview3-35457 - 2.2.0-preview3-35457 - 2.2.0-preview3-35457 - 2.2.0-preview3-35457 - 2.2.0-preview3-35457 - 2.2.0-preview3-35457 - 2.2.0-preview3-35457 - 2.2.0-preview3-35457 - 2.2.0-preview3-35457 - 2.2.0-preview3-35457 - 2.2.0-preview3-35457 - 2.2.0-preview3-35457 - 2.2.0-preview3-35457 - 2.2.0-preview3-35457 - 2.2.0-preview3-35457 - 2.2.0-preview3-35457 - 2.2.0-preview3-35457 - 2.2.0-preview3-35457 - 2.2.0-preview3-35457 + 2.2.0-preview2-20181011.10 + 2.2.0-preview3-35496 + 2.2.0-preview3-35496 + 2.2.0-preview3-35496 + 2.2.0-preview3-35496 + 2.2.0-preview3-35496 + 2.2.0-preview3-35496 + 2.2.0-preview3-35496 + 2.2.0-preview3-35496 + 2.2.0-preview3-35496 + 2.2.0-preview3-35496 + 2.2.0-preview3-35496 + 2.2.0-preview3-35496 + 2.2.0-preview3-35496 + 2.2.0-preview3-35496 + 2.2.0-preview3-35496 + 2.2.0-preview3-35496 + 2.2.0-preview3-35496 + 2.2.0-preview3-35496 + 2.2.0-preview3-35496 + 2.2.0-preview3-35496 + 2.2.0-preview3-35496 + 2.2.0-preview3-35496 + 2.2.0-preview3-35496 + 2.2.0-preview3-35496 + 2.2.0-preview3-35496 + 2.2.0-preview3-35496 + 2.2.0-preview3-35496 2.0.9 2.1.3 2.2.0-preview3-27008-03 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 860121b752..86b4302b5d 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview2-20181011.2 -commithash:09cd1592eb0fbbfa6ef5124120c173bc1d4e353a +version:2.2.0-preview2-20181011.10 +commithash:224e1163a1145b60f324e0b432419ef4ba4880cc From e8b2c9337e6a7768c9af849052719c2d35c7062c Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Tue, 16 Oct 2018 12:48:19 -0700 Subject: [PATCH 2/5] Update package branding for 2.2 RTM --- version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.props b/version.props index 704cac087b..4889a26987 100644 --- a/version.props +++ b/version.props @@ -1,7 +1,7 @@ 2.2.0 - preview3 + rtm $(VersionPrefix) $(VersionPrefix)-$(VersionSuffix)-final t000 From 5c31f1f455ba1e7a0978dad397c8ea504e0534c1 Mon Sep 17 00:00:00 2001 From: Gert Driesen Date: Wed, 17 Oct 2018 22:24:07 +0200 Subject: [PATCH 3/5] Add Remove(string key, out object value) overload to RouteValueDictionary (#858) * Add Remove(string key, out object value) overload to RouteValueDictionary. * Consistently use _count field instead of Count property in Remove overloads. Added comment on EnsureCapacity call. Added test for removing first/middle/last entry. --- .../RouteValueDictionary.cs | 46 +++- .../Template/TemplateBinder.cs | 6 +- .../RouteValueDictionaryTests.cs | 229 ++++++++++++++++++ 3 files changed, 275 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.AspNetCore.Routing.Abstractions/RouteValueDictionary.cs b/src/Microsoft.AspNetCore.Routing.Abstractions/RouteValueDictionary.cs index ca132a7889..55b2438e9e 100644 --- a/src/Microsoft.AspNetCore.Routing.Abstractions/RouteValueDictionary.cs +++ b/src/Microsoft.AspNetCore.Routing.Abstractions/RouteValueDictionary.cs @@ -388,7 +388,9 @@ namespace Microsoft.AspNetCore.Routing return false; } - EnsureCapacity(Count); + // Ensure property storage is converted to array storage as we'll be + // applying the lookup and removal on the array + EnsureCapacity(_count); var index = FindIndex(key); if (index >= 0) @@ -404,6 +406,48 @@ namespace Microsoft.AspNetCore.Routing return false; } + /// + /// Attempts to remove and return the value that has the specified key from the . + /// + /// The key of the element to remove and return. + /// When this method returns, contains the object removed from the , or null if key does not exist. + /// + /// true if the object was removed successfully; otherwise, false. + /// + public bool Remove(string key, out object value) + { + if (key == null) + { + ThrowArgumentNullExceptionForKey(); + } + + if (_count == 0) + { + value = default; + return false; + } + + // Ensure property storage is converted to array storage as we'll be + // applying the lookup and removal on the array + EnsureCapacity(_count); + + var index = FindIndex(key); + if (index >= 0) + { + _count--; + var array = _arrayStorage; + value = array[index].Value; + Array.Copy(array, index + 1, array, index, _count - index); + array[_count] = default; + + return true; + } + + value = default; + return false; + } + + /// /// Attempts to the add the provided and to the dictionary. /// diff --git a/src/Microsoft.AspNetCore.Routing/Template/TemplateBinder.cs b/src/Microsoft.AspNetCore.Routing/Template/TemplateBinder.cs index 40755676fc..4236c3c012 100644 --- a/src/Microsoft.AspNetCore.Routing/Template/TemplateBinder.cs +++ b/src/Microsoft.AspNetCore.Routing/Template/TemplateBinder.cs @@ -471,11 +471,7 @@ namespace Microsoft.AspNetCore.Routing.Template else if (part is RoutePatternParameterPart parameterPart) { // If it's a parameter, get its value - var hasValue = acceptedValues.TryGetValue(parameterPart.Name, out var value); - if (hasValue) - { - acceptedValues.Remove(parameterPart.Name); - } + acceptedValues.Remove(parameterPart.Name, out var value); var isSameAsDefault = false; if (_defaults != null && diff --git a/test/Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests/RouteValueDictionaryTests.cs b/test/Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests/RouteValueDictionaryTests.cs index 1d4ec848af..35b8031fe8 100644 --- a/test/Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests/RouteValueDictionaryTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests/RouteValueDictionaryTests.cs @@ -1393,6 +1393,235 @@ namespace Microsoft.AspNetCore.Routing.Tests Assert.IsType[]>(dict._arrayStorage); } + + [Fact] + public void Remove_KeyAndOutValue_EmptyStorage() + { + // Arrange + var dict = new RouteValueDictionary(); + + // Act + var result = dict.Remove("key", out var removedValue); + + // Assert + Assert.False(result); + Assert.Null(removedValue); + } + + [Fact] + public void Remove_KeyAndOutValue_EmptyStringIsAllowed() + { + // Arrange + var dict = new RouteValueDictionary(); + + // Act + var result = dict.Remove("", out var removedValue); + + // Assert + Assert.False(result); + Assert.Null(removedValue); + } + + [Fact] + public void Remove_KeyAndOutValue_PropertyStorage_Empty() + { + // Arrange + var dict = new RouteValueDictionary(new { }); + + // Act + var result = dict.Remove("other", out var removedValue); + + // Assert + Assert.False(result); + Assert.Null(removedValue); + Assert.Empty(dict); + Assert.NotNull(dict._propertyStorage); + } + + [Fact] + public void Remove_KeyAndOutValue_PropertyStorage_False() + { + // Arrange + var dict = new RouteValueDictionary(new { key = "value" }); + + // Act + var result = dict.Remove("other", out var removedValue); + + // Assert + Assert.False(result); + Assert.Null(removedValue); + Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void Remove_KeyAndOutValue_PropertyStorage_True() + { + // Arrange + object value = "value"; + var dict = new RouteValueDictionary(new { key = value }); + + // Act + var result = dict.Remove("key", out var removedValue); + + // Assert + Assert.True(result); + Assert.Same(value, removedValue); + Assert.Empty(dict); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void Remove_KeyAndOutValue_PropertyStorage_True_CaseInsensitive() + { + // Arrange + object value = "value"; + var dict = new RouteValueDictionary(new { key = value }); + + // Act + var result = dict.Remove("kEy", out var removedValue); + + // Assert + Assert.True(result); + Assert.Same(value, removedValue); + Assert.Empty(dict); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void Remove_KeyAndOutValue_ListStorage_False() + { + // Arrange + var dict = new RouteValueDictionary() + { + { "key", "value" }, + }; + + // Act + var result = dict.Remove("other", out var removedValue); + + // Assert + Assert.False(result); + Assert.Null(removedValue); + Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void Remove_KeyAndOutValue_ListStorage_True() + { + // Arrange + object value = "value"; + var dict = new RouteValueDictionary() + { + { "key", value } + }; + + // Act + var result = dict.Remove("key", out var removedValue); + + // Assert + Assert.True(result); + Assert.Same(value, removedValue); + Assert.Empty(dict); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void Remove_KeyAndOutValue_ListStorage_True_CaseInsensitive() + { + // Arrange + object value = "value"; + var dict = new RouteValueDictionary() + { + { "key", value } + }; + + // Act + var result = dict.Remove("kEy", out var removedValue); + + // Assert + Assert.True(result); + Assert.Same(value, removedValue); + Assert.Empty(dict); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void Remove_KeyAndOutValue_ListStorage_KeyExists_First() + { + // Arrange + object value = "value"; + var dict = new RouteValueDictionary() + { + { "key", value }, + { "other", 5 }, + { "dotnet", "rocks" } + }; + + // Act + var result = dict.Remove("key", out var removedValue); + + // Assert + Assert.True(result); + Assert.Same(value, removedValue); + Assert.Equal(2, dict.Count); + Assert.False(dict.ContainsKey("key")); + Assert.True(dict.ContainsKey("other")); + Assert.True(dict.ContainsKey("dotnet")); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void Remove_KeyAndOutValue_ListStorage_KeyExists_Middle() + { + // Arrange + object value = "value"; + var dict = new RouteValueDictionary() + { + { "other", 5 }, + { "key", value }, + { "dotnet", "rocks" } + }; + + // Act + var result = dict.Remove("key", out var removedValue); + + // Assert + Assert.True(result); + Assert.Same(value, removedValue); + Assert.Equal(2, dict.Count); + Assert.False(dict.ContainsKey("key")); + Assert.True(dict.ContainsKey("other")); + Assert.True(dict.ContainsKey("dotnet")); + Assert.IsType[]>(dict._arrayStorage); + } + + [Fact] + public void Remove_KeyAndOutValue_ListStorage_KeyExists_Last() + { + // Arrange + object value = "value"; + var dict = new RouteValueDictionary() + { + { "other", 5 }, + { "dotnet", "rocks" }, + { "key", value } + }; + + // Act + var result = dict.Remove("key", out var removedValue); + + // Assert + Assert.True(result); + Assert.Same(value, removedValue); + Assert.Equal(2, dict.Count); + Assert.False(dict.ContainsKey("key")); + Assert.True(dict.ContainsKey("other")); + Assert.True(dict.ContainsKey("dotnet")); + Assert.IsType[]>(dict._arrayStorage); + } + [Fact] public void TryAdd_EmptyStringIsAllowed() { From e51da32e9d8f2744d9dacd0946b64a851881b3e8 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 18 Oct 2018 11:55:33 +1300 Subject: [PATCH 4/5] Prefer StringComparison over StringComparer when not using comparers (#870) --- .../Internal/LinkGenerationDecisionTree.cs | 5 +++-- .../Matching/HttpMethodMatcherPolicy.cs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.AspNetCore.Routing/Internal/LinkGenerationDecisionTree.cs b/src/Microsoft.AspNetCore.Routing/Internal/LinkGenerationDecisionTree.cs index 6242198a46..f276bf0658 100644 --- a/src/Microsoft.AspNetCore.Routing/Internal/LinkGenerationDecisionTree.cs +++ b/src/Microsoft.AspNetCore.Routing/Internal/LinkGenerationDecisionTree.cs @@ -164,9 +164,10 @@ namespace Microsoft.AspNetCore.Routing.Internal return x.IsFallbackMatch.CompareTo(y.IsFallbackMatch); } - return StringComparer.Ordinal.Compare( + return string.Compare( x.Match.Entry.RouteTemplate.TemplateText, - y.Match.Entry.RouteTemplate.TemplateText); + y.Match.Entry.RouteTemplate.TemplateText, + StringComparison.Ordinal); } } diff --git a/src/Microsoft.AspNetCore.Routing/Matching/HttpMethodMatcherPolicy.cs b/src/Microsoft.AspNetCore.Routing/Matching/HttpMethodMatcherPolicy.cs index b03c307a21..f1ec26d67d 100644 --- a/src/Microsoft.AspNetCore.Routing/Matching/HttpMethodMatcherPolicy.cs +++ b/src/Microsoft.AspNetCore.Routing/Matching/HttpMethodMatcherPolicy.cs @@ -285,7 +285,7 @@ namespace Microsoft.AspNetCore.Routing.Matching { for (var i = 0; i < httpMethods.Count; i++) { - if (StringComparer.OrdinalIgnoreCase.Equals(httpMethods[i], httpMethod)) + if (string.Equals(httpMethods[i], httpMethod, StringComparison.OrdinalIgnoreCase)) { return true; } From 29b50c7b64593557214e915b67dd67ae50388dd5 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 18 Oct 2018 19:54:49 +1300 Subject: [PATCH 5/5] Add test for link generation with high priority required parameter (#869) --- .../DefaultLinkGeneratorTest.cs | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs index 831576c41c..1f76ef3045 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/DefaultLinkGeneratorTest.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Routing.TestObjects; using Microsoft.Extensions.DependencyInjection; using Xunit; @@ -597,6 +598,64 @@ namespace Microsoft.AspNetCore.Routing Assert.NotSame(original, actual); } + [Fact] + public void GetPathByRouteValues_UsesFirstTemplateThatSucceeds() + { + // Arrange + var endpointControllerAction = EndpointFactory.CreateRouteEndpoint( + "Home/Index", + order: 3, + defaults: new { controller = "Home", action = "Index", }, + metadata: new[] { new RouteValuesAddressMetadata(new RouteValueDictionary(new { controller = "Home", action = "Index", })) }); + var endpointController = EndpointFactory.CreateRouteEndpoint( + "Home", + order: 2, + defaults: new { controller = "Home", action = "Index", }, + metadata: new[] { new RouteValuesAddressMetadata(new RouteValueDictionary(new { controller = "Home", action = "Index", })) }); + var endpointEmpty = EndpointFactory.CreateRouteEndpoint( + "", + order: 1, + defaults: new { controller = "Home", action = "Index", }, + metadata: new[] { new RouteValuesAddressMetadata(new RouteValueDictionary(new { controller = "Home", action = "Index", })) }); + + // This endpoint should be used to generate the link when an id is present + var endpointControllerActionParameter = EndpointFactory.CreateRouteEndpoint( + "Home/Index/{id}", + order: 0, + defaults: new { controller = "Home", action = "Index", }, + metadata: new[] { new RouteValuesAddressMetadata(new RouteValueDictionary(new { controller = "Home", action = "Index", })) }); + + var linkGenerator = CreateLinkGenerator(endpointControllerAction, endpointController, endpointEmpty, endpointControllerActionParameter); + + var context = new EndpointSelectorContext() + { + RouteValues = new RouteValueDictionary(new { controller = "Home", action = "Index", }) + }; + var httpContext = CreateHttpContext(); + httpContext.Features.Set(context); + + // Act + var pathWithoutId = linkGenerator.GetPathByRouteValues( + httpContext, + routeName: null, + values: new RouteValueDictionary()); + + var pathWithId = linkGenerator.GetPathByRouteValues( + httpContext, + routeName: null, + values: new RouteValueDictionary(new { id = "3" })); + + var pathWithCustom = linkGenerator.GetPathByRouteValues( + httpContext, + routeName: null, + values: new RouteValueDictionary(new { custom = "Custom" })); + + // Assert + Assert.Equal("/", pathWithoutId); + Assert.Equal("/Home/Index/3", pathWithId); + Assert.Equal("/?custom=Custom", pathWithCustom); + } + protected override void AddAdditionalServices(IServiceCollection services) { services.AddSingleton, IntAddressScheme>();