public void Pattern_RawTextAndArrayOfSegments_ShouldMakeCopyOfArrayOfSegments()
    {
        // Arrange
        var rawText      = "raw";
        var literalPartA = RoutePatternFactory.LiteralPart("A");
        var paramPartB   = RoutePatternFactory.ParameterPart("B");
        var paramPartC   = RoutePatternFactory.ParameterPart("C");
        var paramPartD   = RoutePatternFactory.ParameterPart("D");
        var segments     = new[]
        {
            RoutePatternFactory.Segment(literalPartA, paramPartB),
            RoutePatternFactory.Segment(paramPartC, literalPartA),
            RoutePatternFactory.Segment(paramPartD),
            RoutePatternFactory.Segment(literalPartA)
        };

        // Act
        var actual = RoutePatternFactory.Pattern(rawText, segments);

        segments[1] = RoutePatternFactory.Segment(RoutePatternFactory.ParameterPart("E"));
        Array.Resize(ref segments, 2);

        // Assert
        Assert.Equal(3, actual.Parameters.Count);
        Assert.Same(paramPartB, actual.Parameters[0]);
        Assert.Same(paramPartC, actual.Parameters[1]);
        Assert.Same(paramPartD, actual.Parameters[2]);
    }
Esempio n. 2
0
        public void ToRoutePatternPathSegment()
        {
            // Arrange
            var literalPartA    = RoutePatternFactory.LiteralPart("A");
            var paramPartB      = RoutePatternFactory.ParameterPart("B");
            var paramPartC      = RoutePatternFactory.ParameterPart("C");
            var paramPartD      = RoutePatternFactory.ParameterPart("D");
            var separatorPartE  = RoutePatternFactory.SeparatorPart("E");
            var templateSegment = new TemplateSegment(RoutePatternFactory.Segment(paramPartC, literalPartA, separatorPartE, paramPartB));

            // Act
            var routePatternPathSegment = templateSegment.ToRoutePatternPathSegment();

            templateSegment.Parts[1] = new TemplatePart(RoutePatternFactory.ParameterPart("D"));
            templateSegment.Parts.RemoveAt(0);

            // Assert
            Assert.Equal(4, routePatternPathSegment.Parts.Count);
            Assert.IsType <RoutePatternParameterPart>(routePatternPathSegment.Parts[0]);
            Assert.Equal(paramPartC.Name, ((RoutePatternParameterPart)routePatternPathSegment.Parts[0]).Name);
            Assert.IsType <RoutePatternLiteralPart>(routePatternPathSegment.Parts[1]);
            Assert.Equal(literalPartA.Content, ((RoutePatternLiteralPart)routePatternPathSegment.Parts[1]).Content);
            Assert.IsType <RoutePatternSeparatorPart>(routePatternPathSegment.Parts[2]);
            Assert.Equal(separatorPartE.Content, ((RoutePatternSeparatorPart)routePatternPathSegment.Parts[2]).Content);
            Assert.IsType <RoutePatternParameterPart>(routePatternPathSegment.Parts[3]);
            Assert.Equal(paramPartB.Name, ((RoutePatternParameterPart)routePatternPathSegment.Parts[3]).Name);
        }
    private static void RemoveParameterDefault(List <RoutePatternPathSegment> segments, List <RoutePatternParameterPart> parameters, RoutePatternParameterPart parameter)
    {
        // We know that a parameter can only appear once, so we only need to rewrite one segment and one parameter.
        for (var i = 0; i < segments.Count; i++)
        {
            var segment = segments[i];
            for (var j = 0; j < segment.Parts.Count; j++)
            {
                if (object.ReferenceEquals(parameter, segment.Parts[j]))
                {
                    // Found it!
                    var updatedParameter = RoutePatternFactory.ParameterPart(parameter.Name, @default: null, parameter.ParameterKind, parameter.ParameterPolicies);

                    var updatedParts = new List <RoutePatternPart>(segment.Parts);
                    updatedParts[j] = updatedParameter;
                    segments[i]     = RoutePatternFactory.Segment(updatedParts);

                    for (var k = 0; k < parameters.Count; k++)
                    {
                        if (ReferenceEquals(parameter, parameters[k]))
                        {
                            parameters[k] = updatedParameter;
                            break;
                        }
                    }

                    return;
                }
            }
        }
    }
    public void Segment_ArrayOfParts()
    {
        // Arrange
        var paramPartB = RoutePatternFactory.ParameterPart("B");
        var paramPartC = RoutePatternFactory.ParameterPart("C");
        var paramPartD = RoutePatternFactory.ParameterPart("D");
        var parts      = new[] { paramPartB, paramPartC, paramPartD };

        // Act
        var actual = RoutePatternFactory.Segment(parts);

        parts[1] = RoutePatternFactory.ParameterPart("E");
        Array.Resize(ref parts, 2);

        // Assert
        Assert.Equal(3, actual.Parts.Count);
        Assert.Same(paramPartB, actual.Parts[0]);
        Assert.Same(paramPartC, actual.Parts[1]);
        Assert.Same(paramPartD, actual.Parts[2]);
    }
    public void Pattern_RawTextAndDefaultsAndParameterPoliciesAndArrayOfSegments_ShouldMakeCopyOfArrayOfSegments()
    {
        // Arrange
        var    rawText           = "raw";
        object defaults          = new { B = 12, C = 4 };
        object parameterPolicies = null;
        var    literalPartA      = RoutePatternFactory.LiteralPart("A");
        var    paramPartB        = RoutePatternFactory.ParameterPart("B");
        var    paramPartC        = RoutePatternFactory.ParameterPart("C");
        var    paramPartD        = RoutePatternFactory.ParameterPart("D");
        var    segments          = new[]
        {
            RoutePatternFactory.Segment(literalPartA, paramPartB),
            RoutePatternFactory.Segment(paramPartC, literalPartA),
            RoutePatternFactory.Segment(paramPartD),
            RoutePatternFactory.Segment(literalPartA)
        };

        // Act
        var actual = RoutePatternFactory.Pattern(rawText, defaults, parameterPolicies, segments);

        segments[1] = RoutePatternFactory.Segment(RoutePatternFactory.ParameterPart("E"));
        Array.Resize(ref segments, 2);

        // Assert
        Assert.Equal(3, actual.Parameters.Count);
        Assert.Equal(paramPartB.Name, actual.Parameters[0].Name);
        Assert.Equal(12, actual.Parameters[0].Default);
        Assert.Null(paramPartB.Default);
        Assert.NotSame(paramPartB, actual.Parameters[0]);
        Assert.Equal(paramPartC.Name, actual.Parameters[1].Name);
        Assert.Equal(4, actual.Parameters[1].Default);
        Assert.NotSame(paramPartC, actual.Parameters[1]);
        Assert.Null(paramPartC.Default);
        Assert.Equal(paramPartD.Name, actual.Parameters[2].Name);
        Assert.Null(actual.Parameters[2].Default);
        Assert.Same(paramPartD, actual.Parameters[2]);
        Assert.Null(paramPartD.Default);
    }
