public async Task ApplyAsync_HasMatchFindsEndpoint_WithRouteValues()
    {
        // Arrange
        var policy = new DynamicControllerEndpointMatcherPolicy(SelectorCache, Comparer);

        var endpoints = new[] { DynamicEndpoint, };
        var values    = new RouteValueDictionary[] { new RouteValueDictionary(new { slug = "test", }), };
        var scores    = new[] { 0, };

        var candidates = new CandidateSet(endpoints, values, scores);

        Transform = (c, values, state) =>
        {
            return(new ValueTask <RouteValueDictionary>(new RouteValueDictionary(new
            {
                controller = "Home",
                action = "Index",
                state
            })));
        };

        var httpContext = new DefaultHttpContext()
        {
            RequestServices = Services,
        };

        // Act
        await policy.ApplyAsync(httpContext, candidates);

        // Assert
        Assert.Same(ControllerEndpoints[0], candidates[0].Endpoint);
        Assert.Collection(
            candidates[0].Values.OrderBy(kvp => kvp.Key),
            kvp =>
        {
            Assert.Equal("action", kvp.Key);
            Assert.Equal("Index", kvp.Value);
        },
            kvp =>
        {
            Assert.Equal("controller", kvp.Key);
            Assert.Equal("Home", kvp.Value);
        },
            kvp =>
        {
            Assert.Equal("slug", kvp.Key);
            Assert.Equal("test", kvp.Value);
        },
            kvp =>
        {
            Assert.Equal("state", kvp.Key);
            Assert.Same(State, kvp.Value);
        });
        Assert.True(candidates.IsValidCandidate(0));
    }
        public void ApplyAsyncSetEndpointInvalidIfEndpointDoesnotHaveODataSelectedActionDescriptor()
        {
            // Arrange
            HttpContext             context           = new DefaultHttpContext();
            Mock <ActionDescriptor> actionDescriptor1 = new Mock <ActionDescriptor>();

            context.ODataFeature().ActionDescriptor = actionDescriptor1.Object;

            Mock <ActionDescriptor> actionDescriptor2 = new Mock <ActionDescriptor>();
            CandidateSet            candidateSet      = CreateCandidateSet(actionDescriptor2.Object);

            Assert.True(candidateSet.IsValidCandidate(0)); // Guard

            // Act
            Task actual = new ODataEndpointSelectorPolicy().ApplyAsync(context, candidateSet);

            // Assert
            Assert.Equal(Task.CompletedTask, actual);
            Assert.False(candidateSet.IsValidCandidate(0));
        }
 private static bool AllInvalid(CandidateSet candidates)
 {
     for (int i = 0; i < candidates.Count; i++)
     {
         if (candidates.IsValidCandidate(i))
         {
             return(false);
         }
     }
     return(true);
 }
        public async Task ApplyAsync_CanExpandTheListOfFoundEndpoints()
        {
            // Arrange
            var policy = new DynamicControllerEndpointMatcherPolicy(SelectorCache, Comparer);

            var endpoints = new[] { DynamicEndpoint, };
            var values    = new RouteValueDictionary[] { new RouteValueDictionary(new { slug = "test", }), };
            var scores    = new[] { 0, };

            var candidates = new CandidateSet(endpoints, values, scores);

            Transform = (c, values, state) =>
            {
                return(new ValueTask <RouteValueDictionary>(new RouteValueDictionary(new
                {
                    controller = "Home",
                    action = "Index",
                    state
                })));
            };

            Filter = (c, values, state, endpoints) => new ValueTask <IReadOnlyList <Endpoint> >(new[]
            {
                ControllerEndpoints[1], ControllerEndpoints[2]
            });

            var httpContext = new DefaultHttpContext()
            {
                RequestServices = Services,
            };

            // Act
            await policy.ApplyAsync(httpContext, candidates);

            // Assert
            Assert.Equal(2, candidates.Count);
            Assert.True(candidates.IsValidCandidate(0));
            Assert.True(candidates.IsValidCandidate(1));
            Assert.Same(ControllerEndpoints[1], candidates[0].Endpoint);
            Assert.Same(ControllerEndpoints[2], candidates[1].Endpoint);
        }
        public Task ApplyAsync(HttpContext httpContext, 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++)
            {
                if (!candidates.IsValidCandidate(i))
                {
                    continue;
                }

                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.
                    Task <CompiledPageActionDescriptor> compiled;
                    if (_loader is DefaultPageLoader defaultPageLoader)
                    {
                        compiled = defaultPageLoader.LoadAsync(page, endpoint.Metadata);
                    }
                    else
                    {
                        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));
                    }
                }
            }
Example #6
0
        private static bool AllInvalid(CandidateSet candidates)
        {
            for (int i = 0; i < candidates.Count; i++)
            {
                // We have to check if candidates needs to be ignored here
                // So we dont return false when all endpoints are invalid
                if (candidates.IsValidCandidate(i) &&
                    candidates[i].Endpoint.Metadata.GetMetadata <IgnoreFromNotFoundSelectorPolicyAttribute>() is null)
                {
                    return(false);
                }
            }

            return(true);
        }
        public Task ApplyAsync(HttpContext httpContext, CandidateSet candidates)
        {
            for (var i = 0; i < candidates.Count; i++)
            {
                if (!candidates.IsValidCandidate(i))
                {
                    continue;
                }

                var metadata = candidates[i].Endpoint.Metadata.GetMetadata <Metadata>();
                if (metadata is null)
                {
                    continue;
                }

                // this one is OURS!
                //
                // since we own it we can update values in place.
                var values = candidates[i].Values ?? new RouteValueDictionary();

                if (!cache.TryGetValue(metadata, out var result))
                {
                    // use whatever criteria you want to match this to an MVC action. We're using an expression/type.
                    result = FindMatchingEndpoint(metadata, dataSource.Endpoints);

                    cache.TryAdd(metadata, result);
                }

                // emplace the MVC standard route values to be convincing
                var action = result.Metadata.GetMetadata <ControllerActionDescriptor>();
                foreach (var kvp in action.RouteValues)
                {
                    if (kvp.Value is string s && s.Length > 0)
                    {
                        values[kvp.Key] = kvp.Value;
                    }
                }

                if (result is null)
                {
                    throw new InvalidOperationException("Derp.");
                }

                candidates.ReplaceEndpoint(i, result, values);
            }

            return(Task.CompletedTask);
        }
