| File: Matching\EndpointMetadataComparer.cs | Web Access |
| Project: src\aspnetcore\src\Http\Routing\src\Microsoft.AspNetCore.Routing.csproj (Microsoft.AspNetCore.Routing) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. #nullable enable using System.Linq; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Routing.Matching; /// <summary> /// A comparer that can order <see cref="Endpoint"/> instances based on implementations of /// <see cref="IEndpointComparerPolicy" />. The implementation can be retrieved from the service /// provider and provided to <see cref="CandidateSet.ExpandEndpoint(int, IReadOnlyList{Endpoint}, IComparer{Endpoint})"/>. /// </summary> public sealed class EndpointMetadataComparer : IComparer<Endpoint> { private readonly IServiceProvider _services; private IComparer<Endpoint>[]? _comparers; // This type is **INTENDED** for use in MatcherPolicy instances yet is also needs the MatcherPolicy instances. // using IServiceProvider to break the cycle. internal EndpointMetadataComparer(IServiceProvider services) { ArgumentNullException.ThrowIfNull(services); _services = services; } private IComparer<Endpoint>[] Comparers { get { if (_comparers == null) { _comparers = _services.GetServices<MatcherPolicy>() .OrderBy(p => p.Order) .OfType<IEndpointComparerPolicy>() .Select(p => p.Comparer) .ToArray(); } return _comparers; } } int IComparer<Endpoint>.Compare(Endpoint? x, Endpoint? y) { ArgumentNullException.ThrowIfNull(x); ArgumentNullException.ThrowIfNull(y); var comparers = Comparers; for (var i = 0; i < comparers.Length; i++) { var compare = comparers[i].Compare(x, y); if (compare != 0) { return compare; } } return 0; } } /// <summary> /// A base class for <see cref="IComparer{Endpoint}"/> implementations that use /// a specific type of metadata from <see cref="Endpoint.Metadata"/> for comparison. /// Useful for implementing <see cref="IEndpointComparerPolicy.Comparer"/>. /// </summary> /// <typeparam name="TMetadata"> /// The type of metadata to compare. Typically this is a type of metadata related /// to the application concern being handled. /// </typeparam> public abstract class EndpointMetadataComparer<TMetadata> : IComparer<Endpoint> where TMetadata : class { /// <summary> /// A default instance of the <see cref="EndpointMetadataComparer"/>. /// </summary> public static readonly EndpointMetadataComparer<TMetadata> Default = new DefaultComparer<TMetadata>(); /// <summary> /// Compares two objects and returns a value indicating whether one is less than, equal to, /// or greater than the other. /// </summary> /// <param name="x">The first object to compare.</param> /// <param name="y">The second object to compare.</param> /// <returns> /// An implementation of this method must return a value less than zero if /// x is less than y, zero if x is equal to y, or a value greater than zero if x is /// greater than y. /// </returns> public int Compare(Endpoint? x, Endpoint? y) { ArgumentNullException.ThrowIfNull(x); ArgumentNullException.ThrowIfNull(y); return CompareMetadata(GetMetadata(x), GetMetadata(y)); } /// <summary> /// Gets the metadata of type <typeparamref name="TMetadata"/> from the provided endpoint. /// </summary> /// <param name="endpoint">The <see cref="Endpoint"/>.</param> /// <returns>The <typeparamref name="TMetadata"/> instance or <c>null</c>.</returns> protected virtual TMetadata? GetMetadata(Endpoint endpoint) { return endpoint.Metadata.GetMetadata<TMetadata>(); } /// <summary> /// Compares two <typeparamref name="TMetadata"/> instances. /// </summary> /// <param name="x">The first object to compare.</param> /// <param name="y">The second object to compare.</param> /// <returns> /// An implementation of this method must return a value less than zero if /// x is less than y, zero if x is equal to y, or a value greater than zero if x is /// greater than y. /// </returns> /// <remarks> /// The base-class implementation of this method will compare metadata based on whether /// or not they are <c>null</c>. The effect of this is that when endpoints are being /// compared, the endpoint that defines an instance of <typeparamref name="TMetadata"/> /// will be considered higher priority. /// </remarks> protected virtual int CompareMetadata(TMetadata? x, TMetadata? y) { // The default policy is that if x endpoint defines TMetadata, and // y endpoint does not, then x is *more specific* than y. We return // -1 for this case so that x will come first in the sort order. if (x == null && y != null) { // y is more specific return 1; } else if (x != null && y == null) { // x is more specific return -1; } // both endpoints have this metadata, or both do not have it, they have // the same specificity. return 0; } private sealed class DefaultComparer<T> : EndpointMetadataComparer<T> where T : class { } }