Esempio n. 6
0
        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);
            }
        }
Esempio n. 7
0
        public RoutePatternPathSegment ToRoutePatternPathSegment()
        {
            var parts = Parts.Select(p => p.ToRoutePatternPart());

            return(RoutePatternFactory.Segment(parts));
        }
Esempio n. 8
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);
            }
        }
Esempio n. 9
0
        private void UpdateEndpoints()
        {
            lock (_lock)
            {
                var           endpoints            = new List <Endpoint>();
                StringBuilder patternStringBuilder = null;

                foreach (var action in _actions.ActionDescriptors.Items)
                {
                    if (action.AttributeRouteInfo == null)
                    {
                        // In traditional conventional routing setup, the routes defined by a user have a static order
                        // defined by how they are added into the list. We would like to maintain the same order when building
                        // up the endpoints too.
                        //
                        // Start with an order of '1' for conventional routes as attribute routes have a default order of '0'.
                        // This is for scenarios dealing with migrating existing Router based code to Endpoint Routing world.
                        var conventionalRouteOrder = 0;

                        // Check each of the conventional patterns to see if the action would be reachable
                        // If the action and pattern are compatible then create an endpoint with the
                        // area/controller/action parameter parts replaced with literals
                        //
                        // e.g. {controller}/{action} with HomeController.Index and HomeController.Login
                        // would result in endpoints:
                        // - Home/Index
                        // - Home/Login
                        foreach (var endpointInfo in ConventionalEndpointInfos)
                        {
                            // An 'endpointInfo' is applicable if:
                            // 1. it has a parameter (or default value) for 'required' non-null route value
                            // 2. it does not have a parameter (or default value) for 'required' null route value
                            var isApplicable = true;
                            foreach (var routeKey in action.RouteValues.Keys)
                            {
                                if (!MatchRouteValue(action, endpointInfo, routeKey))
                                {
                                    isApplicable = false;
                                    break;
                                }
                            }

                            if (!isApplicable)
                            {
                                continue;
                            }

                            var newPathSegments = endpointInfo.ParsedPattern.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, endpointInfo, newPathSegments))
                                {
                                    var subPathSegments = newPathSegments.Take(i);

                                    var subEndpoint = CreateEndpoint(
                                        action,
                                        endpointInfo.Name,
                                        GetPattern(ref patternStringBuilder, subPathSegments),
                                        subPathSegments,
                                        endpointInfo.Defaults,
                                        ++conventionalRouteOrder,
                                        endpointInfo,
                                        endpointInfo.DataTokens,
                                        suppressLinkGeneration: false,
                                        suppressPathMatching: false);
                                    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();
                                        }

                                        // Replace parameter with literal value
                                        segmentParts[j] = RoutePatternFactory.LiteralPart(action.RouteValues[parameterPart.Name]);
                                    }
                                }

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

                            var endpoint = CreateEndpoint(
                                action,
                                endpointInfo.Name,
                                GetPattern(ref patternStringBuilder, newPathSegments),
                                newPathSegments,
                                endpointInfo.Defaults,
                                ++conventionalRouteOrder,
                                endpointInfo,
                                endpointInfo.DataTokens,
                                suppressLinkGeneration: false,
                                suppressPathMatching: false);
                            endpoints.Add(endpoint);
                        }
                    }
                    else
                    {
                        var endpoint = CreateEndpoint(
                            action,
                            action.AttributeRouteInfo.Name,
                            action.AttributeRouteInfo.Template,
                            RoutePatternFactory.Parse(action.AttributeRouteInfo.Template).PathSegments,
                            nonInlineDefaults: null,
                            action.AttributeRouteInfo.Order,
                            action.AttributeRouteInfo,
                            dataTokens: null,
                            suppressLinkGeneration: action.AttributeRouteInfo.SuppressLinkGeneration,
                            suppressPathMatching: action.AttributeRouteInfo.SuppressPathMatching);
                        endpoints.Add(endpoint);
                    }
                }

                // See comments in DefaultActionDescriptorCollectionProvider. These steps are done
                // in a specific order to ensure callers always see a consistent state.

                // Step 1 - capture old token
                var oldCancellationTokenSource = _cancellationTokenSource;

                // Step 2 - update endpoints
                _endpoints = endpoints;

                // Step 3 - create new change token
                _cancellationTokenSource = new CancellationTokenSource();
                _changeToken             = new CancellationChangeToken(_cancellationTokenSource.Token);

                // Step 4 - trigger old token
                oldCancellationTokenSource?.Cancel();
            }

            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);
            }
        }
