private static bool ValidateControllerConstraint(ActionDescriptor action, MvcEndpointInfo endpointInfo)
        {
            if (action is ControllerActionDescriptor controllerActionDescriptor)
            {
                return(endpointInfo.ControllerType.IsAssignableFrom(controllerActionDescriptor.ControllerTypeInfo));
            }

            return(false);
        }
        private bool UseDefaultValuePlusRemainingSegmentsOptional(
            int segmentIndex,
            ActionDescriptor action,
            MvcEndpointInfo endpointInfo,
            List <RoutePatternPathSegment> pathSegments)
        {
            // Check whether the remaining segments are all optional and one or more of them is
            // for area/controller/action and has a default value
            var usedDefaultValue = false;

            for (var i = segmentIndex; i < pathSegments.Count; i++)
            {
                var segment = pathSegments[i];
                for (var j = 0; j < segment.Parts.Count; j++)
                {
                    var part = segment.Parts[j];
                    if (part.IsParameter && part is RoutePatternParameterPart parameterPart)
                    {
                        if (parameterPart.IsOptional || parameterPart.IsCatchAll)
                        {
                            continue;
                        }

                        if (action.RouteValues.ContainsKey(parameterPart.Name))
                        {
                            if (endpointInfo.MergedDefaults[parameterPart.Name] is string defaultValue &&
                                action.RouteValues.TryGetValue(parameterPart.Name, out var routeValue) &&
                                string.Equals(defaultValue, routeValue, StringComparison.OrdinalIgnoreCase))
                            {
                                usedDefaultValue = true;
                                continue;
                            }
                        }
                    }
                    else if (part.IsSeparator && part is RoutePatternSeparatorPart separatorPart &&
                             separatorPart.Content == ".")
                    {
                        // Check if this pattern ends in an optional extension, e.g. ".{ext?}"
                        // Current literal must be "." and followed by a single optional parameter part
                        var nextPartIndex = j + 1;

                        if (nextPartIndex == segment.Parts.Count - 1 &&
                            segment.Parts[nextPartIndex].IsParameter &&
                            segment.Parts[nextPartIndex] is RoutePatternParameterPart extensionParameterPart &&
                            extensionParameterPart.IsOptional)
                        {
                            continue;
                        }
                    }

                    // Stop because there is a non-optional/non-defaulted trailing value
                    return(false);
                }
            }

            return(usedDefaultValue);
        }
        private bool MatchRouteValue(ActionDescriptor action, MvcEndpointInfo endpointInfo, string routeKey)
        {
            if (!action.RouteValues.TryGetValue(routeKey, out var actionValue) || string.IsNullOrWhiteSpace(actionValue))
            {
                // Action does not have a value for this routeKey, most likely because action is not in an area
                // Check that the pattern does not have a parameter for the routeKey
                var matchingParameter = endpointInfo.ParsedPattern.GetParameter(routeKey);
                if (matchingParameter == null &&
                    (!endpointInfo.ParsedPattern.Defaults.TryGetValue(routeKey, out var value) ||
                     !string.IsNullOrEmpty(Convert.ToString(value))))
                {
                    return(true);
                }
            }
            else
            {
                if (endpointInfo.MergedDefaults != null && string.Equals(actionValue, endpointInfo.MergedDefaults[routeKey] as string, StringComparison.OrdinalIgnoreCase))
                {
                    return(true);
                }

                var matchingParameter = endpointInfo.ParsedPattern.GetParameter(routeKey);
                if (matchingParameter != null)
                {
                    // Check that the value matches against constraints on that parameter
                    // e.g. For {controller:regex((Home|Login))} the controller value must match the regex
                    if (endpointInfo.ParameterPolicies.TryGetValue(routeKey, out var parameterPolicies))
                    {
                        foreach (var policy in parameterPolicies)
                        {
                            if (policy is IRouteConstraint constraint &&
                                !constraint.Match(httpContext: null, NullRouter.Instance, routeKey, new RouteValueDictionary(action.RouteValues), RouteDirection.IncomingRequest))
                            {
                                // Did not match constraint
                                return(false);
                            }
                        }
                    }

                    return(true);
                }
            }

            return(false);
        }
        private bool MatchRouteValue(ActionDescriptor action, MvcEndpointInfo endpointInfo, string routeKey)
        {
            if (!action.RouteValues.TryGetValue(routeKey, out var actionValue) || string.IsNullOrWhiteSpace(actionValue))
            {
                // Action does not have a value for this routeKey, most likely because action is not in an area
                // Check that the template does not have a parameter for the routeKey
                var matchingParameter = endpointInfo.ParsedTemplate.Parameters.SingleOrDefault(p => string.Equals(p.Name, routeKey, StringComparison.OrdinalIgnoreCase));
                if (matchingParameter == null)
                {
                    return(true);
                }
            }
            else
            {
                if (endpointInfo.Defaults != null && string.Equals(actionValue, endpointInfo.Defaults[routeKey] as string, StringComparison.OrdinalIgnoreCase))
                {
                    return(true);
                }

                var matchingParameter = endpointInfo.ParsedTemplate.Parameters.SingleOrDefault(p => string.Equals(p.Name, routeKey, StringComparison.OrdinalIgnoreCase));
                if (matchingParameter != null)
                {
                    // Check that the value matches against constraints on that parameter
                    // e.g. For {controller:regex((Home|Login))} the controller value must match the regex
                    //
                    // REVIEW: This is really ugly
                    if (endpointInfo.Constraints.TryGetValue(routeKey, out var constraint) &&
                        !constraint.Match(new DefaultHttpContext()
                    {
                        RequestServices = _serviceProvider
                    }, new DummyRouter(), routeKey, new RouteValueDictionary(action.RouteValues), RouteDirection.IncomingRequest))
                    {
                        // Did not match constraint
                        return(false);
                    }

                    return(true);
                }
            }

            return(false);
        }