Example #8
0
        // This is almost the same as the code in ActionSelector, but we can't really share the logic
        // because we need to track the index of each candidate - and, each candidate has its own route
        // values.
        private IReadOnlyList <(int index, ActionSelectorCandidate candidate)> EvaluateActionConstraints(
            HttpContext httpContext,
            CandidateSet candidateSet)
        {
            var items = new List <(int index, ActionSelectorCandidate candidate)>();

            // We want to execute a group at a time (based on score) so keep track of the score that we've seen.
            int?score = null;

            // Perf: Avoid allocations
            for (var i = 0; i < candidateSet.Count; i++)
            {
                if (candidateSet.IsValidCandidate(i))
                {
                    ref var candidate = ref candidateSet[i];
                    if (score != null && score != candidate.Score)
                    {
                        // This is the end of a group.
                        var matches = EvaluateActionConstraintsCore(httpContext, candidateSet, items, startingOrder: null);
                        if (matches?.Count > 0)
                        {
                            return(matches);
                        }

                        // If we didn't find matches, then reset.
                        items.Clear();
                    }

                    score = candidate.Score;

                    // If we get here, this is either the first endpoint or the we just (unsuccessfully)
                    // executed constraints for a group.
                    //
                    // So keep adding constraints.
                    var endpoint         = candidate.Endpoint;
                    var actionDescriptor = endpoint.Metadata.GetMetadata <ActionDescriptor>();

                    IReadOnlyList <IActionConstraint> constraints = Array.Empty <IActionConstraint>();
                    if (actionDescriptor != null)
                    {
                        constraints = _actionConstraintCache.GetActionConstraints(httpContext, actionDescriptor);
                    }

                    // Capture the index. We need this later to look up the endpoint/route values.
                    items.Add((i, new ActionSelectorCandidate(actionDescriptor ?? NonAction, constraints)));
                }
            }
    /// <inheritdoc/>
    public Task ApplyAsync(HttpContext httpContext, CandidateSet candidates)
    {
        _ = httpContext ?? throw new ArgumentNullException(nameof(httpContext));
        _ = candidates ?? throw new ArgumentNullException(nameof(candidates));

        var headers = httpContext.Request.Headers;

        for (var i = 0; i < candidates.Count; i++)
        {
            if (!candidates.IsValidCandidate(i))
            {
                continue;
            }

            var matchers = candidates[i].Endpoint.Metadata.GetMetadata <IHeaderMetadata>()?.Matchers;

            if (matchers is null)
            {
                continue;
            }

            foreach (var matcher in matchers)
            {
                if (headers.TryGetValue(matcher.Name, out var requestHeaderValues) &&
                    !StringValues.IsNullOrEmpty(requestHeaderValues))
                {
                    if (matcher.Mode is HeaderMatchMode.Exists)
                    {
                        continue;
                    }

                    if (matcher.Mode is HeaderMatchMode.ExactHeader or HeaderMatchMode.HeaderPrefix
                        ? TryMatchExactOrPrefix(matcher, requestHeaderValues)
                        : TryMatchContainsOrNotContains(matcher, requestHeaderValues))
                    {
                        continue;
                    }
                }

                candidates.SetValidity(i, false);
                break;
            }
        }

        return(Task.CompletedTask);
    }
    /// <inheritdoc/>
    public Task ApplyAsync(HttpContext httpContext, CandidateSet candidates)
    {
        _ = httpContext ?? throw new ArgumentNullException(nameof(httpContext));
        _ = candidates ?? throw new ArgumentNullException(nameof(candidates));

        var query = httpContext.Request.Query;

        for (var i = 0; i < candidates.Count; i++)
        {
            if (!candidates.IsValidCandidate(i))
            {
                continue;
            }

            var matchers = candidates[i].Endpoint.Metadata.GetMetadata <IQueryParameterMetadata>()?.Matchers;

            if (matchers is null)
            {
                continue;
            }

            foreach (var matcher in matchers)
            {
                if (query.TryGetValue(matcher.Name, out var requestQueryParameterValues) &&
                    !StringValues.IsNullOrEmpty(requestQueryParameterValues))
                {
                    if (matcher.Mode is QueryParameterMatchMode.Exists)
                    {
                        continue;
                    }

                    if (TryMatch(matcher, requestQueryParameterValues))
                    {
                        continue;
                    }
                }

                candidates.SetValidity(i, false);
                break;
            }
        }

        return(Task.CompletedTask);
    }
        static IReadOnlyList <(int index, ActionDescriptor action)> EvaluateApiVersion(
            HttpContext httpContext,
            CandidateSet candidates,
            ApiVersion apiVersion)
        {
            Contract.Requires(httpContext != null);
            Contract.Requires(candidates != null);
            Contract.Ensures(Contract.Result <IReadOnlyList <(int index, ActionDescriptor action)> >() != null);

            var bestMatches     = new List <(int index, ActionDescriptor action)>();
            var implicitMatches = new List <(int, ActionDescriptor)>();

            for (var i = 0; i < candidates.Count; i++)
            {
                if (!candidates.IsValidCandidate(i))
                {
                    continue;
                }

                ref var candidate = ref candidates[i];
                var     action    = candidate.Endpoint.Metadata?.GetMetadata <ActionDescriptor>();

                if (action == null)
                {
                    candidates.SetValidity(i, false);
                    continue;
                }

                switch (action.MappingTo(apiVersion))
                {
                case Explicit:
                    bestMatches.Add((i, action));
                    break;

                case Implicit:
                    implicitMatches.Add((i, action));
                    break;
                }

                // perf: always make the candidate invalid so we only need to loop through the
                // final, best matches for any remaining, valid candidates
                candidates.SetValidity(i, false);
            }
Example #12
0
        static (bool Matched, bool HasCandidates) MatchApiVersion(CandidateSet candidates, ApiVersion?apiVersion)
        {
            var bestMatches     = new List <int>();
            var implicitMatches = new List <int>();
            var hasCandidates   = false;

            for (var i = 0; i < candidates.Count; i++)
            {
                if (!candidates.IsValidCandidate(i))
                {
                    continue;
                }

                hasCandidates = true;
                ref var candidate = ref candidates[i];
                var     action    = candidate.Endpoint.Metadata.GetMetadata <ActionDescriptor>();

                if (action == null)
                {
                    continue;
                }

                // remember whether the candidate is currently valid. a matching api version will not
                // make the candidate valid; however, we want to short-circuit with 400 if no candidates
                // match the api version at all.
                switch (action.MappingTo(apiVersion))
                {
                case Explicit:
                    bestMatches.Add(i);
                    break;

                case Implicit:
                    implicitMatches.Add(i);
                    break;
                }

                // perf: always make the candidate invalid so we only need to loop through the
                // final, best matches for any remaining candidates
                candidates.SetValidity(i, false);
            }
        public async Task ApplyAsync_HasMatchFindsEndpoint_WithoutRouteValues()
        {
            // Arrange
            var policy = new DynamicPageEndpointMatcherPolicy(Selector, Loader, Comparer);

            var endpoints = new[] { DynamicEndpoint, };
            var values    = new RouteValueDictionary[] { null, };
            var scores    = new[] { 0, };

            var candidates = new CandidateSet(endpoints, values, scores);

            Transform = (c, values) =>
            {
                return(new ValueTask <RouteValueDictionary>(new RouteValueDictionary(new
                {
                    page = "/Index",
                })));
            };

            var httpContext = new DefaultHttpContext()
            {
                RequestServices = Services,
            };

            // Act
            await policy.ApplyAsync(httpContext, candidates);

            // Assert
            Assert.Same(LoadedEndpoint, candidates[0].Endpoint);
            Assert.Collection(
                candidates[0].Values.OrderBy(kvp => kvp.Key),
                kvp =>
            {
                Assert.Equal("page", kvp.Key);
                Assert.Equal("/Index", kvp.Value);
            });
            Assert.True(candidates.IsValidCandidate(0));
        }
Example #14
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="httpContext"></param>
        /// <param name="candidates"></param>
        /// <returns></returns>
        public Task ApplyAsync(HttpContext httpContext, CandidateSet candidates)
        {
            // The goal of this method is to perform the final matching:
            // Map between route values matched by the template and the ones we want to expose to the action for binding.
            // (tweaking the route values is fine here)
            // Invalidating the candidate if the key/function values are not valid/missing.
            // Perform overload resolution for functions by looking at the candidates and their metadata.
            for (var i = 0; i < candidates.Count; i++)
            {
                ref var candidate = ref candidates[i];
                if (!candidates.IsValidCandidate(i))
                {
                    continue;
                }

                var oDataMetadata = candidate.Endpoint.Metadata.OfType <ODataEndpointMetadata>().FirstOrDefault();
                if (oDataMetadata == null)
                {
                    continue;
                }

                var originalValues = candidate.Values;
                var oPath          = oDataMetadata.GenerateODataPath(originalValues, httpContext.Request.QueryString);
                if (oPath != null)
                {
                    var odata = httpContext.Request.ODataFeature();
                    odata.Model = oDataMetadata.Model;
                    odata.Path  = oPath;

                    //candidates.SetValidity(i, true); // Double confirm whether it's required or not?
                    continue;
                }
                else
                {
                    candidates.SetValidity(i, false);
                    continue;
                }
            }
        public async Task ApplyAsync_CanDiscardFoundEndpoints()
        {
            // Arrange
            var policy = new DynamicControllerEndpointMatcherPolicy(Selector, Comparer);

            var endpoints = new[] { DynamicEndpoint, };
            var values    = new RouteValueDictionary[] { new RouteValueDictionary(new { slug = "test", }), };
            var scores    = new[] { 0, };

            var candidates = new CandidateSet(endpoints, values, scores);

            Transform = (c, values, state) =>
            {
                return(new ValueTask <RouteValueDictionary>(new RouteValueDictionary(new
                {
                    controller = "Home",
                    action = "Index",
                    state
                })));
            };

            Filter = (c, values, state, endpoints) =>
            {
                return(new ValueTask <IReadOnlyList <Endpoint> >(Array.Empty <Endpoint>()));
            };

            var httpContext = new DefaultHttpContext()
            {
                RequestServices = Services,
            };

            // Act
            await policy.ApplyAsync(httpContext, candidates);

            // Assert
            Assert.False(candidates.IsValidCandidate(0));
        }
        internal async Task ApplyAsyncInternal(HttpContext httpContext, CandidateSet candidates)
        {
            var featureManager = httpContext.RequestServices.GetRequiredService <IFeatureManagerSnapshot <TFeature> >();

            for (var i = 0; i < candidates.Count; i++)
            {
                if (candidates.IsValidCandidate(i))
                {
                    var candidate = candidates[i];

                    var enabled = true;

                    foreach (var metadata in candidate.Endpoint.Metadata
                             .GetOrderedMetadata <IFeatureActionConstraintMetadata <TFeature> >()
                             .Where(m => m.Features?.Any() == true))
                    {
                        enabled = enabled && await featureManager.IsEnabledAsync(metadata.RequirementType, metadata.Features).ConfigureAwait(false);

                        if (!enabled)
                        {
                            // If the endpoint has multiple feature action constraints we don't want to evaluate any
                            // more constraints if we have already determined that the endpoint is not enabled.
                            break;
                        }
                    }

                    if (!enabled)
                    {
                        // The endpoint is not be enabled so set the endpoint validity to false.
                        candidates.SetValidity(i, false);
                    }
                }
            }

            return;
        }
Example #17
0
        public Task ApplyAsync(HttpContext httpContext, CandidateSet candidates)
        {
            if (httpContext == null)
            {
                throw new ArgumentNullException(nameof(httpContext));
            }

            IODataFeature odataFeature = httpContext.ODataFeature();

            if (odataFeature.Path != null)
            {
                // If we have the OData path setting, it means there's some Policy working.
                // Let's skip this default OData matcher policy.
                return(Task.CompletedTask);
            }

            for (var i = 0; i < candidates.Count; i++)
            {
                ref CandidateState candidate = ref candidates[i];
                if (!candidates.IsValidCandidate(i))
                {
                    continue;
                }

                IODataRoutingMetadata metadata = candidate.Endpoint.Metadata.OfType <IODataRoutingMetadata>().FirstOrDefault();
                if (metadata == null)
                {
                    continue;
                }

                // Get api-version query from HttpRequest?
                QueryStringApiVersionReader reader = new QueryStringApiVersionReader("api-version");
                string apiVersionStr = reader.Read(httpContext.Request);
                if (apiVersionStr == null)
                {
                    candidates.SetValidity(i, false);
                    continue;
                }
                ApiVersion apiVersion = ApiVersion.Parse(apiVersionStr);

                IEdmModel model = GetEdmModel(apiVersion);
                if (model == null)
                {
                    candidates.SetValidity(i, false);
                    continue;
                }

                if (!IsApiVersionMatch(candidate.Endpoint.Metadata, apiVersion))
                {
                    candidates.SetValidity(i, false);
                    continue;
                }

                ODataTemplateTranslateContext translatorContext
                    = new ODataTemplateTranslateContext(httpContext, candidate.Endpoint, candidate.Values, model);

                try
                {
                    ODataPath odataPath = _translator.Translate(metadata.Template, translatorContext);
                    if (odataPath != null)
                    {
                        odataFeature.RoutePrefix = metadata.Prefix;
                        odataFeature.Model       = model;
                        odataFeature.Path        = odataPath;

                        ODataOptions options = new ODataOptions();
                        UpdateQuerySetting(options);
                        options.AddRouteComponents(model);
                        odataFeature.Services = options.GetRouteServices(string.Empty);

                        MergeRouteValues(translatorContext.UpdatedValues, candidate.Values);
                    }
                    else
                    {
                        candidates.SetValidity(i, false);
                    }
                }
                catch
                {
                    candidates.SetValidity(i, false);
                }
            }
        /// <summary>
        /// Applies the policy to the CandidateSet.
        /// </summary>
        /// <param name="httpContext">The context associated with the current request.</param>
        /// <param name="candidates">The CandidateSet.</param>
        /// <returns>The task.</returns>
        public Task ApplyAsync(HttpContext httpContext, CandidateSet candidates)
        {
            if (httpContext == null)
            {
                throw Error.ArgumentNull(nameof(httpContext));
            }

            IODataFeature odataFeature = httpContext.ODataFeature();

            if (odataFeature.Path != null)
            {
                // If we have the OData path setting, it means there's some Policy working.
                // Let's skip this default OData matcher policy.
                return(Task.CompletedTask);
            }

            // The goal of this method is to perform the final matching:
            // Map between route values matched by the template and the ones we want to expose to the action for binding.
            // (tweaking the route values is fine here)
            // Invalidating the candidate if the key/function values are not valid/missing.
            // Perform overload resolution for functions by looking at the candidates and their metadata.
            for (var i = 0; i < candidates.Count; i++)
            {
                ref CandidateState candidate = ref candidates[i];
                if (!candidates.IsValidCandidate(i))
                {
                    continue;
                }

                IODataRoutingMetadata metadata = candidate.Endpoint.Metadata.OfType <IODataRoutingMetadata>().FirstOrDefault();
                if (metadata == null)
                {
                    continue;
                }

                if (odataFeature.Path != null)
                {
                    // If it's odata endpoint, and we have a path set, let other odata endpoints invalid.
                    candidates.SetValidity(i, false);
                    continue;
                }

                ODataTemplateTranslateContext translatorContext =
                    new ODataTemplateTranslateContext(httpContext, candidate.Endpoint, candidate.Values, metadata.Model);

                ODataPath odataPath = _translator.Translate(metadata.Template, translatorContext);
                if (odataPath != null)
                {
                    odataFeature.RoutePrefix = metadata.Prefix;
                    odataFeature.Model       = metadata.Model;
                    odataFeature.Path        = odataPath;

                    MergeRouteValues(translatorContext.UpdatedValues, candidate.Values);

                    // Shall we break the remaining candidates?
                    // So far the answer is no. Because we can use this matcher to obsolete the unmatched endpoint.
                    // break;
                }
                else
                {
                    candidates.SetValidity(i, false);
                }
            }
Example #19
0
        public async Task ApplyAsync(HttpContext httpContext, CandidateSet candidates)
        {
            if (httpContext == null)
            {
                throw new ArgumentNullException(nameof(httpContext));
            }

            if (candidates == null)
            {
                throw new ArgumentNullException(nameof(candidates));
            }

            // The per-route selector, must be the same for all the endpoints we are dealing with.
            DynamicControllerEndpointSelector?selector = null;

            // 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 originalValues = candidates[i].Values !;

                RouteValueDictionary?dynamicValues = null;

                // We don't expect both of these to be provided, and they are internal so there's
                // no realistic way this could happen.
                var dynamicControllerMetadata = endpoint.Metadata.GetMetadata <DynamicControllerMetadata>();
                var transformerMetadata       = endpoint.Metadata.GetMetadata <DynamicControllerRouteValueTransformerMetadata>();

                DynamicRouteValueTransformer?transformer = null;
                if (dynamicControllerMetadata != null)
                {
                    dynamicValues = dynamicControllerMetadata.Values;
                }
                else if (transformerMetadata != null)
                {
                    transformer = (DynamicRouteValueTransformer)httpContext.RequestServices.GetRequiredService(transformerMetadata.SelectorType);
                    if (transformer.State != null)
                    {
                        throw new InvalidOperationException(Resources.FormatStateShouldBeNullForRouteValueTransformers(transformerMetadata.SelectorType.Name));
                    }
                    transformer.State = transformerMetadata.State;

                    dynamicValues = await transformer.TransformAsync(httpContext, originalValues);
                }
                else
                {
                    // Not a dynamic controller.
                    continue;
                }

                if (dynamicValues == null)
                {
                    candidates.ReplaceEndpoint(i, null, null);
                    continue;
                }

                selector = ResolveSelector(selector, endpoint);

                var endpoints = selector.SelectEndpoints(dynamicValues);
                if (endpoints.Count == 0 && dynamicControllerMetadata != null)
                {
                    // Naving no match for a fallback is a configuration error. We can't really check
                    // during startup that the action you configured exists, so this is the best we can do.
                    throw new InvalidOperationException(
                              "Cannot find the fallback endpoint specified by route values: " +
                              "{ " + string.Join(", ", dynamicValues.Select(kvp => $"{kvp.Key}: {kvp.Value}")) + " }.");
                }
                else if (endpoints.Count == 0)
                {
                    candidates.ReplaceEndpoint(i, null, null);
                    continue;
                }

                // We need to provide the route values associated with this endpoint, so that features
                // like URL generation work.
                var values = new RouteValueDictionary(dynamicValues);

                // Include values that were matched by the fallback route.
                if (originalValues != null)
                {
                    foreach (var kvp in originalValues)
                    {
                        values.TryAdd(kvp.Key, kvp.Value);
                    }
                }

                if (transformer != null)
                {
                    endpoints = await transformer.FilterAsync(httpContext, values, endpoints);

                    if (endpoints.Count == 0)
                    {
                        candidates.ReplaceEndpoint(i, null, null);
                        continue;
                    }
                }

                // Update the route values
                candidates.ReplaceEndpoint(i, endpoint, values);

                // Expand the list of endpoints
                candidates.ExpandEndpoint(i, endpoints, _comparer);
            }
        }
Example #20
0
    /// <inheritdoc />
    public Task ApplyAsync(HttpContext httpContext, 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++)
        {
            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 = ReadOnlySpan <char> .Empty;

                // 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 off 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);
    }
Example #21
0
        public async 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));
            }

            // 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}")) + " }.");
                }

                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);
            }
        }
Example #22
0
    /// <summary>
    /// For framework use only.
    /// </summary>
    /// <param name="httpContext"></param>
    /// <param name="candidates"></param>
    /// <returns></returns>
    public Task ApplyAsync(HttpContext httpContext, CandidateSet candidates)
    {
        if (httpContext == null)
        {
            throw new ArgumentNullException(nameof(httpContext));
        }

        if (candidates == null)
        {
            throw new ArgumentNullException(nameof(candidates));
        }

        // Returning a 405 here requires us to return keep track of all 'seen' HTTP methods. We allocate to
        // keep track of this because we either need to keep track of the HTTP methods or keep track of the
        // endpoints - both allocate.
        //
        // Those code only runs in the presence of dynamic endpoints anyway.
        //
        // We want to return a 405 iff we eliminated ALL of the currently valid endpoints due to HTTP method
        // mismatch.
        bool?            needs405Endpoint = null;
        HashSet <string>?methods          = null;

        for (var i = 0; i < candidates.Count; i++)
        {
            // We do this check first for consistency with how 405 is implemented for the graph version
            // of this code. We still want to know if any endpoints in this set require an HTTP method
            // even if those endpoints are already invalid - hence the null-check.
            var metadata = candidates[i].Endpoint?.Metadata.GetMetadata <IHttpMethodMetadata>();
            if (metadata == null || metadata.HttpMethods.Count == 0)
            {
                // Can match any method.
                needs405Endpoint = false;
                continue;
            }

            // Saw a valid endpoint.
            needs405Endpoint = needs405Endpoint ?? true;

            if (!candidates.IsValidCandidate(i))
            {
                continue;
            }

            var httpMethod = httpContext.Request.Method;
            var headers    = httpContext.Request.Headers;
            if (metadata.AcceptCorsPreflight &&
                HttpMethods.Equals(httpMethod, PreflightHttpMethod) &&
                headers.ContainsKey(HeaderNames.Origin) &&
                headers.TryGetValue(HeaderNames.AccessControlRequestMethod, out var accessControlRequestMethod) &&
                !StringValues.IsNullOrEmpty(accessControlRequestMethod))
            {
                needs405Endpoint = false; // We don't return a 405 for a CORS preflight request when the endpoints accept CORS preflight.
                httpMethod       = accessControlRequestMethod.ToString();
            }

            var matched = false;
            for (var j = 0; j < metadata.HttpMethods.Count; j++)
            {
                var candidateMethod = metadata.HttpMethods[j];
                if (!HttpMethods.Equals(httpMethod, candidateMethod))
                {
                    methods = methods ?? new HashSet <string>(StringComparer.OrdinalIgnoreCase);
                    methods.Add(candidateMethod);
                    continue;
                }

                matched          = true;
                needs405Endpoint = false;
                break;
            }

            if (!matched)
            {
                candidates.SetValidity(i, false);
            }
        }

        if (needs405Endpoint == true)
        {
            // We saw some endpoints coming in, and we eliminated them all.
            httpContext.SetEndpoint(CreateRejectionEndpoint(methods !.OrderBy(m => m, StringComparer.OrdinalIgnoreCase)));
            httpContext.Request.RouteValues = null !;
        }

        return(Task.CompletedTask);
    }
