diff --git a/src/Microsoft.AspNetCore.Routing.Abstractions/EndpointMetadataCollection.cs b/src/Microsoft.AspNetCore.Routing.Abstractions/EndpointMetadataCollection.cs index 54624d8717..ee12d0b8e7 100644 --- a/src/Microsoft.AspNetCore.Routing.Abstractions/EndpointMetadataCollection.cs +++ b/src/Microsoft.AspNetCore.Routing.Abstractions/EndpointMetadataCollection.cs @@ -3,8 +3,10 @@ using System; using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; namespace Microsoft.AspNetCore.Routing { @@ -24,6 +26,7 @@ namespace Microsoft.AspNetCore.Routing public static readonly EndpointMetadataCollection Empty = new EndpointMetadataCollection(Array.Empty()); private readonly object[] _items; + private readonly ConcurrentDictionary _cache; /// /// Creates a new . @@ -37,6 +40,7 @@ namespace Microsoft.AspNetCore.Routing } _items = items.ToArray(); + _cache = new ConcurrentDictionary(); } /// @@ -67,18 +71,23 @@ namespace Microsoft.AspNetCore.Routing /// /// The most significant metadata of type or null. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public T GetMetadata() where T : class { - for (var i = _items.Length - 1; i >= 0; i--) + if (_cache.TryGetValue(typeof(T), out var result)) { - var item = _items[i] as T; - if (item != null) - { - return item; - } + var length = result.Length; + return length > 0 ? (T)result[length - 1] : default; } - return default; + return GetMetadataSlow(); + } + + private T GetMetadataSlow() where T : class + { + var array = GetOrderedMetadataSlow(); + var length = array.Length; + return length > 0 ? array[length - 1] : default; } /// @@ -87,16 +96,32 @@ namespace Microsoft.AspNetCore.Routing /// /// The type of metadata. /// A sequence of metadata items of . + [MethodImpl(MethodImplOptions.AggressiveInlining)] public IEnumerable GetOrderedMetadata() where T : class { + if (_cache.TryGetValue(typeof(T), out var result)) + { + return (T[])result; + } + + return GetOrderedMetadataSlow(); + } + + private T[] GetOrderedMetadataSlow() where T : class + { + var items = new List(); for (var i = 0; i < _items.Length; i++) { var item = _items[i] as T; if (item != null) { - yield return item; + items.Add(item); } } + + var array = items.ToArray(); + _cache.TryAdd(typeof(T), array); + return array; } /// diff --git a/test/Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests/EndpointMetadataCollectionTests.cs b/test/Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests/EndpointMetadataCollectionTests.cs index a7e645c047..12c75020a4 100644 --- a/test/Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests/EndpointMetadataCollectionTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests/EndpointMetadataCollectionTests.cs @@ -44,5 +44,98 @@ namespace Microsoft.AspNetCore.Routing value => Assert.Equal(2, value), value => Assert.Equal(3, value)); } + + [Fact] + public void GetMetadata_Match_ReturnsLastMatchingEntry() + { + // Arrange + var items = new object[] + { + new Metadata1(), + new Metadata2(), + new Metadata3(), + }; + + var metadata = new EndpointMetadataCollection(items); + + // Act + var result = metadata.GetMetadata(); + + // Assert + Assert.Same(items[1], result); + } + + [Fact] + public void GetMetadata_NoMatch_ReturnsNull() + { + // Arrange + var items = new object[] + { + new Metadata3(), + new Metadata3(), + new Metadata3(), + }; + + var metadata = new EndpointMetadataCollection(items); + + // Act + var result = metadata.GetMetadata(); + + // Assert + Assert.Null(result); + } + + [Fact] + public void GetOrderedMetadata_Match_ReturnsItemsInAscendingOrder() + { + // Arrange + var items = new object[] + { + new Metadata1(), + new Metadata2(), + new Metadata3(), + }; + + var metadata = new EndpointMetadataCollection(items); + + // Act + var result = metadata.GetOrderedMetadata(); + + // Assert + Assert.Collection( + result, + i => Assert.Same(items[0], i), + i => Assert.Same(items[1], i)); + } + + [Fact] + public void GetOrderedMetadata_NoMatch_ReturnsEmpty() + { + // Arrange + var items = new object[] + { + new Metadata3(), + new Metadata3(), + new Metadata3(), + }; + + var metadata = new EndpointMetadataCollection(items); + + // Act + var result = metadata.GetOrderedMetadata(); + + // Assert + Assert.Empty(result); + } + + private interface IMetadata1 { } + private interface IMetadata2 { } + private interface IMetadata3 { } + private interface IMetadata4 { } + private interface IMetadata5 { } + private class Metadata1 : IMetadata1, IMetadata4, IMetadata5 { } + private class Metadata2 : IMetadata2, IMetadata5 { } + private class Metadata3 : IMetadata3 { } + } } \ No newline at end of file