Implement IEndpointSelectorPolicy for ConsumesMatcherPolicy
This commit is contained in:
parent
eca6a71754
commit
6eeda774ef
|
|
@ -9,12 +9,10 @@ using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.AspNetCore.Routing.Matching;
|
using Microsoft.AspNetCore.Routing.Matching;
|
||||||
using Microsoft.AspNetCore.Routing.Patterns;
|
|
||||||
using Microsoft.Extensions.Primitives;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Routing
|
namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
{
|
{
|
||||||
internal class ConsumesMatcherPolicy : MatcherPolicy, IEndpointComparerPolicy, INodeBuilderPolicy
|
internal class ConsumesMatcherPolicy : MatcherPolicy, IEndpointComparerPolicy, INodeBuilderPolicy, IEndpointSelectorPolicy
|
||||||
{
|
{
|
||||||
internal const string Http415EndpointDisplayName = "415 HTTP Unsupported Media Type";
|
internal const string Http415EndpointDisplayName = "415 HTTP Unsupported Media Type";
|
||||||
internal const string AnyContentType = "*/*";
|
internal const string AnyContentType = "*/*";
|
||||||
|
|
@ -24,16 +22,140 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
|
|
||||||
public IComparer<Endpoint> Comparer { get; } = new ConsumesMetadataEndpointComparer();
|
public IComparer<Endpoint> Comparer { get; } = new ConsumesMetadataEndpointComparer();
|
||||||
|
|
||||||
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ContainsDynamicEndpoints(endpoints))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return AppliesToEndpointsCore(endpoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IEndpointSelectorPolicy.AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
|
||||||
|
{
|
||||||
|
if (endpoints == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(endpoints));
|
||||||
|
}
|
||||||
|
|
||||||
|
// When the node contains dynamic endpoints we can't make any assumptions.
|
||||||
|
return ContainsDynamicEndpoints(endpoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool AppliesToEndpointsCore(IReadOnlyList<Endpoint> endpoints)
|
||||||
|
{
|
||||||
return endpoints.Any(e => e.Metadata.GetMetadata<IConsumesMetadata>()?.ContentTypes.Count > 0);
|
return endpoints.Any(e => e.Metadata.GetMetadata<IConsumesMetadata>()?.ContentTypes.Count > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
// We want to return a 415 iff we eliminated ALL of the currently valid endpoints due to content type
|
||||||
|
// mismatch.
|
||||||
|
bool? needs415Endpoint = null;
|
||||||
|
|
||||||
|
for (var i = 0; i < candidates.Count; i++)
|
||||||
|
{
|
||||||
|
// We do this check first for consistency with how 415 is implemented for the graph version
|
||||||
|
// of this code. We still want to know if any endpoints in this set require an a ContentType
|
||||||
|
// even if those endpoints are already invalid.
|
||||||
|
var metadata = candidates[i].Endpoint.Metadata.GetMetadata<IConsumesMetadata>();
|
||||||
|
if (metadata == null || metadata.ContentTypes.Count == 0)
|
||||||
|
{
|
||||||
|
// Can match any content type.
|
||||||
|
needs415Endpoint = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Saw a valid endpoint.
|
||||||
|
needs415Endpoint = needs415Endpoint ?? true;
|
||||||
|
|
||||||
|
if (!candidates.IsValidCandidate(i))
|
||||||
|
{
|
||||||
|
// If the candidate is already invalid, then do a search to see if it has a wildcard content type.
|
||||||
|
//
|
||||||
|
// We don't want to return a 415 if any content type could be accepted depending on other parameters.
|
||||||
|
if (metadata != null)
|
||||||
|
{
|
||||||
|
for (var j = 0; j < metadata.ContentTypes.Count; j++)
|
||||||
|
{
|
||||||
|
if (string.Equals("*/*", metadata.ContentTypes[j], StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
needs415Endpoint = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var contentType = httpContext.Request.ContentType;
|
||||||
|
var mediaType = string.IsNullOrEmpty(contentType) ? (MediaType?)null : new MediaType(contentType);
|
||||||
|
|
||||||
|
var matched = false;
|
||||||
|
for (var j = 0; j < metadata.ContentTypes.Count; j++)
|
||||||
|
{
|
||||||
|
var candidateMediaType = new MediaType(metadata.ContentTypes[j]);
|
||||||
|
if (candidateMediaType.MatchesAllTypes)
|
||||||
|
{
|
||||||
|
// We don't need a 415 response because there's an endpoint that would accept any type.
|
||||||
|
needs415Endpoint = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there's no ContentType, then then can only matched by a wildcard `*/*`.
|
||||||
|
if (mediaType == null && !candidateMediaType.MatchesAllTypes)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have a ContentType but it's not a match.
|
||||||
|
else if (mediaType != null && !mediaType.Value.IsSubsetOf(candidateMediaType))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have a ContentType and we accept any value OR we have a ContentType and it's a match.
|
||||||
|
matched = true;
|
||||||
|
needs415Endpoint = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!matched)
|
||||||
|
{
|
||||||
|
candidates.SetValidity(i, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needs415Endpoint == true)
|
||||||
|
{
|
||||||
|
// We saw some endpoints coming in, and we eliminated them all.
|
||||||
|
context.Endpoint = CreateRejectionEndpoint();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
public IReadOnlyList<PolicyNodeEdge> GetEdges(IReadOnlyList<Endpoint> endpoints)
|
public IReadOnlyList<PolicyNodeEdge> GetEdges(IReadOnlyList<Endpoint> endpoints)
|
||||||
{
|
{
|
||||||
if (endpoints == null)
|
if (endpoints == null)
|
||||||
|
|
|
||||||
|
|
@ -13,15 +13,17 @@ using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Routing
|
namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
{
|
{
|
||||||
|
// There are some unit tests here for the IEndpointSelectorPolicy implementation.
|
||||||
|
// The INodeBuilderPolicy implementation is well-tested by functional tests.
|
||||||
public class ConsumesMatcherPolicyTest
|
public class ConsumesMatcherPolicyTest
|
||||||
{
|
{
|
||||||
[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);
|
||||||
|
|
@ -31,7 +33,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void AppliesToEndpoints_EndpointWithoutContentTypes_ReturnsFalse()
|
public void INodeBuilderPolicy_AppliesToEndpoints_EndpointWithoutContentTypes_ReturnsFalse()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var endpoints = new[]
|
var endpoints = new[]
|
||||||
|
|
@ -39,7 +41,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
CreateEndpoint("/", new ConsumesMetadata(Array.Empty<string>())),
|
CreateEndpoint("/", new ConsumesMetadata(Array.Empty<string>())),
|
||||||
};
|
};
|
||||||
|
|
||||||
var policy = CreatePolicy();
|
var policy = (INodeBuilderPolicy)CreatePolicy();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = policy.AppliesToEndpoints(endpoints);
|
var result = policy.AppliesToEndpoints(endpoints);
|
||||||
|
|
@ -49,7 +51,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void AppliesToEndpoints_EndpointHasContentTypes_ReturnsTrue()
|
public void INodeBuilderPolicy_AppliesToEndpoints_EndpointHasContentTypes_ReturnsTrue()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var endpoints = new[]
|
var endpoints = new[]
|
||||||
|
|
@ -58,7 +60,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
CreateEndpoint("/", new ConsumesMetadata(new[] { "application/json", })),
|
CreateEndpoint("/", new ConsumesMetadata(new[] { "application/json", })),
|
||||||
};
|
};
|
||||||
|
|
||||||
var policy = CreatePolicy();
|
var policy = (INodeBuilderPolicy)CreatePolicy();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = policy.AppliesToEndpoints(endpoints);
|
var result = policy.AppliesToEndpoints(endpoints);
|
||||||
|
|
@ -67,6 +69,96 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
Assert.True(result);
|
Assert.True(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void INodeBuilderPolicy_AppliesToEndpoints_WithDynamicMetadata_ReturnsFalse()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoints = new[]
|
||||||
|
{
|
||||||
|
CreateEndpoint("/", new ConsumesMetadata(Array.Empty<string>()), new DynamicEndpointMetadata()),
|
||||||
|
CreateEndpoint("/", new ConsumesMetadata(new[] { "application/json", })),
|
||||||
|
};
|
||||||
|
|
||||||
|
var policy = (INodeBuilderPolicy)CreatePolicy();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = policy.AppliesToEndpoints(endpoints);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[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_EndpointWithoutContentTypes_ReturnsTrue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoints = new[]
|
||||||
|
{
|
||||||
|
CreateEndpoint("/", new ConsumesMetadata(Array.Empty<string>()), new DynamicEndpointMetadata()),
|
||||||
|
};
|
||||||
|
|
||||||
|
var policy = (IEndpointSelectorPolicy)CreatePolicy();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = policy.AppliesToEndpoints(endpoints);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IEndpointSelectorPolicy_AppliesToEndpoints_EndpointHasContentTypes_ReturnsTrue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoints = new[]
|
||||||
|
{
|
||||||
|
CreateEndpoint("/", new ConsumesMetadata(Array.Empty<string>()), new DynamicEndpointMetadata()),
|
||||||
|
CreateEndpoint("/", new ConsumesMetadata(new[] { "application/json", })),
|
||||||
|
};
|
||||||
|
|
||||||
|
var policy = (IEndpointSelectorPolicy)CreatePolicy();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = policy.AppliesToEndpoints(endpoints);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IEndpointSelectorPolicy_AppliesToEndpoints_WithoutDynamicMetadata_ReturnsFalse()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoints = new[]
|
||||||
|
{
|
||||||
|
CreateEndpoint("/", new ConsumesMetadata(Array.Empty<string>())),
|
||||||
|
CreateEndpoint("/", new ConsumesMetadata(new[] { "application/json", })),
|
||||||
|
};
|
||||||
|
|
||||||
|
var policy = (IEndpointSelectorPolicy)CreatePolicy();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = policy.AppliesToEndpoints(endpoints);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(result);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GetEdges_GroupsByContentType()
|
public void GetEdges_GroupsByContentType()
|
||||||
{
|
{
|
||||||
|
|
@ -224,7 +316,303 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
Assert.Equal(expected, actual);
|
Assert.Equal(expected, actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static RouteEndpoint CreateEndpoint(string template, ConsumesMetadata consumesMetadata)
|
[Fact]
|
||||||
|
public async Task ApplyAsync_EndpointWithoutMetadata_MatchWithoutContentType()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoints = new[]
|
||||||
|
{
|
||||||
|
CreateEndpoint("/", null),
|
||||||
|
};
|
||||||
|
|
||||||
|
var candidates = CreateCandidateSet(endpoints);
|
||||||
|
var context = new EndpointSelectorContext();
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
|
||||||
|
var policy = CreatePolicy();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await policy.ApplyAsync(httpContext, context, candidates);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(candidates.IsValidCandidate(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApplyAsync_EndpointAllowsAnyContentType_MatchWithoutContentType()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoints = new[]
|
||||||
|
{
|
||||||
|
CreateEndpoint("/", new ConsumesMetadata(Array.Empty<string>())),
|
||||||
|
};
|
||||||
|
|
||||||
|
var candidates = CreateCandidateSet(endpoints);
|
||||||
|
var context = new EndpointSelectorContext();
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
|
||||||
|
var policy = CreatePolicy();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await policy.ApplyAsync(httpContext, context, candidates);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(candidates.IsValidCandidate(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApplyAsync_EndpointHasWildcardContentType_MatchWithoutContentType()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoints = new[]
|
||||||
|
{
|
||||||
|
CreateEndpoint("/", new ConsumesMetadata(new string[] { "*/*" })),
|
||||||
|
};
|
||||||
|
|
||||||
|
var candidates = CreateCandidateSet(endpoints);
|
||||||
|
var context = new EndpointSelectorContext();
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
|
||||||
|
var policy = CreatePolicy();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await policy.ApplyAsync(httpContext, context, candidates);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(candidates.IsValidCandidate(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApplyAsync_EndpointWithoutMetadata_MatchWithAnyContentType()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoints = new[]
|
||||||
|
{
|
||||||
|
CreateEndpoint("/", null),
|
||||||
|
};
|
||||||
|
|
||||||
|
var candidates = CreateCandidateSet(endpoints);
|
||||||
|
var context = new EndpointSelectorContext();
|
||||||
|
var httpContext = new DefaultHttpContext()
|
||||||
|
{
|
||||||
|
Request =
|
||||||
|
{
|
||||||
|
ContentType = "text/plain",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var policy = CreatePolicy();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await policy.ApplyAsync(httpContext, context, candidates);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(candidates.IsValidCandidate(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApplyAsync_EndpointAllowsAnyContentType_MatchWithAnyContentType()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoints = new[]
|
||||||
|
{
|
||||||
|
CreateEndpoint("/", new ConsumesMetadata(Array.Empty<string>())),
|
||||||
|
};
|
||||||
|
|
||||||
|
var candidates = CreateCandidateSet(endpoints);
|
||||||
|
var context = new EndpointSelectorContext();
|
||||||
|
var httpContext = new DefaultHttpContext()
|
||||||
|
{
|
||||||
|
Request =
|
||||||
|
{
|
||||||
|
ContentType = "text/plain",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var policy = CreatePolicy();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await policy.ApplyAsync(httpContext, context, candidates);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(candidates.IsValidCandidate(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApplyAsync_EndpointHasWildcardContentType_MatchWithAnyContentType()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoints = new[]
|
||||||
|
{
|
||||||
|
CreateEndpoint("/", new ConsumesMetadata(new string[] { "*/*" })),
|
||||||
|
};
|
||||||
|
|
||||||
|
var candidates = CreateCandidateSet(endpoints);
|
||||||
|
var context = new EndpointSelectorContext();
|
||||||
|
var httpContext = new DefaultHttpContext()
|
||||||
|
{
|
||||||
|
Request =
|
||||||
|
{
|
||||||
|
ContentType = "text/plain",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var policy = CreatePolicy();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await policy.ApplyAsync(httpContext, context, candidates);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(candidates.IsValidCandidate(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApplyAsync_EndpointHasSubTypeWildcard_MatchWithValidContentType()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoints = new[]
|
||||||
|
{
|
||||||
|
CreateEndpoint("/", new ConsumesMetadata(new string[] { "application/*+json", })),
|
||||||
|
};
|
||||||
|
|
||||||
|
var candidates = CreateCandidateSet(endpoints);
|
||||||
|
var context = new EndpointSelectorContext();
|
||||||
|
var httpContext = new DefaultHttpContext()
|
||||||
|
{
|
||||||
|
Request =
|
||||||
|
{
|
||||||
|
ContentType = "application/project+json",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var policy = CreatePolicy();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await policy.ApplyAsync(httpContext, context, candidates);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(candidates.IsValidCandidate(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApplyAsync_EndpointHasMultipleContentType_MatchWithValidContentType()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoints = new[]
|
||||||
|
{
|
||||||
|
CreateEndpoint("/", new ConsumesMetadata(new string[] { "text/xml", "application/xml", })),
|
||||||
|
};
|
||||||
|
|
||||||
|
var candidates = CreateCandidateSet(endpoints);
|
||||||
|
var context = new EndpointSelectorContext();
|
||||||
|
var httpContext = new DefaultHttpContext()
|
||||||
|
{
|
||||||
|
Request =
|
||||||
|
{
|
||||||
|
ContentType = "application/xml",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var policy = CreatePolicy();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await policy.ApplyAsync(httpContext, context, candidates);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(candidates.IsValidCandidate(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApplyAsync_EndpointDoesNotMatch_Returns415()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoints = new[]
|
||||||
|
{
|
||||||
|
CreateEndpoint("/", new ConsumesMetadata(new string[] { "text/xml", "application/xml", })),
|
||||||
|
};
|
||||||
|
|
||||||
|
var candidates = CreateCandidateSet(endpoints);
|
||||||
|
var context = new EndpointSelectorContext();
|
||||||
|
var httpContext = new DefaultHttpContext()
|
||||||
|
{
|
||||||
|
Request =
|
||||||
|
{
|
||||||
|
ContentType = "application/json",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var policy = CreatePolicy();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await policy.ApplyAsync(httpContext, context, candidates);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(candidates.IsValidCandidate(0));
|
||||||
|
Assert.NotNull(context.Endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApplyAsync_EndpointDoesNotMatch_DoesNotReturns415WithContentTypeObliviousEndpoint()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoints = new[]
|
||||||
|
{
|
||||||
|
CreateEndpoint("/", new ConsumesMetadata(new string[] { "text/xml", "application/xml", })),
|
||||||
|
CreateEndpoint("/", null)
|
||||||
|
};
|
||||||
|
|
||||||
|
var candidates = CreateCandidateSet(endpoints);
|
||||||
|
var context = new EndpointSelectorContext();
|
||||||
|
var httpContext = new DefaultHttpContext()
|
||||||
|
{
|
||||||
|
Request =
|
||||||
|
{
|
||||||
|
ContentType = "application/json",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var policy = CreatePolicy();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await policy.ApplyAsync(httpContext, context, candidates);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(candidates.IsValidCandidate(0));
|
||||||
|
Assert.Null(context.Endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApplyAsync_EndpointDoesNotMatch_DoesNotReturns415WithContentTypeWildcardEndpoint()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoints = new[]
|
||||||
|
{
|
||||||
|
CreateEndpoint("/", new ConsumesMetadata(new string[] { "text/xml", "application/xml", })),
|
||||||
|
CreateEndpoint("/", new ConsumesMetadata(new string[] { "*/*", }))
|
||||||
|
};
|
||||||
|
|
||||||
|
var candidates = CreateCandidateSet(endpoints);
|
||||||
|
var context = new EndpointSelectorContext();
|
||||||
|
var httpContext = new DefaultHttpContext()
|
||||||
|
{
|
||||||
|
Request =
|
||||||
|
{
|
||||||
|
ContentType = "application/json",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var policy = CreatePolicy();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await policy.ApplyAsync(httpContext, context, candidates);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(candidates.IsValidCandidate(0));
|
||||||
|
Assert.True(candidates.IsValidCandidate(1));
|
||||||
|
Assert.Null(context.Endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RouteEndpoint CreateEndpoint(string template, ConsumesMetadata consumesMetadata, params object[] more)
|
||||||
{
|
{
|
||||||
var metadata = new List<object>();
|
var metadata = new List<object>();
|
||||||
if (consumesMetadata != null)
|
if (consumesMetadata != null)
|
||||||
|
|
@ -232,6 +620,11 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
metadata.Add(consumesMetadata);
|
metadata.Add(consumesMetadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (more != null)
|
||||||
|
{
|
||||||
|
metadata.AddRange(more);
|
||||||
|
}
|
||||||
|
|
||||||
return new RouteEndpoint(
|
return new RouteEndpoint(
|
||||||
(context) => Task.CompletedTask,
|
(context) => Task.CompletedTask,
|
||||||
RoutePatternFactory.Parse(template),
|
RoutePatternFactory.Parse(template),
|
||||||
|
|
@ -240,9 +633,19 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
$"test: {template} - {string.Join(", ", consumesMetadata?.ContentTypes ?? Array.Empty<string>())}");
|
$"test: {template} - {string.Join(", ", consumesMetadata?.ContentTypes ?? Array.Empty<string>())}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static CandidateSet CreateCandidateSet(Endpoint[] endpoints)
|
||||||
|
{
|
||||||
|
return new CandidateSet(endpoints, new RouteValueDictionary[endpoints.Length], new int[endpoints.Length]);
|
||||||
|
}
|
||||||
|
|
||||||
private static ConsumesMatcherPolicy CreatePolicy()
|
private static ConsumesMatcherPolicy CreatePolicy()
|
||||||
{
|
{
|
||||||
return new ConsumesMatcherPolicy();
|
return new ConsumesMatcherPolicy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class DynamicEndpointMetadata : IDynamicEndpointMetadata
|
||||||
|
{
|
||||||
|
public bool IsDynamic => true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue