Make HostMatcherPolicy implement IEndpointSelectorPolicy

This commit is contained in:
Ryan Nowak 2019-04-14 13:44:36 -07:00 committed by Ryan Nowak
parent 94fab79771
commit 8df3dc7ae4
6 changed files with 311 additions and 45 deletions

View File

@ -558,14 +558,16 @@ namespace Microsoft.AspNetCore.Routing.Matching
protected EndpointSelector() { } protected EndpointSelector() { }
public abstract System.Threading.Tasks.Task SelectAsync(Microsoft.AspNetCore.Http.HttpContext httpContext, Microsoft.AspNetCore.Routing.EndpointSelectorContext context, Microsoft.AspNetCore.Routing.Matching.CandidateSet candidates); public abstract System.Threading.Tasks.Task SelectAsync(Microsoft.AspNetCore.Http.HttpContext httpContext, Microsoft.AspNetCore.Routing.EndpointSelectorContext context, Microsoft.AspNetCore.Routing.Matching.CandidateSet candidates);
} }
public sealed partial class HostMatcherPolicy : Microsoft.AspNetCore.Routing.MatcherPolicy, Microsoft.AspNetCore.Routing.Matching.IEndpointComparerPolicy, Microsoft.AspNetCore.Routing.Matching.INodeBuilderPolicy public sealed partial class HostMatcherPolicy : Microsoft.AspNetCore.Routing.MatcherPolicy, Microsoft.AspNetCore.Routing.Matching.IEndpointComparerPolicy, Microsoft.AspNetCore.Routing.Matching.IEndpointSelectorPolicy, Microsoft.AspNetCore.Routing.Matching.INodeBuilderPolicy
{ {
public HostMatcherPolicy() { } public HostMatcherPolicy() { }
public System.Collections.Generic.IComparer<Microsoft.AspNetCore.Http.Endpoint> Comparer { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } public System.Collections.Generic.IComparer<Microsoft.AspNetCore.Http.Endpoint> Comparer { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
public override int Order { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } public override int Order { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
public bool AppliesToEndpoints(System.Collections.Generic.IReadOnlyList<Microsoft.AspNetCore.Http.Endpoint> endpoints) { throw null; } public System.Threading.Tasks.Task ApplyAsync(Microsoft.AspNetCore.Http.HttpContext httpContext, Microsoft.AspNetCore.Routing.EndpointSelectorContext context, Microsoft.AspNetCore.Routing.Matching.CandidateSet candidates) { throw null; }
public Microsoft.AspNetCore.Routing.Matching.PolicyJumpTable BuildJumpTable(int exitDestination, System.Collections.Generic.IReadOnlyList<Microsoft.AspNetCore.Routing.Matching.PolicyJumpTableEdge> edges) { throw null; } public Microsoft.AspNetCore.Routing.Matching.PolicyJumpTable BuildJumpTable(int exitDestination, System.Collections.Generic.IReadOnlyList<Microsoft.AspNetCore.Routing.Matching.PolicyJumpTableEdge> edges) { throw null; }
public System.Collections.Generic.IReadOnlyList<Microsoft.AspNetCore.Routing.Matching.PolicyNodeEdge> GetEdges(System.Collections.Generic.IReadOnlyList<Microsoft.AspNetCore.Http.Endpoint> endpoints) { throw null; } public System.Collections.Generic.IReadOnlyList<Microsoft.AspNetCore.Routing.Matching.PolicyNodeEdge> GetEdges(System.Collections.Generic.IReadOnlyList<Microsoft.AspNetCore.Http.Endpoint> endpoints) { throw null; }
bool Microsoft.AspNetCore.Routing.Matching.IEndpointSelectorPolicy.AppliesToEndpoints(System.Collections.Generic.IReadOnlyList<Microsoft.AspNetCore.Http.Endpoint> endpoints) { throw null; }
bool Microsoft.AspNetCore.Routing.Matching.INodeBuilderPolicy.AppliesToEndpoints(System.Collections.Generic.IReadOnlyList<Microsoft.AspNetCore.Http.Endpoint> endpoints) { throw null; }
} }
public sealed partial class HttpMethodMatcherPolicy : Microsoft.AspNetCore.Routing.MatcherPolicy, Microsoft.AspNetCore.Routing.Matching.IEndpointComparerPolicy, Microsoft.AspNetCore.Routing.Matching.INodeBuilderPolicy public sealed partial class HttpMethodMatcherPolicy : Microsoft.AspNetCore.Routing.MatcherPolicy, Microsoft.AspNetCore.Routing.Matching.IEndpointComparerPolicy, Microsoft.AspNetCore.Routing.Matching.INodeBuilderPolicy
{ {

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Routing.Matching namespace Microsoft.AspNetCore.Routing.Matching
@ -12,20 +13,41 @@ namespace Microsoft.AspNetCore.Routing.Matching
/// An <see cref="MatcherPolicy"/> that implements filtering and selection by /// An <see cref="MatcherPolicy"/> that implements filtering and selection by
/// the host header of a request. /// the host header of a request.
/// </summary> /// </summary>
public sealed class HostMatcherPolicy : MatcherPolicy, IEndpointComparerPolicy, INodeBuilderPolicy public sealed class HostMatcherPolicy : MatcherPolicy, IEndpointComparerPolicy, INodeBuilderPolicy, IEndpointSelectorPolicy
{ {
private const string WildcardHost = "*";
private const string WildcardPrefix = "*.";
// Run after HTTP methods, but before 'default'. // Run after HTTP methods, but before 'default'.
public override int Order { get; } = -100; public override int Order { get; } = -100;
public IComparer<Endpoint> Comparer { get; } = new HostMetadataEndpointComparer(); public IComparer<Endpoint> Comparer { get; } = new HostMetadataEndpointComparer();
public bool AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints) bool INodeBuilderPolicy.AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
{ {
if (endpoints == null) if (endpoints == null)
{ {
throw new ArgumentNullException(nameof(endpoints)); throw new ArgumentNullException(nameof(endpoints));
} }
return !ContainsDynamicEndpoints(endpoints) && AppliesToEndpointsCore(endpoints);
}
bool IEndpointSelectorPolicy.AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
{
// When the node contains dynamic endpoints we can't make any assumptions.
var applies = ContainsDynamicEndpoints(endpoints);
if (applies)
{
// Run for the side-effect of validating metadata.
AppliesToEndpointsCore(endpoints);
}
return applies;
}
private bool AppliesToEndpointsCore(IReadOnlyList<Endpoint> endpoints)
{
return endpoints.Any(e => return endpoints.Any(e =>
{ {
var hosts = e.Metadata.GetMetadata<IHostMetadata>()?.Hosts; var hosts = e.Metadata.GetMetadata<IHostMetadata>()?.Hosts;
@ -48,6 +70,97 @@ namespace Microsoft.AspNetCore.Routing.Matching
}); });
} }
public Task ApplyAsync(HttpContext httpContext, EndpointSelectorContext context, CandidateSet candidates)
{
if (httpContext == null)
{
throw new ArgumentNullException(nameof(httpContext));
}
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (candidates == null)
{
throw new ArgumentNullException(nameof(candidates));
}
for (var i = 0; i < candidates.Count; i++)
{
if (!candidates.IsValidCandidate(i))
{
continue;
}
var hosts = candidates[i].Endpoint.Metadata.GetMetadata<IHostMetadata>()?.Hosts;
if (hosts == null || hosts.Count == 0)
{
// Can match any host.
continue;
}
var matched = false;
var (requestHost, requestPort) = GetHostAndPort(httpContext);
for (var j = 0; j < hosts.Count; j++)
{
var host = hosts[j].AsSpan();
var port = "".AsSpan();
// Split into host and port
var pivot = host.IndexOf(":");
if (pivot >= 0)
{
port = host.Slice(pivot + 1);
host = host.Slice(0, pivot);
}
if (host == null || MemoryExtensions.Equals(host, WildcardHost, StringComparison.OrdinalIgnoreCase))
{
// Can match any host
}
else if (
host.StartsWith(WildcardPrefix) &&
// Note that we only slice of the `*`. We want to match the leading `.` also.
MemoryExtensions.EndsWith(requestHost, host.Slice(WildcardHost.Length), StringComparison.OrdinalIgnoreCase))
{
// Matches a suffix wildcard.
}
else if (MemoryExtensions.Equals(requestHost, host, StringComparison.OrdinalIgnoreCase))
{
// Matches exactly
}
else
{
// If we get here then the host doesn't match.
continue;
}
if (MemoryExtensions.Equals(port, WildcardHost, StringComparison.OrdinalIgnoreCase))
{
// Port is a wildcard, we allow any port.
}
else if (port.Length > 0 && (!int.TryParse(port, out var parsed) || parsed != requestPort))
{
// If we get here then the port doesn't match.
continue;
}
matched = true;
break;
}
if (!matched)
{
candidates.SetValidity(i, false);
}
}
return Task.CompletedTask;
}
private static EdgeKey CreateEdgeKey(string host) private static EdgeKey CreateEdgeKey(string host)
{ {
if (host == null) if (host == null)
@ -71,7 +184,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
{ {
return new EdgeKey(hostParts[0], port); return new EdgeKey(hostParts[0], port);
} }
else if (string.Equals(hostParts[1], "*", StringComparison.Ordinal)) else if (string.Equals(hostParts[1], WildcardHost, StringComparison.Ordinal))
{ {
return new EdgeKey(hostParts[0], null); return new EdgeKey(hostParts[0], null);
} }
@ -211,6 +324,27 @@ namespace Microsoft.AspNetCore.Routing.Matching
} }
} }
private static (string host, int? port) GetHostAndPort(HttpContext httpContext)
{
var hostString = httpContext.Request.Host;
if (hostString.Port != null)
{
return (hostString.Host, hostString.Port);
}
else if (string.Equals("https", httpContext.Request.Scheme, StringComparison.OrdinalIgnoreCase))
{
return (hostString.Host, 443);
}
else if (string.Equals("http", httpContext.Request.Scheme, StringComparison.OrdinalIgnoreCase))
{
return (hostString.Host, 80);
}
else
{
return (hostString.Host, null);
}
}
private class HostMetadataEndpointComparer : EndpointMetadataComparer<IHostMetadata> private class HostMetadataEndpointComparer : EndpointMetadataComparer<IHostMetadata>
{ {
protected override int CompareMetadata(IHostMetadata x, IHostMetadata y) protected override int CompareMetadata(IHostMetadata x, IHostMetadata y)
@ -237,9 +371,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
{ {
// HostString can allocate when accessing the host or port // HostString can allocate when accessing the host or port
// Store host and port locally and reuse // Store host and port locally and reuse
var requestHost = httpContext.Request.Host; var (host, port) = GetHostAndPort(httpContext);
var host = requestHost.Host;
var port = ResolvePort(httpContext, requestHost);
var destinations = _destinations; var destinations = _destinations;
for (var i = 0; i < destinations.Length; i++) for (var i = 0; i < destinations.Length; i++)
@ -255,31 +387,10 @@ namespace Microsoft.AspNetCore.Routing.Matching
return _exitDestination; return _exitDestination;
} }
private static int? ResolvePort(HttpContext httpContext, HostString requestHost)
{
if (requestHost.Port != null)
{
return requestHost.Port;
}
else if (string.Equals("https", httpContext.Request.Scheme, StringComparison.OrdinalIgnoreCase))
{
return 443;
}
else if (string.Equals("http", httpContext.Request.Scheme, StringComparison.OrdinalIgnoreCase))
{
return 80;
}
else
{
return null;
}
}
} }
private readonly struct EdgeKey : IEquatable<EdgeKey>, IComparable<EdgeKey>, IComparable private readonly struct EdgeKey : IEquatable<EdgeKey>, IComparable<EdgeKey>, IComparable
{ {
private const string WildcardHost = "*";
internal static readonly EdgeKey WildcardEdgeKey = new EdgeKey(null, null); internal static readonly EdgeKey WildcardEdgeKey = new EdgeKey(null, null);
public readonly int? Port; public readonly int? Port;
@ -292,7 +403,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
Host = host ?? WildcardHost; Host = host ?? WildcardHost;
Port = port; Port = port;
HasHostWildcard = Host.StartsWith("*.", StringComparison.Ordinal); HasHostWildcard = Host.StartsWith(WildcardPrefix, StringComparison.Ordinal);
_wildcardEndsWith = HasHostWildcard ? Host.Substring(1) : null; _wildcardEndsWith = HasHostWildcard ? Host.Substring(1) : null;
} }
@ -342,6 +453,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
return true; return true;
} }
public override int GetHashCode() public override int GetHashCode()
{ {
return (Host?.GetHashCode() ?? 0) ^ (Port?.GetHashCode() ?? 0); return (Host?.GetHashCode() ?? 0) ^ (Port?.GetHashCode() ?? 0);
@ -359,7 +471,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
public override string ToString() public override string ToString()
{ {
return $"{Host}:{Port?.ToString() ?? "*"}"; return $"{Host}:{Port?.ToString() ?? WildcardHost}";
} }
} }
} }

View File

@ -0,0 +1,11 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Routing.Matching
{
// End-to-end tests for the host matching functionality
public class HostMatcherPolicyIEndpointSelectorPolicyIntegrationTest : HostMatcherPolicyIntegrationTestBase
{
protected override bool HasDynamicMetadata => true;
}
}

View File

@ -0,0 +1,11 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Routing.Matching
{
// End-to-end tests for the host matching functionality
public class HostMatcherPolicyINodeBuilderPolicyIntegrationTest : HostMatcherPolicyIntegrationTestBase
{
protected override bool HasDynamicMetadata => false;
}
}

View File

@ -13,8 +13,10 @@ using Xunit;
namespace Microsoft.AspNetCore.Routing.Matching namespace Microsoft.AspNetCore.Routing.Matching
{ {
// End-to-end tests for the host matching functionality // End-to-end tests for the host matching functionality
public class HostMatcherPolicyIntegrationTest public abstract class HostMatcherPolicyIntegrationTestBase
{ {
protected abstract bool HasDynamicMetadata { get; }
[Fact] [Fact]
public async Task Match_Host() public async Task Match_Host()
{ {
@ -308,7 +310,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
return (httpContext, context); return (httpContext, context);
} }
internal static RouteEndpoint CreateEndpoint( internal RouteEndpoint CreateEndpoint(
string template, string template,
object defaults = null, object defaults = null,
object constraints = null, object constraints = null,
@ -321,6 +323,11 @@ namespace Microsoft.AspNetCore.Routing.Matching
metadata.Add(new HostAttribute(hosts ?? Array.Empty<string>())); metadata.Add(new HostAttribute(hosts ?? Array.Empty<string>()));
} }
if (HasDynamicMetadata)
{
metadata.Add(new DynamicEndpointMetadata());
}
var displayName = "endpoint: " + template + " " + string.Join(", ", hosts ?? new[] { "*:*" }); var displayName = "endpoint: " + template + " " + string.Join(", ", hosts ?? new[] { "*:*" });
return new RouteEndpoint( return new RouteEndpoint(
TestConstants.EmptyRequestDelegate, TestConstants.EmptyRequestDelegate,
@ -335,5 +342,10 @@ namespace Microsoft.AspNetCore.Routing.Matching
var endpoint = CreateEndpoint(template); var endpoint = CreateEndpoint(template);
return (CreateMatcher(endpoint), endpoint); return (CreateMatcher(endpoint), endpoint);
} }
private class DynamicEndpointMetadata : IDynamicEndpointMetadata
{
public bool IsDynamic => true;
}
} }
} }

