| File: Routing\EndpointMetadataCollection.cs | Web Access |
| Project: src\aspnetcore\src\Http\Http.Abstractions\src\Microsoft.AspNetCore.Http.Abstractions.csproj (Microsoft.AspNetCore.Http.Abstractions) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections; using System.Collections.Concurrent; using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; namespace Microsoft.AspNetCore.Http; /// <summary> /// A collection of arbitrary metadata associated with an endpoint. /// </summary> /// <remarks> /// <see cref="EndpointMetadataCollection"/> instances contain a list of metadata items /// of arbitrary types. The metadata items are stored as an ordered collection with /// items arranged in ascending order of precedence. /// </remarks> [DebuggerTypeProxy(typeof(EndpointMetadataCollectionDebugView))] [DebuggerDisplay("Count = {Count}")] public sealed class EndpointMetadataCollection : IReadOnlyList<object> { /// <summary> /// An empty <see cref="EndpointMetadataCollection"/>. /// </summary> public static readonly EndpointMetadataCollection Empty = new EndpointMetadataCollection(Array.Empty<object>()); private readonly object[] _items; private readonly ConcurrentDictionary<Type, object[]> _cache; /// <summary> /// Creates a new <see cref="EndpointMetadataCollection"/>. /// </summary> /// <param name="items">The metadata items.</param> public EndpointMetadataCollection(IEnumerable<object> items) { ArgumentNullException.ThrowIfNull(items); _items = items.ToArray(); _cache = new ConcurrentDictionary<Type, object[]>(); } /// <summary> /// Creates a new <see cref="EndpointMetadataCollection"/>. /// </summary> /// <param name="items">The metadata items.</param> public EndpointMetadataCollection(params object[] items) : this((IEnumerable<object>)items) { } /// <summary> /// Gets the item at <paramref name="index"/>. /// </summary> /// <param name="index">The index of the item to retrieve.</param> /// <returns>The item at <paramref name="index"/>.</returns> public object this[int index] => _items[index]; /// <summary> /// Gets the count of metadata items. /// </summary> public int Count => _items.Length; /// <summary> /// Gets the most significant metadata item of type <typeparamref name="T"/>. /// </summary> /// <typeparam name="T">The type of metadata to retrieve.</typeparam> /// <returns> /// The most significant metadata of type <typeparamref name="T"/> or <c>null</c>. /// </returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public T? GetMetadata<T>() where T : class { if (_cache.TryGetValue(typeof(T), out var obj)) { var result = (T[])obj; var length = result.Length; return length > 0 ? result[length - 1] : default; } return GetMetadataSlow<T>(); } private T? GetMetadataSlow<T>() where T : class { var result = GetOrderedMetadataSlow<T>(); var length = result.Length; return length > 0 ? result[length - 1] : default; } /// <summary> /// Gets the metadata items of type <typeparamref name="T"/> in ascending /// order of precedence. /// </summary> /// <typeparam name="T">The type of metadata.</typeparam> /// <returns>A sequence of metadata items of <typeparamref name="T"/>.</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public IReadOnlyList<T> GetOrderedMetadata<T>() where T : class { if (_cache.TryGetValue(typeof(T), out var result)) { return (T[])result; } return GetOrderedMetadataSlow<T>(); } private T[] GetOrderedMetadataSlow<T>() where T : class { // Perf: avoid allocations totally for the common case where there are no matching metadata. List<T>? matches = null; var items = _items; for (var i = 0; i < items.Length; i++) { if (items[i] is T item) { matches ??= new List<T>(); matches.Add(item); } } var results = matches == null ? Array.Empty<T>() : matches.ToArray(); _cache.TryAdd(typeof(T), results); return results; } /// <summary> /// Gets the most significant metadata item of type <typeparamref name="T"/>. /// Throws an <see cref="InvalidOperationException"/> if the metadata is not found. /// </summary> /// <typeparam name="T">The type of metadata to retrieve.</typeparam> /// <returns> /// The most significant metadata of type <typeparamref name="T"/>. /// </returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public T GetRequiredMetadata<T>() where T : class { var metadata = GetMetadata<T>(); return metadata ?? throw new InvalidOperationException($"Metadata '{typeof(T)}' is not found."); } /// <summary> /// Gets an <see cref="IEnumerator"/> of all metadata items. /// </summary> /// <returns>An <see cref="IEnumerator"/> of all metadata items.</returns> public Enumerator GetEnumerator() => new Enumerator(this); /// <summary> /// Gets an <see cref="IEnumerator{Object}"/> of all metadata items. /// </summary> /// <returns>An <see cref="IEnumerator{Object}"/> of all metadata items.</returns> IEnumerator<object> IEnumerable<object>.GetEnumerator() => GetEnumerator(); /// <summary> /// Gets an <see cref="IEnumerator"/> of all metadata items. /// </summary> /// <returns>An <see cref="IEnumerator"/> of all metadata items.</returns> IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// <summary> /// Enumerates the elements of an <see cref="EndpointMetadataCollection"/>. /// </summary> public struct Enumerator : IEnumerator<object> { #pragma warning disable IDE0044 // Intentionally not readonly to prevent defensive struct copies private object[] _items; #pragma warning restore IDE0044 private int _index; private object? _current; internal Enumerator(EndpointMetadataCollection collection) { _items = collection._items; _index = 0; _current = null; } /// <summary> /// Gets the element at the current position of the enumerator /// </summary> public object Current => _current!; /// <summary> /// Releases all resources used by the <see cref="Enumerator"/>. /// </summary> public void Dispose() { } /// <summary> /// Advances the enumerator to the next element of the <see cref="Enumerator"/>. /// </summary> /// <returns> /// <c>true</c> if the enumerator was successfully advanced to the next element; /// <c>false</c> if the enumerator has passed the end of the collection. /// </returns> public bool MoveNext() { if (_index < _items.Length) { _current = _items[_index++]; return true; } _current = null; return false; } /// <summary> /// Sets the enumerator to its initial position, which is before the first element in the collection. /// </summary> public void Reset() { _index = 0; _current = null; } } private sealed class EndpointMetadataCollectionDebugView { private readonly EndpointMetadataCollection _collection; public EndpointMetadataCollectionDebugView(EndpointMetadataCollection collection) { ArgumentNullException.ThrowIfNull(collection); _collection = collection; } [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] public object[] Items { get { var items = new object[_collection.Count]; _collection._items.CopyTo(items, 0); return items; } } } }