Esempio n. 10
0
        private IReadOnlyList <Endpoint> Update(StaticFileOptionsProvider staticFileOptionsProvider)
        {
            const string HtmlExtension      = ".html";
            const string CatchAllSlugPrefix = "...";

            var staticFileOptions = staticFileOptionsProvider.StaticFileOptions;
            var requestDelegate   = CreateRequestDelegate(this.endpointRouteBuilder, staticFileOptions);
            var endpoints         = new List <Endpoint>();

            foreach (var filePath in TraverseFiles(staticFileOptions.FileProvider))
            {
                if (!filePath.EndsWith(HtmlExtension))
                {
                    continue;
                }

                var fileWithoutHtml = filePath.Substring(0, filePath.Length - HtmlExtension.Length);
                var patternSegments = new List <RoutePatternPathSegment>();
                var segments        = fileWithoutHtml.Split('/');

                // NOTE: Start at 1 because paths here always have a leading slash
                for (int i = 1; i < segments.Length; i++)
                {
                    var segment = segments[i];
                    if (i == segments.Length - 1 && segment == "index")
                    {
                        // Skip `index` segment, match whatever we got so far.
                        // This is so that e.g. file `/a/b/index.html` is served at path `/a/b`, as desired.

                        // TODO: Should we also serve the same file at `/a/b/index`? Note that `/a/b/index.html` will already work
                        // via the UseStaticFiles middleware added by `NextjsStaticHostingExtensions.UseNextjsStaticHosting`.
                        break;
                    }

                    var match = slugRegex.Match(segment);
                    if (match.Success)
                    {
                        string slugName = match.Groups[1].Value;
                        if (slugName.StartsWith(CatchAllSlugPrefix))
                        {
                            // Catch all route -- see: https://nextjs.org/docs/routing/dynamic-routes#catch-all-routes
                            var parameterName = slugName.Substring(CatchAllSlugPrefix.Length);
                            patternSegments.Add(
                                RoutePatternFactory.Segment(
                                    RoutePatternFactory.ParameterPart(parameterName, null, RoutePatternParameterKind.CatchAll)));
                        }
                        else
                        {
                            // Dynamic route -- see: https://nextjs.org/docs/routing/dynamic-routes
                            patternSegments.Add(
                                RoutePatternFactory.Segment(
                                    RoutePatternFactory.ParameterPart(slugName)));
                        }
                    }
                    else
                    {
                        // Literal match
                        patternSegments.Add(
                            RoutePatternFactory.Segment(
                                RoutePatternFactory.LiteralPart(segment)));
                    }
                }
                var endpointBuilder = new RouteEndpointBuilder(requestDelegate, RoutePatternFactory.Pattern(patternSegments), order: DefaultEndpointOrder);

                endpointBuilder.Metadata.Add(new StaticFileEndpointMetadata(filePath));
                endpointBuilder.DisplayName = $"Next.js {filePath}";
                var endpoint = endpointBuilder.Build();
                endpoints.Add(endpoint);
            }

            return(endpoints);
        }