View File

@ -4,11 +4,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Matching;
using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.AspNetCore.Routing.Patterns;
using Xunit; using Xunit;
@ -17,12 +14,12 @@ namespace Microsoft.AspNetCore.Routing.Matching
public class HostMatcherPolicyTest public class HostMatcherPolicyTest
{ {
[Fact] [Fact]
public void AppliesToEndpoints_EndpointWithoutMetadata_ReturnsFalse() public void INodeBuilderPolicy_AppliesToEndpoints_EndpointWithoutMetadata_ReturnsFalse()
{ {
// Arrange // Arrange
var endpoints = new[] { CreateEndpoint("/", null), }; var endpoints = new[] { CreateEndpoint("/", null), };
var policy = CreatePolicy(); var policy = (INodeBuilderPolicy)CreatePolicy();
// Act // Act
var result = policy.AppliesToEndpoints(endpoints); var result = policy.AppliesToEndpoints(endpoints);
@ -32,7 +29,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
} }
[Fact] [Fact]
public void AppliesToEndpoints_EndpointWithoutHosts_ReturnsFalse() public void INodeBuilderPolicy_AppliesToEndpoints_EndpointWithoutHosts_ReturnsFalse()
{ {
// Arrange // Arrange
var endpoints = new[] var endpoints = new[]
@ -40,7 +37,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
CreateEndpoint("/", new HostAttribute(Array.Empty<string>())), CreateEndpoint("/", new HostAttribute(Array.Empty<string>())),
}; };
var policy = CreatePolicy(); var policy = (INodeBuilderPolicy)CreatePolicy();
// Act // Act
var result = policy.AppliesToEndpoints(endpoints); var result = policy.AppliesToEndpoints(endpoints);
@ -50,7 +47,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
} }
[Fact] [Fact]
public void AppliesToEndpoints_EndpointHasHosts_ReturnsTrue() public void INodeBuilderPolicy_AppliesToEndpoints_EndpointHasHosts_ReturnsTrue()
{ {
// Arrange // Arrange
var endpoints = new[] var endpoints = new[]
@ -59,7 +56,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
CreateEndpoint("/", new HostAttribute(new[] { "localhost", })), CreateEndpoint("/", new HostAttribute(new[] { "localhost", })),
}; };
var policy = CreatePolicy(); var policy = (INodeBuilderPolicy)CreatePolicy();
// Act // Act
var result = policy.AppliesToEndpoints(endpoints); var result = policy.AppliesToEndpoints(endpoints);
@ -68,6 +65,25 @@ namespace Microsoft.AspNetCore.Routing.Matching
Assert.True(result); Assert.True(result);
} }
[Fact]
public void INodeBuilderPolicy_AppliesToEndpoints_EndpointHasDynamicMetadata_ReturnsFalse()
{
// Arrange
var endpoints = new[]
{
CreateEndpoint("/", new HostAttribute(Array.Empty<string>())),
CreateEndpoint("/", new HostAttribute(new[] { "localhost", }), new DynamicEndpointMetadata()),
};
var policy = (INodeBuilderPolicy)CreatePolicy();
// Act
var result = policy.AppliesToEndpoints(endpoints);
// Assert
Assert.False(result);
}
[Theory] [Theory]
[InlineData(":")] [InlineData(":")]
[InlineData(":80")] [InlineData(":80")]
@ -75,12 +91,104 @@ namespace Microsoft.AspNetCore.Routing.Matching
[InlineData("")] [InlineData("")]
[InlineData("::")] [InlineData("::")]
[InlineData("*:test")] [InlineData("*:test")]
public void AppliesToEndpoints_InvalidHosts(string host) public void INodeBuilderPolicy_AppliesToEndpoints_InvalidHosts(string host)
{ {
// Arrange // Arrange
var endpoints = new[] { CreateEndpoint("/", new HostAttribute(new[] { host })), }; var endpoints = new[] { CreateEndpoint("/", new HostAttribute(new[] { host })), };
var policy = CreatePolicy(); var policy = (INodeBuilderPolicy)CreatePolicy();
// Act & Assert
Assert.Throws<InvalidOperationException>(() =>
{
policy.AppliesToEndpoints(endpoints);
});
}
[Fact]
public void IEndpointSelectorPolicy_AppliesToEndpoints_EndpointWithoutMetadata_ReturnsTrue()
{
// Arrange
var endpoints = new[] { CreateEndpoint("/", null, new DynamicEndpointMetadata()), };
var policy = (IEndpointSelectorPolicy)CreatePolicy();
// Act
var result = policy.AppliesToEndpoints(endpoints);
// Assert
Assert.True(result);
}
[Fact]
public void IEndpointSelectorPolicy_AppliesToEndpoints_EndpointWithoutHosts_ReturnsTrue()
{
// Arrange
var endpoints = new[]
{
CreateEndpoint("/", new HostAttribute(Array.Empty<string>()), new DynamicEndpointMetadata()),
};
var policy = (IEndpointSelectorPolicy)CreatePolicy();
// Act
var result = policy.AppliesToEndpoints(endpoints);
// Assert
Assert.True(result);
}
[Fact]
public void IEndpointSelectorPolicy_AppliesToEndpoints_EndpointHasHosts_ReturnsTrue()
{
// Arrange
var endpoints = new[]
{
CreateEndpoint("/", new HostAttribute(Array.Empty<string>())),
CreateEndpoint("/", new HostAttribute(new[] { "localhost", }), new DynamicEndpointMetadata()),
};
var policy = (IEndpointSelectorPolicy)CreatePolicy();
// Act
var result = policy.AppliesToEndpoints(endpoints);
// Assert
Assert.True(result);
}
[Fact]
public void IEndpointSelectorPolicy_AppliesToEndpoints_EndpointHasNoDynamicMetadata_ReturnsFalse()
{
// Arrange
var endpoints = new[]
{
CreateEndpoint("/", new HostAttribute(Array.Empty<string>())),
CreateEndpoint("/", new HostAttribute(new[] { "localhost", })),
};
var policy = (IEndpointSelectorPolicy)CreatePolicy();
// Act
var result = policy.AppliesToEndpoints(endpoints);
// Assert
Assert.False(result);
}
[Theory]
[InlineData(":")]
[InlineData(":80")]
[InlineData("80:")]
[InlineData("")]
[InlineData("::")]
[InlineData("*:test")]
public void IEndpointSelectorPolicy_AppliesToEndpoints_InvalidHosts(string host)
{
// Arrange
var endpoints = new[] { CreateEndpoint("/", new HostAttribute(new[] { host }), new DynamicEndpointMetadata()), };
var policy = (IEndpointSelectorPolicy)CreatePolicy();
// Act & Assert // Act & Assert
Assert.Throws<InvalidOperationException>(() => Assert.Throws<InvalidOperationException>(() =>
@ -152,7 +260,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
}); });
} }
private static RouteEndpoint CreateEndpoint(string template, IHostMetadata hostMetadata) private static RouteEndpoint CreateEndpoint(string template, IHostMetadata hostMetadata, params object[] more)
{ {
var metadata = new List<object>(); var metadata = new List<object>();
if (hostMetadata != null) if (hostMetadata != null)
@ -160,6 +268,11 @@ namespace Microsoft.AspNetCore.Routing.Matching
metadata.Add(hostMetadata); metadata.Add(hostMetadata);
} }
if (more != null)
{
metadata.AddRange(more);
}
return new RouteEndpoint( return new RouteEndpoint(
(context) => Task.CompletedTask, (context) => Task.CompletedTask,
RoutePatternFactory.Parse(template), RoutePatternFactory.Parse(template),
@ -172,5 +285,10 @@ namespace Microsoft.AspNetCore.Routing.Matching
{ {
return new HostMatcherPolicy(); return new HostMatcherPolicy();
} }
private class DynamicEndpointMetadata : IDynamicEndpointMetadata
{
public bool IsDynamic => true;
}
} }
} }