Example #23
0
        public Task ApplyAsync(HttpContext httpContext, CandidateSet candidates)
        {
            if (httpContext == null)
            {
                throw new ArgumentNullException(nameof(httpContext));
            }

            // The goal of this method is to perform the final matching:
            // Map between route values matched by the template and the ones we want to expose to the action for binding.
            // (tweaking the route values is fine here)
            // Invalidating the candidate if the key/function values are not valid/missing.
            // Perform overload resolution for functions by looking at the candidates and their metadata.
            for (var i = 0; i < candidates.Count; i++)
            {
                ref CandidateState candidate = ref candidates[i];
                if (!candidates.IsValidCandidate(i))
                {
                    continue;
                }

                IODataRoutingMetadata metadata = candidate.Endpoint.Metadata.OfType <IODataRoutingMetadata>().FirstOrDefault();
                if (metadata == null)
                {
                    continue;
                }

                IHttpMethodMetadata httpMetadata = candidate.Endpoint.Metadata.GetMetadata <IHttpMethodMetadata>();
                if (httpMetadata == null)
                {
                    // Check the http method
                    if (metadata.HttpMethods != null && !metadata.HttpMethods.Contains(httpContext.Request.Method))
                    {
                        candidates.SetValidity(i, false);
                        continue;
                    }
                }

                ODataTemplateTranslateContext translatorContext =
                    new ODataTemplateTranslateContext(httpContext, candidate.Values, metadata.Model);

                try
                {
                    ODataPath odataPath = _translator.Translate(metadata.Template, translatorContext);
                    if (odataPath != null)
                    {
                        IODataFeature odataFeature = httpContext.ODataFeature();
                        odataFeature.PrefixName = metadata.Prefix;
                        odataFeature.Model      = metadata.Model;
                        odataFeature.Path       = odataPath;

                        MergeRouteValues(translatorContext.UpdatedValues, candidate.Values);
                    }
                    else
                    {
                        candidates.SetValidity(i, false);
                    }
                }
#if DEBUG
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
#else
                catch (Exception)
                {
#endif
                    candidates.SetValidity(i, false);
                }
            }
Example #24
0
        /// <inheritdoc/>
        public Task ApplyAsync(HttpContext httpContext, CandidateSet candidates)
        {
            _ = httpContext ?? throw new ArgumentNullException(nameof(httpContext));
            _ = candidates ?? throw new ArgumentNullException(nameof(candidates));

            for (var i = 0; i < candidates.Count; i++)
            {
                if (!candidates.IsValidCandidate(i))
                {
                    continue;
                }

                var matchers = candidates[i].Endpoint.Metadata.GetMetadata <IHeaderMetadata>()?.Matchers;

                if (matchers == null)
                {
                    continue;
                }

                for (var m = 0; m < matchers.Count; m++)
                {
                    var matcher              = matchers[m];
                    var expectedHeaderName   = matcher.Name;
                    var expectedHeaderValues = matcher.Values;

                    var matched = false;
                    if (httpContext.Request.Headers.TryGetValue(expectedHeaderName, out var requestHeaderValues))
                    {
                        if (StringValues.IsNullOrEmpty(requestHeaderValues))
                        {
                            // A non-empty value is required for a match.
                        }
                        else if (matcher.Mode == HeaderMatchMode.Exists)
                        {
                            // We were asked to match as long as the header exists, and it *does* exist
                            matched = true;
                        }
                        // Multi-value headers are not supported.
                        // Note a single entry may also contain multiple values, we don't distinguish, we only match on the whole header.
                        else if (requestHeaderValues.Count == 1)
                        {
                            var requestHeaderValue = requestHeaderValues.ToString();
                            for (var j = 0; j < expectedHeaderValues.Count; j++)
                            {
                                if (MatchHeader(matcher.Mode, requestHeaderValue, expectedHeaderValues[j], matcher.IsCaseSensitive))
                                {
                                    matched = true;
                                    break;
                                }
                            }
                        }
                    }

                    // All rules must match
                    if (!matched)
                    {
                        candidates.SetValidity(i, false);
                        break;
                    }
                }
            }

            return(Task.CompletedTask);
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="httpContext"></param>
        /// <param name="candidates"></param>
        /// <returns></returns>
        public Task ApplyAsync(HttpContext httpContext, CandidateSet candidates)
        {
            // The goal of this method is to perform the final matching:
            // Map between route values matched by the template and the ones we want to expose to the action for binding.
            // (tweaking the route values is fine here)
            // Invalidating the candidate if the key/function values are not valid/missing.
            // Perform overload resolution for functions by looking at the candidates and their metadata.
            for (var i = 0; i < candidates.Count; i++)
            {
                ref var candidate = ref candidates[i];
                if (!candidates.IsValidCandidate(i))
                {
                    continue;
                }

                var oDataMetadata = candidate.Endpoint.Metadata.OfType <ODataEndpointMetadata>().FirstOrDefault();
                if (oDataMetadata == null)
                {
                    continue;
                }

                var original = candidate.Endpoint.RequestDelegate;
                var name     = candidate.Endpoint.DisplayName;

                var newEndpoint    = new Endpoint(EndpointWithODataPath, candidate.Endpoint.Metadata, name);
                var originalValues = candidate.Values;
                var newValues      = new RouteValueDictionary();
                foreach (var(key, value) in originalValues)
                {
                    //if (key.EndsWith(".Name"))
                    //{
                    //    var keyValue = originalValues[key.Replace(".Name", ".Value")];
                    //    var partName = originalValues[key];
                    //    var parameterName = oDataMetadata.ParameterMappings[oDataMetadata.ParameterMappings.Keys.Single(key => key.Name == (string)partName)];
                    //    newValues.Add(parameterName, keyValue);
                    //}

                    newValues.Add(key, value);
                }

                var oPath = oDataMetadata.GenerateODataPath(originalValues, httpContext.Request.QueryString);
                if (oPath != null)
                {
                    var odata = httpContext.Request.ODataFeature();
                    odata.Model             = oDataMetadata.Model;
                    odata.IsEndpointRouting = true;
                    odata.RequestContainer  = httpContext.RequestServices; // sp;
                    odata.Path = oPath;

                    //candidates.SetValidity(i, true); // Double confirm whether it's required or not?
                    continue;
                }
                else
                {
                    candidates.SetValidity(i, false);
                    continue;
                }

                //candidates.ReplaceEndpoint(i, newEndpoint, newValues);

                Task EndpointWithODataPath(HttpContext httpContext)
                {
                    var odataPath = oDataMetadata.ODataPathFactory(httpContext.GetRouteData().Values, oDataMetadata.ParameterMappings);
                    var odata     = httpContext.Request.ODataFeature();

                    odata.IsEndpointRouting = true;
                    odata.RequestContainer  = httpContext.RequestServices;
                    odata.Path      = odataPath;
                    odata.RouteName = name;
                    var prc = httpContext.RequestServices.GetRequiredService <IPerRouteContainer>();

                    if (!prc.HasODataRootContainer(name))
                    {
                        prc.AddRoute(odata.RouteName, "");
                    }

                    return(original(httpContext));
                }
            }