Exemple #5
0
        private bool UseDefaultValuePlusRemainingSegementsOptional(
            int segmentIndex,
            ActionDescriptor action,
            MvcEndpointInfo endpointInfo,
            RouteTemplate template)
        {
            // Check whether the remaining segments are all optional and one or more of them is
            // for area/controller/action and has a default value
            var usedDefaultValue = false;

            for (var i = segmentIndex; i < template.Segments.Count; i++)
            {
                var segment = template.Segments[i];
                for (var j = 0; j < segment.Parts.Count; j++)
                {
                    var part = segment.Parts[j];
                    if (part.IsOptional || part.IsOptionalSeperator || part.IsCatchAll)
                    {
                        continue;
                    }
                    if (part.IsParameter)
                    {
                        if (IsMvcParameter(part.Name))
                        {
                            if (endpointInfo.MergedDefaults[part.Name] is string defaultValue &&
                                action.RouteValues.TryGetValue(part.Name, out var routeValue) &&
                                string.Equals(defaultValue, routeValue, StringComparison.OrdinalIgnoreCase))
                            {
                                usedDefaultValue = true;
                                continue;
                            }
                        }
                    }

                    // Stop because there is a non-optional/non-defaulted trailing value
                    return(false);
                }
            }

            return(usedDefaultValue);
        }
        private void UpdatePathSegments(
            int i,
            ActionDescriptor action,
            IDictionary <string, string> resolvedRequiredValues,
            RoutePattern routePattern,
            List <RoutePatternPathSegment> newPathSegments,
            ref IDictionary <string, IList <IParameterPolicy> > allParameterPolicies)
        {
            List <RoutePatternPart> segmentParts = null; // Initialize only as needed
            var segment = newPathSegments[i];

            for (var j = 0; j < segment.Parts.Count; j++)
            {
                var part = segment.Parts[j];

                if (part is RoutePatternParameterPart parameterPart)
                {
                    if (resolvedRequiredValues.TryGetValue(parameterPart.Name, out var parameterRouteValue))
                    {
                        if (segmentParts == null)
                        {
                            segmentParts = segment.Parts.ToList();
                        }
                        if (allParameterPolicies == null)
                        {
                            allParameterPolicies = MvcEndpointInfo.BuildParameterPolicies(routePattern.Parameters, _parameterPolicyFactory);
                        }

                        // Route value could be null if it is a "known" route value.
                        // Do not use the null value to de-normalize the route pattern,
                        // instead leave the parameter unchanged.
                        // e.g.
                        //     RouteValues will contain a null "page" value if there are Razor pages
                        //     Skip replacing the {page} parameter
                        if (parameterRouteValue != null)
                        {
                            if (allParameterPolicies.TryGetValue(parameterPart.Name, out var parameterPolicies))
                            {
                                // Check if the parameter has a transformer policy
                                // Use the first transformer policy
                                for (var k = 0; k < parameterPolicies.Count; k++)
                                {
                                    if (parameterPolicies[k] is IOutboundParameterTransformer parameterTransformer)
                                    {
                                        parameterRouteValue = parameterTransformer.TransformOutbound(parameterRouteValue);
                                        break;
                                    }
                                }
                            }

                            segmentParts[j] = RoutePatternFactory.LiteralPart(parameterRouteValue);
                        }
                    }
                }
            }

            // A parameter part was replaced so replace segment with updated parts
            if (segmentParts != null)
            {
                newPathSegments[i] = RoutePatternFactory.Segment(segmentParts);
            }
        }
