Fix #629 - Add logging to DfaMatcther
Adds logging for the most common things that can prevent an endpoint from matching. Note that we already have good logging in other parts of the system, the stuff here completes the story by providing details at the debug level.
This commit is contained in:
parent
b7939328b8
commit
3227de8c0b
|
|
@ -421,8 +421,8 @@ namespace Microsoft.AspNetCore.Routing
|
|||
LogLevel.Debug,
|
||||
EventIds.TemplateFailedExpansion,
|
||||
"Failed to process the template {Template} for {Endpoint}. " +
|
||||
"The failure occured while expanding the template with values {Values}. " +
|
||||
"This is usually due to a missing or empty value in a complex segment.");
|
||||
"The failure occured while expanding the template with values {Values} " +
|
||||
"This is usually due to a missing or empty value in a complex segment");
|
||||
|
||||
private static readonly Action<ILogger, IEnumerable<string>, string, Exception> _linkGenerationSucceeded = LoggerMessage.Define<IEnumerable<string>, string>(
|
||||
LogLevel.Debug,
|
||||
|
|
|
|||
|
|
@ -57,12 +57,12 @@ namespace Microsoft.AspNetCore.Routing
|
|||
private static readonly Action<ILogger, string, Exception> _executingEndpoint = LoggerMessage.Define<string>(
|
||||
LogLevel.Information,
|
||||
new EventId(0, "ExecutingEndpoint"),
|
||||
"Executing endpoint '{EndpointName}'.");
|
||||
"Executing endpoint '{EndpointName}'");
|
||||
|
||||
private static readonly Action<ILogger, string, Exception> _executedEndpoint = LoggerMessage.Define<string>(
|
||||
LogLevel.Information,
|
||||
new EventId(1, "ExecutedEndpoint"),
|
||||
"Executed endpoint '{EndpointName}'.");
|
||||
"Executed endpoint '{EndpointName}'");
|
||||
|
||||
public static void ExecutingEndpoint(ILogger logger, Endpoint endpoint)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -119,12 +119,12 @@ namespace Microsoft.AspNetCore.Routing
|
|||
private static readonly Action<ILogger, string, Exception> _matchSuccess = LoggerMessage.Define<string>(
|
||||
LogLevel.Debug,
|
||||
new EventId(1, "MatchSuccess"),
|
||||
"Request matched endpoint '{EndpointName}'.");
|
||||
"Request matched endpoint '{EndpointName}'");
|
||||
|
||||
private static readonly Action<ILogger, Exception> _matchFailure = LoggerMessage.Define(
|
||||
LogLevel.Debug,
|
||||
new EventId(2, "MatchFailure"),
|
||||
"Request did not match any endpoints.");
|
||||
"Request did not match any endpoints");
|
||||
|
||||
public static void MatchSuccess(ILogger logger, EndpointSelectorContext context)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Routing.Logging
|
|||
LogLevel.Debug,
|
||||
1,
|
||||
"Route value '{RouteValue}' with key '{RouteKey}' did not match " +
|
||||
"the constraint '{RouteConstraint}'.");
|
||||
"the constraint '{RouteConstraint}'");
|
||||
}
|
||||
|
||||
public static void RouteValueDoesNotMatchConstraint(
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Routing.Logging
|
|||
_requestDidNotMatchRoutes = LoggerMessage.Define(
|
||||
LogLevel.Debug,
|
||||
1,
|
||||
"Request did not match any routes.");
|
||||
"Request did not match any routes");
|
||||
}
|
||||
|
||||
public static void RequestDidNotMatchRoutes(this ILogger logger)
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Routing.Logging
|
|||
_matchedRoute = LoggerMessage.Define<string, string>(
|
||||
LogLevel.Debug,
|
||||
1,
|
||||
"Request successfully matched the route with name '{RouteName}' and template '{RouteTemplate}'.");
|
||||
"Request successfully matched the route with name '{RouteName}' and template '{RouteTemplate}'");
|
||||
}
|
||||
|
||||
public static void MatchedRoute(
|
||||
|
|
|
|||
|
|
@ -6,17 +6,20 @@ using System.Collections.Generic;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing.Patterns;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
internal sealed class DfaMatcher : Matcher
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly EndpointSelector _selector;
|
||||
private readonly DfaState[] _states;
|
||||
private readonly int _maxSegmentCount;
|
||||
|
||||
public DfaMatcher(EndpointSelector selector, DfaState[] states, int maxSegmentCount)
|
||||
public DfaMatcher(ILogger<DfaMatcher> logger, EndpointSelector selector, DfaState[] states, int maxSegmentCount)
|
||||
{
|
||||
_logger = logger;
|
||||
_selector = selector;
|
||||
_states = states;
|
||||
_maxSegmentCount = maxSegmentCount;
|
||||
|
|
@ -34,6 +37,9 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
// All of the logging we do here is at level debug, so we can get away with doing a single check.
|
||||
var log = _logger.IsEnabled(LogLevel.Debug);
|
||||
|
||||
// The sequence of actions we take is optimized to avoid doing expensive work
|
||||
// like creating substrings, creating route value dictionaries, and calling
|
||||
// into policies like versioning.
|
||||
|
|
@ -49,9 +55,19 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
var candidates = FindCandidateSet(httpContext, path, segments);
|
||||
if (candidates.Length == 0)
|
||||
{
|
||||
if (log)
|
||||
{
|
||||
Logger.CandidatesNotFound(_logger, path);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
if (log)
|
||||
{
|
||||
Logger.CandidatesFound(_logger, path, candidates);
|
||||
}
|
||||
|
||||
// At this point we have a candidate set, defined as a list of endpoints in
|
||||
// priority order.
|
||||
//
|
||||
|
|
@ -117,21 +133,34 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
// Now that we have the route values, we need to process complex segments.
|
||||
// Complex segments go through an old API that requires a fully-materialized
|
||||
// route value dictionary.
|
||||
var isMatch = true;
|
||||
if ((flags & Candidate.CandidateFlags.HasComplexSegments) != 0)
|
||||
{
|
||||
if (!ProcessComplexSegments(candidate.ComplexSegments, path, segments, values))
|
||||
if (!ProcessComplexSegments(candidate.Endpoint, candidate.ComplexSegments, path, segments, values))
|
||||
{
|
||||
candidateSet.SetValidity(i, false);
|
||||
continue;
|
||||
isMatch = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ((flags & Candidate.CandidateFlags.HasConstraints) != 0)
|
||||
{
|
||||
if (!ProcessConstraints(candidate.Constraints, httpContext, values))
|
||||
if (!ProcessConstraints(candidate.Endpoint, candidate.Constraints, httpContext, values))
|
||||
{
|
||||
candidateSet.SetValidity(i, false);
|
||||
continue;
|
||||
isMatch = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (log)
|
||||
{
|
||||
if (isMatch)
|
||||
{
|
||||
Logger.CandidateValid(_logger, path, candidate.Endpoint);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.CandidateNotValid(_logger, path, candidate.Endpoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -202,6 +231,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
}
|
||||
|
||||
private bool ProcessComplexSegments(
|
||||
Endpoint endpoint,
|
||||
(RoutePatternPathSegment pathSegment, int segmentIndex)[] complexSegments,
|
||||
string path,
|
||||
ReadOnlySpan<PathSegment> segments,
|
||||
|
|
@ -209,10 +239,12 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
{
|
||||
for (var i = 0; i < complexSegments.Length; i++)
|
||||
{
|
||||
var segment = segments[complexSegments[i].segmentIndex];
|
||||
(var complexSegment, var segmentIndex) = complexSegments[i];
|
||||
var segment = segments[segmentIndex];
|
||||
var text = path.Substring(segment.Start, segment.Length);
|
||||
if (!RoutePatternMatcher.MatchComplexSegment(complexSegments[i].pathSegment, text, values))
|
||||
if (!RoutePatternMatcher.MatchComplexSegment(complexSegment, text, values))
|
||||
{
|
||||
Logger.CandidateRejectedByComplexSegment(_logger, path, endpoint, complexSegment);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -221,6 +253,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
}
|
||||
|
||||
private bool ProcessConstraints(
|
||||
Endpoint endpoint,
|
||||
KeyValuePair<string, IRouteConstraint>[] constraints,
|
||||
HttpContext httpContext,
|
||||
RouteValueDictionary values)
|
||||
|
|
@ -230,11 +263,112 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
var constraint = constraints[i];
|
||||
if (!constraint.Value.Match(httpContext, NullRouter.Instance, constraint.Key, values, RouteDirection.IncomingRequest))
|
||||
{
|
||||
Logger.CandidateRejectedByConstraint(_logger, httpContext.Request.Path, endpoint, constraint.Key, constraint.Value, values[constraint.Key]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static class EventIds
|
||||
{
|
||||
public static readonly EventId CandidatesNotFound = new EventId(1000, "CandidatesNotFound");
|
||||
public static readonly EventId CandidatesFound = new EventId(1001, "CandidatesFound");
|
||||
|
||||
public static readonly EventId CandidateRejectedByComplexSegment = new EventId(1002, "CandidateRejectedByComplexSegment");
|
||||
public static readonly EventId CandidateRejectedByConstraint = new EventId(1003, "CandidateRejectedByConstraint");
|
||||
|
||||
public static readonly EventId CandidateNotValid = new EventId(1004, "CandiateNotValid");
|
||||
public static readonly EventId CandidateValid = new EventId(1005, "CandiateValid");
|
||||
}
|
||||
|
||||
private static class Logger
|
||||
{
|
||||
private static readonly Action<ILogger, string, Exception> _candidatesNotFound = LoggerMessage.Define<string>(
|
||||
LogLevel.Debug,
|
||||
EventIds.CandidatesNotFound,
|
||||
"No candidates found for the request path '{Path}'");
|
||||
|
||||
private static readonly Action<ILogger, int, string, Exception> _candidatesFound = LoggerMessage.Define<int, string>(
|
||||
LogLevel.Debug,
|
||||
EventIds.CandidatesFound,
|
||||
"{CandidateCount} candidate(s) found for the request path '{Path}'");
|
||||
|
||||
private static readonly Action<ILogger, string, string, string, string, Exception> _candidateRejectedByComplexSegment = LoggerMessage.Define<string, string, string, string>(
|
||||
LogLevel.Debug,
|
||||
EventIds.CandidateRejectedByComplexSegment,
|
||||
"Endpoint '{Endpoint}' with route pattern '{RoutePattern}' was rejected by complex segment '{Segment}' for the request path '{Path}'");
|
||||
|
||||
private static readonly Action<ILogger, string, string, string, string, object, string, Exception> _candidateRejectedByConstraint = LoggerMessage.Define<string, string, string, string, object, string>(
|
||||
LogLevel.Debug,
|
||||
EventIds.CandidateRejectedByConstraint,
|
||||
"Endpoint '{Endpoint}' with route pattern '{RoutePattern}' was rejected by constraint '{ConstraintName}':'{Constraint}' with value '{RouteValue}' for the request path '{Path}'");
|
||||
|
||||
private static readonly Action<ILogger, string, string, string, Exception> _candidateNotValid = LoggerMessage.Define<string, string, string>(
|
||||
LogLevel.Debug,
|
||||
EventIds.CandidateNotValid,
|
||||
"Endpoint '{Endpoint}' with route pattern '{RoutePattern}' is not valid for the request path '{Path}'");
|
||||
|
||||
private static readonly Action<ILogger, string, string, string, Exception> _candidateValid = LoggerMessage.Define<string, string, string>(
|
||||
LogLevel.Debug,
|
||||
EventIds.CandidateValid,
|
||||
"Endpoint '{Endpoint}' with route pattern '{RoutePattern}' is valid for the request path '{Path}'");
|
||||
|
||||
public static void CandidatesNotFound(ILogger logger, string path)
|
||||
{
|
||||
_candidatesNotFound(logger, path, null);
|
||||
}
|
||||
|
||||
public static void CandidatesFound(ILogger logger, string path, Candidate[] candidates)
|
||||
{
|
||||
_candidatesFound(logger, candidates.Length, path, null);
|
||||
}
|
||||
|
||||
public static void CandidateRejectedByComplexSegment(ILogger logger, string path, Endpoint endpoint, RoutePatternPathSegment segment)
|
||||
{
|
||||
// This should return a real pattern since we're processing complex segments.... but just in case.
|
||||
if (logger.IsEnabled(LogLevel.Debug))
|
||||
{
|
||||
var routePattern = GetRoutePattern(endpoint);
|
||||
_candidateRejectedByComplexSegment(logger, endpoint.DisplayName, routePattern, segment.DebuggerToString(), path, null);
|
||||
}
|
||||
}
|
||||
|
||||
public static void CandidateRejectedByConstraint(ILogger logger, string path, Endpoint endpoint, string constraintName, IRouteConstraint constraint, object value)
|
||||
{
|
||||
// This should return a real pattern since we're processing constraints.... but just in case.
|
||||
if (logger.IsEnabled(LogLevel.Debug))
|
||||
{
|
||||
var routePattern = GetRoutePattern(endpoint);
|
||||
_candidateRejectedByConstraint(logger, endpoint.DisplayName, routePattern, constraintName, constraint.ToString(), value, path, null);
|
||||
}
|
||||
}
|
||||
|
||||
public static void CandidateNotValid(ILogger logger, string path, Endpoint endpoint)
|
||||
{
|
||||
// This can be the fallback value because it really might not be a route endpoint
|
||||
if (logger.IsEnabled(LogLevel.Debug))
|
||||
{
|
||||
var routePattern = GetRoutePattern(endpoint);
|
||||
_candidateNotValid(logger, endpoint.DisplayName, routePattern, path, null);
|
||||
}
|
||||
}
|
||||
|
||||
public static void CandidateValid(ILogger logger, string path, Endpoint endpoint)
|
||||
{
|
||||
// This can be the fallback value because it really might not be a route endpoint
|
||||
if (logger.IsEnabled(LogLevel.Debug))
|
||||
{
|
||||
var routePattern = GetRoutePattern(endpoint);
|
||||
_candidateValid(logger, endpoint.DisplayName, routePattern, path, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetRoutePattern(Endpoint endpoint)
|
||||
{
|
||||
return (endpoint as RouteEndpoint)?.RoutePattern?.RawText ?? "(none)";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing.Patterns;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
|
|
@ -13,6 +14,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
{
|
||||
private readonly List<RouteEndpoint> _endpoints = new List<RouteEndpoint>();
|
||||
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
private readonly ParameterPolicyFactory _parameterPolicyFactory;
|
||||
private readonly EndpointSelector _selector;
|
||||
private readonly MatcherPolicy[] _policies;
|
||||
|
|
@ -29,10 +31,12 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
private int _stateIndex;
|
||||
|
||||
public DfaMatcherBuilder(
|
||||
ILoggerFactory loggerFactory,
|
||||
ParameterPolicyFactory parameterPolicyFactory,
|
||||
EndpointSelector selector,
|
||||
IEnumerable<MatcherPolicy> policies)
|
||||
{
|
||||
_loggerFactory = loggerFactory;
|
||||
_parameterPolicyFactory = parameterPolicyFactory;
|
||||
_selector = selector;
|
||||
_policies = policies.OrderBy(p => p.Order).ToArray();
|
||||
|
|
@ -304,7 +308,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
JumpTableBuilder.Build(exitDestination, exitDestination, null),
|
||||
null);
|
||||
|
||||
return new DfaMatcher(_selector, states, maxSegmentCount);
|
||||
return new DfaMatcher(_loggerFactory.CreateLogger<DfaMatcher>(), _selector, states, maxSegmentCount);
|
||||
}
|
||||
|
||||
private int AddNode(
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
};
|
||||
var sink = SetUpMatch(constraints, loggerEnabled: true);
|
||||
var expectedMessage = "Route value 'value' with key 'b' did not match the constraint " +
|
||||
$"'{typeof(FailConstraint).FullName}'.";
|
||||
$"'{typeof(FailConstraint).FullName}'";
|
||||
|
||||
// Assert
|
||||
Assert.Empty(sink.Scopes);
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
public async Task Invoke_OnCall_WritesToConfiguredLogger()
|
||||
{
|
||||
// Arrange
|
||||
var expectedMessage = "Request matched endpoint 'Test endpoint'.";
|
||||
var expectedMessage = "Request matched endpoint 'Test endpoint'";
|
||||
|
||||
var sink = new TestSink(
|
||||
TestSink.EnableWithTypeName<EndpointRoutingMiddleware>,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing.Patterns;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -117,6 +118,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
{
|
||||
var dataSource = new CompositeEndpointDataSource(Array.Empty<EndpointDataSource>());
|
||||
return new DfaMatcherBuilder(
|
||||
NullLoggerFactory.Instance,
|
||||
Mock.Of<ParameterPolicyFactory>(),
|
||||
Mock.Of<EndpointSelector>(),
|
||||
policies);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using System.Linq;
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing.Constraints;
|
||||
using Microsoft.AspNetCore.Routing.Patterns;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
|
@ -972,6 +973,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
{
|
||||
var dataSource = new CompositeEndpointDataSource(Array.Empty<EndpointDataSource>());
|
||||
return new DfaMatcherBuilder(
|
||||
NullLoggerFactory.Instance,
|
||||
new DefaultParameterPolicyFactory(Options.Create(new RouteOptions()), Mock.Of<IServiceProvider>()),
|
||||
Mock.Of<EndpointSelector>(),
|
||||
policies);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ using Microsoft.AspNetCore.Http;
|
|||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Routing.Patterns;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Testing;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -26,7 +28,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
template);
|
||||
}
|
||||
|
||||
private Matcher CreateDfaMatcher(EndpointDataSource dataSource, EndpointSelector endpointSelector = null)
|
||||
private Matcher CreateDfaMatcher(EndpointDataSource dataSource, EndpointSelector endpointSelector = null, ILoggerFactory loggerFactory = null)
|
||||
{
|
||||
var serviceCollection = new ServiceCollection()
|
||||
.AddLogging()
|
||||
|
|
@ -38,6 +40,11 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
serviceCollection.AddSingleton<EndpointSelector>(endpointSelector);
|
||||
}
|
||||
|
||||
if (loggerFactory != null)
|
||||
{
|
||||
serviceCollection.AddSingleton<ILoggerFactory>(loggerFactory);
|
||||
}
|
||||
|
||||
var services = serviceCollection.BuildServiceProvider();
|
||||
|
||||
var factory = services.GetRequiredService<MatcherFactory>();
|
||||
|
|
@ -76,14 +83,14 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
|
||||
var matcher = CreateDfaMatcher(endpointDataSource);
|
||||
|
||||
var (httpContext, endpointFeature) = CreateContext();
|
||||
var (httpContext, context) = CreateContext();
|
||||
httpContext.Request.Path = "/One";
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, endpointFeature);
|
||||
await matcher.MatchAsync(httpContext, context);
|
||||
|
||||
// Assert
|
||||
Assert.Null(endpointFeature.Endpoint);
|
||||
Assert.Null(context.Endpoint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -101,14 +108,14 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
|
||||
var matcher = CreateDfaMatcher(endpointDataSource);
|
||||
|
||||
var (httpContext, endpointFeature) = CreateContext();
|
||||
var (httpContext, context) = CreateContext();
|
||||
httpContext.Request.Path = "/Teams";
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, endpointFeature);
|
||||
await matcher.MatchAsync(httpContext, context);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(lowerOrderEndpoint, endpointFeature.Endpoint);
|
||||
Assert.Equal(lowerOrderEndpoint, context.Endpoint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -147,14 +154,181 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
|
||||
var matcher = CreateDfaMatcher(endpointDataSource, endpointSelector.Object);
|
||||
|
||||
var (httpContext, endpointFeature) = CreateContext();
|
||||
var (httpContext, context) = CreateContext();
|
||||
httpContext.Request.Path = "/Teams";
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, endpointFeature);
|
||||
await matcher.MatchAsync(httpContext, context);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(endpoint2, endpointFeature.Endpoint);
|
||||
Assert.Equal(endpoint2, context.Endpoint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MatchAsync_NoCandidates_Logging()
|
||||
{
|
||||
// Arrange
|
||||
var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint>
|
||||
{
|
||||
CreateEndpoint("/{p:int}", 0)
|
||||
});
|
||||
|
||||
var sink = new TestSink();
|
||||
var matcher = CreateDfaMatcher(endpointDataSource, loggerFactory: new TestLoggerFactory(sink, enabled: true));
|
||||
|
||||
var (httpContext, context) = CreateContext();
|
||||
httpContext.Request.Path = "/";
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, context);
|
||||
|
||||
// Assert
|
||||
Assert.Null(context.Endpoint);
|
||||
|
||||
Assert.Collection(
|
||||
sink.Writes,
|
||||
(log) =>
|
||||
{
|
||||
Assert.Equal(DfaMatcher.EventIds.CandidatesNotFound, log.EventId);
|
||||
Assert.Equal("No candidates found for the request path '/'", log.Message);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MatchAsync_ConstraintRejectsEndpoint_Logging()
|
||||
{
|
||||
// Arrange
|
||||
var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint>
|
||||
{
|
||||
CreateEndpoint("/{p:int}", 0)
|
||||
});
|
||||
|
||||
var sink = new TestSink();
|
||||
var matcher = CreateDfaMatcher(endpointDataSource, loggerFactory: new TestLoggerFactory(sink, enabled: true));
|
||||
|
||||
var (httpContext, context) = CreateContext();
|
||||
httpContext.Request.Path = "/One";
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, context);
|
||||
|
||||
// Assert
|
||||
Assert.Null(context.Endpoint);
|
||||
|
||||
Assert.Collection(
|
||||
sink.Writes,
|
||||
(log) =>
|
||||
{
|
||||
Assert.Equal(DfaMatcher.EventIds.CandidatesFound, log.EventId);
|
||||
Assert.Equal("1 candidate(s) found for the request path '/One'", log.Message);
|
||||
},
|
||||
(log) =>
|
||||
{
|
||||
Assert.Equal(DfaMatcher.EventIds.CandidateRejectedByConstraint, log.EventId);
|
||||
Assert.Equal("Endpoint '/{p:int}' with route pattern '/{p:int}' was rejected by constraint 'p':'Microsoft.AspNetCore.Routing.Constraints.IntRouteConstraint' with value 'One' for the request path '/One'", log.Message);
|
||||
},
|
||||
(log) =>
|
||||
{
|
||||
Assert.Equal(DfaMatcher.EventIds.CandidateNotValid, log.EventId);
|
||||
Assert.Equal("Endpoint '/{p:int}' with route pattern '/{p:int}' is not valid for the request path '/One'", log.Message);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MatchAsync_ComplexSegmentRejectsEndpoint_Logging()
|
||||
{
|
||||
// Arrange
|
||||
var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint>
|
||||
{
|
||||
CreateEndpoint("/x-{id}-y", 0)
|
||||
});
|
||||
|
||||
var sink = new TestSink();
|
||||
var matcher = CreateDfaMatcher(endpointDataSource, loggerFactory: new TestLoggerFactory(sink, enabled: true));
|
||||
|
||||
var (httpContext, context) = CreateContext();
|
||||
httpContext.Request.Path = "/One";
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, context);
|
||||
|
||||
// Assert
|
||||
Assert.Null(context.Endpoint);
|
||||
|
||||
Assert.Collection(
|
||||
sink.Writes,
|
||||
(log) =>
|
||||
{
|
||||
Assert.Equal(DfaMatcher.EventIds.CandidatesFound, log.EventId);
|
||||
Assert.Equal("1 candidate(s) found for the request path '/One'", log.Message);
|
||||
},
|
||||
(log) =>
|
||||
{
|
||||
Assert.Equal(DfaMatcher.EventIds.CandidateRejectedByComplexSegment, log.EventId);
|
||||
Assert.Equal("Endpoint '/x-{id}-y' with route pattern '/x-{id}-y' was rejected by complex segment 'x-{id}-y' for the request path '/One'", log.Message);
|
||||
},
|
||||
(log) =>
|
||||
{
|
||||
Assert.Equal(DfaMatcher.EventIds.CandidateNotValid, log.EventId);
|
||||
Assert.Equal("Endpoint '/x-{id}-y' with route pattern '/x-{id}-y' is not valid for the request path '/One'", log.Message);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MatchAsync_MultipleCandidates_Logging()
|
||||
{
|
||||
// Arrange
|
||||
var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint>
|
||||
{
|
||||
CreateEndpoint("/One", 0),
|
||||
CreateEndpoint("/{p:int}", 0),
|
||||
CreateEndpoint("/x-{id}-y", 0),
|
||||
});
|
||||
|
||||
var sink = new TestSink();
|
||||
var matcher = CreateDfaMatcher(endpointDataSource, loggerFactory: new TestLoggerFactory(sink, enabled: true));
|
||||
|
||||
var (httpContext, context) = CreateContext();
|
||||
httpContext.Request.Path = "/One";
|
||||
|
||||
// Act
|
||||
await matcher.MatchAsync(httpContext, context);
|
||||
|
||||
// Assert
|
||||
Assert.Same(endpointDataSource.Endpoints[0], context.Endpoint);
|
||||
|
||||
Assert.Collection(
|
||||
sink.Writes,
|
||||
(log) =>
|
||||
{
|
||||
Assert.Equal(DfaMatcher.EventIds.CandidatesFound, log.EventId);
|
||||
Assert.Equal("3 candidate(s) found for the request path '/One'", log.Message);
|
||||
},
|
||||
(log) =>
|
||||
{
|
||||
Assert.Equal(DfaMatcher.EventIds.CandidateValid, log.EventId);
|
||||
Assert.Equal("Endpoint '/One' with route pattern '/One' is valid for the request path '/One'", log.Message);
|
||||
},
|
||||
(log) =>
|
||||
{
|
||||
Assert.Equal(DfaMatcher.EventIds.CandidateRejectedByConstraint, log.EventId);
|
||||
Assert.Equal("Endpoint '/{p:int}' with route pattern '/{p:int}' was rejected by constraint 'p':'Microsoft.AspNetCore.Routing.Constraints.IntRouteConstraint' with value 'One' for the request path '/One'", log.Message);
|
||||
},
|
||||
(log) =>
|
||||
{
|
||||
Assert.Equal(DfaMatcher.EventIds.CandidateNotValid, log.EventId);
|
||||
Assert.Equal("Endpoint '/{p:int}' with route pattern '/{p:int}' is not valid for the request path '/One'", log.Message);
|
||||
},
|
||||
(log) =>
|
||||
{
|
||||
Assert.Equal(DfaMatcher.EventIds.CandidateRejectedByComplexSegment, log.EventId);
|
||||
Assert.Equal("Endpoint '/x-{id}-y' with route pattern '/x-{id}-y' was rejected by complex segment 'x-{id}-y' for the request path '/One'", log.Message);
|
||||
},
|
||||
(log) =>
|
||||
{
|
||||
Assert.Equal(DfaMatcher.EventIds.CandidateNotValid, log.EventId);
|
||||
Assert.Equal("Endpoint '/x-{id}-y' with route pattern '/x-{id}-y' is not valid for the request path '/One'", log.Message);
|
||||
});
|
||||
}
|
||||
|
||||
private (HttpContext httpContext, EndpointSelectorContext context) CreateContext()
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Routing
|
|||
public async Task Invoke_LogsCorrectValues_WhenNotHandled()
|
||||
{
|
||||
// Arrange
|
||||
var expectedMessage = "Request did not match any routes.";
|
||||
var expectedMessage = "Request did not match any routes";
|
||||
var isHandled = false;
|
||||
|
||||
var sink = new TestSink(
|
||||
|
|
|
|||
Loading…
Reference in New Issue