Esempio n. 11
0
        private List <Endpoint> CreateEndpoints()
        {
            var           endpoints            = new List <Endpoint>();
            StringBuilder patternStringBuilder = null;

            foreach (var action in _actions.ActionDescriptors.Items)
            {
                if (action.AttributeRouteInfo == null)
                {
                    // In traditional conventional routing setup, the routes defined by a user have a static order
                    // defined by how they are added into the list. We would like to maintain the same order when building
                    // up the endpoints too.
                    //
                    // Start with an order of '1' for conventional routes as attribute routes have a default order of '0'.
                    // This is for scenarios dealing with migrating existing Router based code to Endpoint Routing world.
                    var conventionalRouteOrder = 0;

                    // Check each of the conventional patterns to see if the action would be reachable
                    // If the action and pattern are compatible then create an endpoint with the
                    // area/controller/action parameter parts replaced with literals
                    //
                    // e.g. {controller}/{action} with HomeController.Index and HomeController.Login
                    // would result in endpoints:
                    // - Home/Index
                    // - Home/Login
                    foreach (var endpointInfo in ConventionalEndpointInfos)
                    {
                        if (MatchRouteValue(action, endpointInfo, "Area") &&
                            MatchRouteValue(action, endpointInfo, "Controller") &&
                            MatchRouteValue(action, endpointInfo, "Action"))
                        {
                            var newPathSegments = endpointInfo.ParsedPattern.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 (UseDefaultValuePlusRemainingSegementsOptional(i, action, endpointInfo, newPathSegments))
                                {
                                    var subPathSegments = newPathSegments.Take(i);

                                    var subEndpoint = CreateEndpoint(
                                        action,
                                        endpointInfo.Name,
                                        GetPattern(ref patternStringBuilder, subPathSegments),
                                        subPathSegments,
                                        endpointInfo.Defaults,
                                        ++conventionalRouteOrder,
                                        endpointInfo,
                                        suppressLinkGeneration: false);
                                    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 && IsMvcParameter(parameterPart.Name))
                                    {
                                        if (segmentParts == null)
                                        {
                                            segmentParts = segment.Parts.ToList();
                                        }

                                        // Replace parameter with literal value
                                        segmentParts[j] = RoutePatternFactory.LiteralPart(action.RouteValues[parameterPart.Name]);
                                    }
                                }

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

                            var endpoint = CreateEndpoint(
                                action,
                                endpointInfo.Name,
                                GetPattern(ref patternStringBuilder, newPathSegments),
                                newPathSegments,
                                endpointInfo.Defaults,
                                ++conventionalRouteOrder,
                                endpointInfo,
                                suppressLinkGeneration: false);
                            endpoints.Add(endpoint);
                        }
                    }
                }
                else
                {
                    var endpoint = CreateEndpoint(
                        action,
                        action.AttributeRouteInfo.Name,
                        action.AttributeRouteInfo.Template,
                        RoutePatternFactory.Parse(action.AttributeRouteInfo.Template).PathSegments,
                        nonInlineDefaults: null,
                        action.AttributeRouteInfo.Order,
                        action.AttributeRouteInfo,
                        suppressLinkGeneration: action.AttributeRouteInfo.SuppressLinkGeneration);
                    endpoints.Add(endpoint);
                }
            }

            return(endpoints);

            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);
            }
        }