public async Task apply_should_have_candidate_for_matched_api_version() { // arrange var feature = new Mock <IApiVersioningFeature>(); var context = new EndpointSelectorContext(); var items = new object[] { new ActionDescriptor() { Properties = { [typeof(ApiVersionModel)] = new ApiVersionModel(new ApiVersion(1, 0)) }, }, }; var endpoint = new Endpoint(c => CompletedTask, new EndpointMetadataCollection(items), default); var candidates = new CandidateSet(new[] { endpoint }, new[] { new RouteValueDictionary() }, new[] { 0 }); var policy = new ApiVersionMatcherPolicy(NewDefaultOptions(), NewReporter(), NewLoggerFactory()); feature.SetupProperty(f => f.RequestedApiVersion, new ApiVersion(1, 0)); var httpContext = NewHttpContext(feature); // act await policy.ApplyAsync(httpContext, context, candidates); // assert candidates.IsValidCandidate(0).Should().BeTrue(); }
internal static void Select( HttpContext httpContext, EndpointSelectorContext context, CandidateState[] candidateState) { // Fast path: We can specialize for trivial numbers of candidates since there can // be no ambiguities switch (candidateState.Length) { case 0: { // Do nothing break; } case 1: { ref var state = ref candidateState[0]; if (CandidateSet.IsValidCandidate(ref state)) { context.Endpoint = state.Endpoint; context.RouteValues = state.Values; } break; }
public override Task MatchAsync(ProtoContext httpContext, EndpointSelectorContext context) { if (httpContext == null) { throw new ArgumentNullException(nameof(httpContext)); } if (context == null) { throw new ArgumentNullException(nameof(context)); } var path = httpContext.Request.Path.Value; for (var i = 0; i < Matchers.Length; i++) { if (Matchers[i].TryMatch(path)) { context.Endpoint = Matchers[i].Endpoint; context.RouteValues = new RouteValueDictionary(); } } return(Task.CompletedTask); }
public async Task Endpoints_InvokeReturnedEndpoint_ActionInvokerProviderCalled() { // Arrange var endpointFeature = new EndpointSelectorContext { RouteValues = new RouteValueDictionary() }; var featureCollection = new FeatureCollection(); featureCollection.Set <IEndpointFeature>(endpointFeature); featureCollection.Set <IRouteValuesFeature>(endpointFeature); featureCollection.Set <IRoutingFeature>(endpointFeature); var httpContextMock = new Mock <HttpContext>(); httpContextMock.Setup(m => m.Features).Returns(featureCollection); var descriptorProviderMock = new Mock <IActionDescriptorCollectionProvider>(); descriptorProviderMock.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List <ActionDescriptor> { new ActionDescriptor { AttributeRouteInfo = new AttributeRouteInfo { Template = string.Empty }, FilterDescriptors = new List <FilterDescriptor>() } }, 0)); var actionInvokerCalled = false; var actionInvokerMock = new Mock <IActionInvoker>(); actionInvokerMock.Setup(m => m.InvokeAsync()).Returns(() => { actionInvokerCalled = true; return(Task.CompletedTask); }); var actionInvokerProviderMock = new Mock <IActionInvokerFactory>(); actionInvokerProviderMock.Setup(m => m.CreateInvoker(It.IsAny <ActionContext>())).Returns(actionInvokerMock.Object); var dataSource = CreateMvcEndpointDataSource( descriptorProviderMock.Object, new MvcEndpointInvokerFactory(actionInvokerProviderMock.Object)); // Act var endpoints = dataSource.Endpoints; // Assert var endpoint = Assert.Single(endpoints); var matcherEndpoint = Assert.IsType <RouteEndpoint>(endpoint); await matcherEndpoint.RequestDelegate(httpContextMock.Object); Assert.True(actionInvokerCalled); }
/// <inheritdoc /> public Task ApplyAsync(HttpContext httpContext, EndpointSelectorContext context, CandidateSet candidates) { Arg.NotNull(httpContext, nameof(httpContext)); Arg.NotNull(context, nameof(context)); Arg.NotNull(candidates, nameof(candidates)); if (IsRequestedApiVersionAmbiguous(httpContext, context, out var apiVersion)) { return(CompletedTask); } if (apiVersion == null && Options.AssumeDefaultVersionWhenUnspecified) { apiVersion = TrySelectApiVersion(httpContext, candidates); httpContext.Features.Get <IApiVersioningFeature>().RequestedApiVersion = apiVersion; } var finalMatches = EvaluateApiVersion(httpContext, candidates, apiVersion); if (finalMatches.Count == 0) { context.Endpoint = ClientError(httpContext, candidates); } else { for (var i = 0; i < finalMatches.Count; i++) { candidates.SetValidity(finalMatches[i].index, true); } } return(CompletedTask); }
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)); }
public async Task apply_should_use_400_endpoint_for_ambiguous_api_version() { // arrange var feature = new Mock <IApiVersioningFeature>(); var errorResponses = new Mock <IErrorResponseProvider>(); var result = new Mock <IActionResult>(); feature.SetupGet(f => f.RequestedApiVersion).Throws(new AmbiguousApiVersionException("Test", new[] { "1.0", "2.0" })); result.Setup(r => r.ExecuteResultAsync(It.IsAny <ActionContext>())).Returns(CompletedTask); errorResponses.Setup(er => er.CreateResponse(It.IsAny <ErrorResponseContext>())).Returns(result.Object); var options = Options.Create(new ApiVersioningOptions() { ErrorResponses = errorResponses.Object }); var policy = new ApiVersionMatcherPolicy(options, NewReporter(), NewLoggerFactory()); var httpContext = NewHttpContext(feature); var context = new EndpointSelectorContext(); var candidates = new CandidateSet(Empty <Endpoint>(), Empty <RouteValueDictionary>(), Empty <int>()); // act await policy.ApplyAsync(httpContext, context, candidates); await context.Endpoint.RequestDelegate(httpContext); // assert result.Verify(r => r.ExecuteResultAsync(It.IsAny <ActionContext>()), Once()); errorResponses.Verify(er => er.CreateResponse(It.Is <ErrorResponseContext>(c => c.StatusCode == 400 && c.ErrorCode == "AmbiguousApiVersion")), Once()); }
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); }
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)); }
public Task ApplyAsync(HttpContext httpContext, EndpointSelectorContext context, CandidateSet candidates) { if (httpContext == null) { throw new ArgumentNullException(nameof(httpContext)); } if (candidates == null) { throw new ArgumentNullException(nameof(candidates)); } for (var i = 0; i < candidates.Count; i++) { ref var candidate = ref candidates[i]; var endpoint = candidate.Endpoint; var page = endpoint.Metadata.GetMetadata <PageActionDescriptor>(); if (page != null) { // We found an endpoint instance that has a PageActionDescriptor, but not a // CompiledPageActionDescriptor. Update the CandidateSet. var compiled = _loader.LoadAsync(page); if (compiled.IsCompletedSuccessfully) { candidates.ReplaceEndpoint(i, compiled.Result.Endpoint, candidate.Values); } else { // In the most common case, GetOrAddAsync will return a synchronous result. // Avoid going async since this is a fairly hot path. return(ApplyAsyncAwaited(candidates, compiled, i)); } } }
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++) { ref var candidate = ref candidates[i]; var endpoint = (RouteEndpoint)candidate.Endpoint; var page = endpoint.Metadata.GetMetadata <PageActionDescriptor>(); if (page != null) { var compiled = _loader.Load(page); candidates.ReplaceEndpoint(i, compiled.Endpoint, candidate.Values); } }
public async Task ApplyAsync(HttpContext httpContext, EndpointSelectorContext context, CandidateSet candidates) { if (httpContext == null) { throw new ArgumentNullException(nameof(httpContext)); } if (candidates == null) { throw new ArgumentNullException(nameof(candidates)); } // There's no real benefit here from trying to avoid the async state machine. // We only execute on nodes that contain a dynamic policy, and thus always have // to await something. for (var i = 0; i < candidates.Count; i++) { if (!candidates.IsValidCandidate(i)) { continue; } var endpoint = candidates[i].Endpoint; var metadata = endpoint.Metadata.GetMetadata <DynamicPageMetadata>(); if (metadata == null) { continue; } var matchedValues = candidates[i].Values; var endpoints = _selector.SelectEndpoints(metadata.Values); if (endpoints.Count == 0) { // If there's no match this is a configuration error. We can't really check // during startup that the action you configured exists. throw new InvalidOperationException( "Cannot find the fallback endpoint specified by route values: " + "{ " + string.Join(", ", metadata.Values.Select(kvp => $"{kvp.Key}: {kvp.Value}")) + " }."); } // It is possible to have more than one result for pages but they are equivalent. var compiled = await _loader.LoadAsync(endpoints[0].Metadata.GetMetadata <PageActionDescriptor>()); var replacement = compiled.Endpoint; // We need to provide the route values associated with this endpoint, so that features // like URL generation work. var values = new RouteValueDictionary(metadata.Values); // Include values that were matched by the fallback route. foreach (var kvp in matchedValues) { values.TryAdd(kvp.Key, kvp.Value); } candidates.ReplaceEndpoint(i, replacement, values); } }
public async Task LegacyRouter() { var httpContext = Requests[0]; var feature = new EndpointSelectorContext(httpContext); await _route.MatchAsync(httpContext, feature); Validate(httpContext, Endpoints[0], feature.Endpoint); }
public async Task Baseline() { var httpContext = Requests[0]; var feature = new EndpointSelectorContext(httpContext); await _baseline.MatchAsync(httpContext, feature); Validate(httpContext, Endpoints[0], feature.Endpoint); }
public Task ApplyAsync(HttpContext httpContext, EndpointSelectorContext context, CandidateSet candidates) { for (var i = 0; i < candidates.Count; i++) { ref var candidate = ref candidates[i]; var newEndpoint = _negotiateEndpointCache.GetOrAdd(candidate.Endpoint, CreateNegotiateEndpoint); candidates.ReplaceEndpoint(i, newEndpoint, candidate.Values); }
public static void AssertNotMatch(EndpointSelectorContext context, HttpContext httpContext) { if (context.Endpoint != null) { throw new XunitException( $"Was expected not to match '{context.Endpoint.DisplayName}' " + $"but matched with values: {FormatRouteValues(httpContext.Features.Get<IRouteValuesFeature>().RouteValues)}."); } }
public override Task MatchAsync(HttpContext httpContext, EndpointSelectorContext context) { if (_isHandled) { context.RouteValues = new RouteValueDictionary(new { controller = "Home", action = "Index" }); context.Endpoint = new Endpoint(TestConstants.EmptyRequestDelegate, EndpointMetadataCollection.Empty, "Test endpoint"); } return(Task.CompletedTask); }
private static (HttpContext httpContext, EndpointSelectorContext context) CreateContext() { var context = new EndpointSelectorContext(); var httpContext = new DefaultHttpContext(); httpContext.Features.Set <IEndpointFeature>(context); httpContext.Features.Set <IRouteValuesFeature>(context); return(httpContext, context); }
public override Task MatchAsync(HttpContext httpContext, EndpointSelectorContext context) { if (TryMatch(httpContext.Request.Path.Value)) { context.Endpoint = Endpoint; context.RouteValues = new RouteValueDictionary(); } return(Task.CompletedTask); }
public void Setup() { SetupEndpoints(); SetupRequests(); _baseline = (BarebonesMatcher)SetupMatcher(new BarebonesMatcherBuilder()); _dfa = SetupMatcher(CreateDfaMatcherBuilder()); _feature = new EndpointSelectorContext(); }
public static void AssertMatch(EndpointSelectorContext context, HttpContext httpContext, Endpoint expected, string[] keys, string[] values) { keys = keys ?? Array.Empty <string>(); values = values ?? Array.Empty <string>(); if (keys.Length != values.Length) { throw new XunitException($"Keys and Values must be the same length."); } var zipped = keys.Zip(values, (k, v) => new KeyValuePair <string, object>(k, v)); AssertMatch(context, httpContext, expected, new RouteValueDictionary(zipped)); }
public async Task RouteAsync(RouteContext routeContext) { var context = new EndpointSelectorContext(routeContext.HttpContext); // This is needed due to a quirk of our tests - they reuse the endpoint feature. context.Endpoint = null; await _selector.SelectAsync(routeContext.HttpContext, context, new CandidateSet(_candidates, _values, _scores)); if (context.Endpoint != null) { routeContext.Handler = (_) => Task.CompletedTask; } }
public Task ApplyAsync(HttpContext httpContext, EndpointSelectorContext context, CandidateSet candidateSet) { // PERF: we can skip over action constraints if there aren't any app-wide. // // Running action constraints (or just checking for them) in a candidate set // is somewhat expensive compared to other routing operations. This should only // happen if user-code adds action constraints. if (ShouldRunActionConstraints) { ApplyActionConstraints(httpContext, candidateSet); } return(Task.CompletedTask); }
public void Setup() { SetupEndpoints(); SetupRequests(); // The perf is kinda slow for these benchmarks, so we do some sampling // of the request data. _samples = SampleRequests(EndpointCount, SampleCount); _baseline = (BarebonesMatcher)SetupMatcher(new BarebonesMatcherBuilder()); _dfa = SetupMatcher(CreateDfaMatcherBuilder()); _feature = new EndpointSelectorContext(); }
internal static (HttpContext httpContext, EndpointSelectorContext context) CreateContext(string path) { var httpContext = new DefaultHttpContext(); httpContext.Request.Method = "TEST"; httpContext.Request.Path = path; httpContext.RequestServices = CreateServices(); var context = new EndpointSelectorContext(); httpContext.Features.Set <IEndpointFeature>(context); httpContext.Features.Set <IRouteValuesFeature>(context); return(httpContext, context); }
public async Task AddEndpoints_AttributeRouted_UsesActionInvoker() { // Arrange var values = new { action = "Test", controller = "Test", page = (string)null, }; var action = CreateActionDescriptor(values, pattern: "/Test"); var endpointFeature = new EndpointSelectorContext { RouteValues = new RouteValueDictionary() }; var featureCollection = new FeatureCollection(); featureCollection.Set <IEndpointFeature>(endpointFeature); featureCollection.Set <IRouteValuesFeature>(endpointFeature); featureCollection.Set <IRoutingFeature>(endpointFeature); var httpContextMock = new Mock <HttpContext>(); httpContextMock.Setup(m => m.Features).Returns(featureCollection); var actionInvokerCalled = false; var actionInvokerMock = new Mock <IActionInvoker>(); actionInvokerMock.Setup(m => m.InvokeAsync()).Returns(() => { actionInvokerCalled = true; return(Task.CompletedTask); }); InvokerFactory .Setup(m => m.CreateInvoker(It.IsAny <ActionContext>())) .Returns(actionInvokerMock.Object); // Act var endpoint = CreateAttributeRoutedEndpoint(action); // Assert await endpoint.RequestDelegate(httpContextMock.Object); Assert.True(actionInvokerCalled); }
public async override Task MatchAsync(HttpContext httpContext, EndpointSelectorContext context) { if (httpContext == null) { throw new ArgumentNullException(nameof(httpContext)); } var routeContext = new RouteContext(httpContext); await _inner.RouteAsync(routeContext); if (routeContext.Handler != null) { context.RouteValues = routeContext.RouteData.Values; await routeContext.Handler(httpContext); } }
public sealed override Task MatchAsync(HttpContext httpContext, EndpointSelectorContext context) { if (httpContext == null) { throw new ArgumentNullException(nameof(httpContext)); } var path = httpContext.Request.Path.Value; if (string.Equals(_endpoint.RoutePattern.RawText, path, StringComparison.OrdinalIgnoreCase)) { context.Endpoint = _endpoint; context.RouteValues = new RouteValueDictionary(); } return(Task.CompletedTask); }
public void Setup() { Endpoints = new RouteEndpoint[1]; Endpoints[0] = CreateEndpoint("/plaintext"); Requests = new ProtoContext[1]; Requests[0] = new DefaultProtoContext(); Requests[0].RequestServices = CreateServices(); Requests[0].Request.Path = "/plaintext"; _baseline = (BarebonesMatcher)SetupMatcher(new BarebonesMatcherBuilder()); _dfa = SetupMatcher(CreateDfaMatcherBuilder()); _route = SetupMatcher(new RouteMatcherBuilder()); _tree = SetupMatcher(new TreeRouterMatcherBuilder()); _feature = new EndpointSelectorContext(); }
public sealed override Task MatchAsync(HttpContext httpContext, EndpointSelectorContext context) { if (httpContext == null) { throw new ArgumentNullException(nameof(httpContext)); } // 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. var path = httpContext.Request.Path.Value; // First tokenize the path into series of segments. Span <PathSegment> buffer = stackalloc PathSegment[_maxSegmentCount]; var count = FastPathTokenizer.Tokenize(path, buffer); var segments = buffer.Slice(0, count); // FindCandidateSet will process the DFA and return a candidate set. This does // some preliminary matching of the URL (mostly the literal segments). var(candidates, policies) = FindCandidateSet(httpContext, path, segments); var candidateCount = candidates.Length; if (candidateCount == 0) { if (log) { Logger.CandidatesNotFound(_logger, path); } return(Task.CompletedTask); } if (log) { Logger.CandidatesFound(_logger, path, candidates); } var policyCount = policies.Length; // This is a fast path for single candidate, 0 policies and default selector if (candidateCount == 1 && policyCount == 0 && _isDefaultEndpointSelector) { ref readonly var candidate = ref candidates[0];