Exemple #7
0
        // CreateEndpoints processes the route pattern, replacing area/controller/action parameters with endpoint values
        // Because of default values it is possible for a route pattern to resolve to multiple endpoints
        private int CreateEndpoints(
            List <Endpoint> endpoints,
            ref StringBuilder patternStringBuilder,
            ActionDescriptor action,
            int routeOrder,
            RoutePattern routePattern,
            IReadOnlyDictionary <string, object> allDefaults,
            IReadOnlyDictionary <string, object> nonInlineDefaults,
            string name,
            RouteValueDictionary dataTokens,
            IDictionary <string, IList <IParameterPolicy> > allParameterPolicies,
            bool suppressLinkGeneration,
            bool suppressPathMatching)
        {
            var newPathSegments = routePattern.PathSegments.ToList();

            for (var i = 0; i < newPathSegments.Count; i++)
            {
                // Check if the pattern can be shortened because the remaining parameters are optional
                //
                // e.g. Matching pattern {controller=Home}/{action=Index}/{id?} against HomeController.Index
                // can resolve to the following endpoints:
                // - /Home/Index/{id?}
                // - /Home
                // - /
                if (UseDefaultValuePlusRemainingSegmentsOptional(i, action, allDefaults, newPathSegments))
                {
                    var subPathSegments = newPathSegments.Take(i);

                    var subEndpoint = CreateEndpoint(
                        action,
                        name,
                        GetPattern(ref patternStringBuilder, subPathSegments),
                        subPathSegments,
                        nonInlineDefaults,
                        routeOrder++,
                        dataTokens,
                        suppressLinkGeneration,
                        suppressPathMatching);
                    endpoints.Add(subEndpoint);
                }

                List <RoutePatternPart> segmentParts = null; // Initialize only as needed
                var segment = newPathSegments[i];
                for (var j = 0; j < segment.Parts.Count; j++)
                {
                    var part = segment.Parts[j];

                    if (part.IsParameter &&
                        part is RoutePatternParameterPart parameterPart &&
                        action.RouteValues.ContainsKey(parameterPart.Name))
                    {
                        if (segmentParts == null)
                        {
                            segmentParts = segment.Parts.ToList();
                        }
                        if (allParameterPolicies == null)
                        {
                            allParameterPolicies = MvcEndpointInfo.BuildParameterPolicies(routePattern.Parameters, _parameterPolicyFactory);
                        }

                        var parameterRouteValue = action.RouteValues[parameterPart.Name];

                        // Replace parameter with literal value
                        if (allParameterPolicies.TryGetValue(parameterPart.Name, out var parameterPolicies))
                        {
                            // Check if the parameter has a transformer policy
                            // Use the first transformer policy
                            for (var k = 0; k < parameterPolicies.Count; k++)
                            {
                                if (parameterPolicies[k] is IParameterTransformer parameterTransformer)
                                {
                                    parameterRouteValue = parameterTransformer.Transform(parameterRouteValue);
                                    break;
                                }
                            }
                        }

                        segmentParts[j] = RoutePatternFactory.LiteralPart(parameterRouteValue);
                    }
                }

                // A parameter part was replaced so replace segment with updated parts
                if (segmentParts != null)
                {
                    newPathSegments[i] = RoutePatternFactory.Segment(segmentParts);
                }
            }

            var endpoint = CreateEndpoint(
                action,
                name,
                GetPattern(ref patternStringBuilder, newPathSegments),
                newPathSegments,
                nonInlineDefaults,
                routeOrder++,
                dataTokens,
                suppressLinkGeneration,
                suppressPathMatching);

            endpoints.Add(endpoint);

            return(routeOrder);

            string GetPattern(ref StringBuilder sb, IEnumerable <RoutePatternPathSegment> segments)
            {
                if (sb == null)
                {
                    sb = new StringBuilder();
                }

                RoutePatternWriter.WriteString(sb, segments);
                var rawPattern = sb.ToString();

                sb.Length = 0;

                return(rawPattern);
            }
        }