Example #1
0
        internal void Match(RouteContext context)
        {
            var pathIndex     = 0;
            var templateIndex = 0;
            Dictionary <string, object> parameters = null;

            // We will iterate over the path segments and the template segments until we have consumed
            // one of them.
            // There are three cases we need to account here for:
            // * Path is shorter than template ->
            //   * This can match only if we have t-p optional parameters at the end.
            // * Path and template have the same number of segments
            //   * This can happen when the catch-all segment matches 1 segment
            //   * This can happen when an optional parameter has been specified.
            //   * This can happen when the route only contains literals and parameters.
            // * Path is longer than template -> This can only match if the parameter has a catch-all at the end.
            //   * We still need to iterate over all the path segments if the catch-all is constrained.
            //   * We still need to iterate over all the template/path segments before the catch-all
            while (pathIndex < context.Segments.Length && templateIndex < Template.Segments.Length)
            {
                var pathSegment     = context.Segments[pathIndex];
                var templateSegment = Template.Segments[templateIndex];

                var matches = templateSegment.Match(pathSegment, out var match);
                if (!matches)
                {
                    // A constraint or literal didn't match
                    return;
                }

                if (!templateSegment.IsCatchAll)
                {
                    // We were dealing with a literal or a parameter, so just advance both cursors.
                    pathIndex++;
                    templateIndex++;

                    if (templateSegment.IsParameter)
                    {
                        parameters ??= new(StringComparer.OrdinalIgnoreCase);
                        parameters[templateSegment.Value] = match;
                    }
                }
                else
                {
                    if (templateSegment.Constraints.Length == 0)
                    {
                        // Unconstrained catch all, we can stop early
                        parameters ??= new(StringComparer.OrdinalIgnoreCase);
                        parameters[templateSegment.Value] = string.Join('/', context.Segments, pathIndex, context.Segments.Length - pathIndex);

                        // Mark the remaining segments as consumed.
                        pathIndex = context.Segments.Length;

                        // Catch-alls are always last.
                        templateIndex++;

                        // We are done, so break out of the loop.
                        break;
                    }
                    else
                    {
                        // For constrained catch-alls, we advance the path index but keep the template index on the catch-all.
                        pathIndex++;
                        if (pathIndex == context.Segments.Length)
                        {
                            parameters ??= new(StringComparer.OrdinalIgnoreCase);
                            parameters[templateSegment.Value] = string.Join('/', context.Segments, templateIndex, context.Segments.Length - templateIndex);

                            // This is important to signal that we consumed the entire template.
                            templateIndex++;
                        }
                    }
                }
            }

            var hasRemainingOptionalSegments = templateIndex < Template.Segments.Length &&
                                               RemainingSegmentsAreOptional(pathIndex, Template.Segments);

            if ((pathIndex == context.Segments.Length && templateIndex == Template.Segments.Length) || hasRemainingOptionalSegments)
            {
                if (hasRemainingOptionalSegments)
                {
                    parameters ??= new Dictionary <string, object>(StringComparer.Ordinal);
                    AddDefaultValues(parameters, templateIndex, Template.Segments);
                }
                if (UnusedRouteParameterNames?.Length > 0)
                {
                    parameters ??= new Dictionary <string, object>(StringComparer.Ordinal);
                    for (var i = 0; i < UnusedRouteParameterNames.Length; i++)
                    {
                        parameters[UnusedRouteParameterNames[i]] = null;
                    }
                }
                context.Handler    = Handler;
                context.Parameters = parameters;
            }
        }
Example #2
0
        internal void Match(RouteContext context)
        {
            // If there are no optional segments on the route and the length of the route
            // and the template do not match, then there is no chance of this matching and
            // we can bail early.
            if (Template.OptionalSegmentsCount == 0 && Template.Segments.Length != context.Segments.Length)
            {
                return;
            }

            // Parameters will be lazily initialized.
            Dictionary <string, object> parameters = null;
            var numMatchingSegments = 0;

            for (var i = 0; i < Template.Segments.Length; i++)
            {
                var segment = Template.Segments[i];

                // If the template contains more segments than the path, then
                // we may need to break out of this for-loop. This can happen
                // in one of two cases:
                //
                // (1) If we are comparing a literal route with a literal template
                // and the route is shorter than the template.
                // (2) If we are comparing a template where the last value is an optional
                // parameter that the route does not provide.
                if (i >= context.Segments.Length)
                {
                    // If we are under condition (1) above then we can stop evaluating
                    // matches on the rest of this template.
                    if (!segment.IsParameter && !segment.IsOptional)
                    {
                        break;
                    }
                }

                string pathSegment = null;
                if (i < context.Segments.Length)
                {
                    pathSegment = context.Segments[i];
                }

                if (!segment.Match(pathSegment, out var matchedParameterValue))
                {
                    return;
                }
                else
                {
                    numMatchingSegments++;
                    if (segment.IsParameter)
                    {
                        parameters ??= new Dictionary <string, object>(StringComparer.Ordinal);
                        parameters[segment.Value] = matchedParameterValue;
                    }
                }
            }

            // In addition to extracting parameter values from the URL, each route entry
            // also knows which other parameters should be supplied with null values. These
            // are parameters supplied by other route entries matching the same handler.
            if (UnusedRouteParameterNames.Length > 0)
            {
                parameters ??= new Dictionary <string, object>(StringComparer.Ordinal);
                for (var i = 0; i < UnusedRouteParameterNames.Length; i++)
                {
                    parameters[UnusedRouteParameterNames[i]] = null;
                }
            }

            // We track the number of segments in the template that matched
            // against this particular route then only select the route that
            // matches the most number of segments on the route that was passed.
            // This check is an exactness check that favors the more precise of
            // two templates in the event that the following route table exists.
            //  Route 1: /{anythingGoes}
            //  Route 2: /users/{id:int}
            // And the provided route is `/users/1`. We want to choose Route 2
            // over Route 1.
            // Furthermore, literal routes are preferred over parameterized routes.
            // If the two routes below are registered in the route table.
            // Route 1: /users/1
            // Route 2: /users/{id:int}
            // And the provided route is `/users/1`. We want to choose Route 1 over
            // Route 2.
            var allRouteSegmentsMatch = numMatchingSegments >= context.Segments.Length;
            // Checking that all route segments have been matches does not suffice if we are
            // comparing literal templates with literal routes. For example, the template
            // `/this/is/a/template` and the route `/this/`. In that case, we want to ensure
            // that all non-optional segments have matched as well.
            var allNonOptionalSegmentsMatch = numMatchingSegments >= (Template.Segments.Length - Template.OptionalSegmentsCount);

            if (allRouteSegmentsMatch && allNonOptionalSegmentsMatch)
            {
                context.Parameters = parameters;
                context.Handler    = Handler;
            }
        }