Esempio n. 1
0
        public void CanMatchMultipleOptionalParameterWithConstraint()
        {
            // Arrange
            var    template       = "/optional/{value:datetime?}/{value2:datetime?}";
            var    contextUrl     = "/optional//";
            object convertedValue = null;

            var routeTable = new TestRouteTableBuilder().AddRoute(template).Build();
            var context    = new RouteContext(contextUrl);

            // Act
            routeTable.Route(context);

            // Assert
            if (context.Handler == null)
            {
                // Make it easier to track down failing tests when using MemberData
                throw new InvalidOperationException($"Failed to match template '{template}'.");
            }
            Assert.Equal(new Dictionary <string, object>
            {
                { "value", convertedValue },
                { "value2", convertedValue }
            }, context.Parameters);
        }
Esempio n. 2
0
 public void Route(RouteContext routeContext)
 {
     for (var i = 0; i < Routes.Length; i++)
     {
         Routes[i].Match(routeContext);
         if (routeContext.Handler != null)
         {
             return;
         }
     }
 }
Esempio n. 3
0
        public void RouteMatchingIsCaseInsensitive()
        {
            // Arrange
            var routeTable = new TestRouteTableBuilder().AddRoute("/some/AWESOME/route/").Build();
            var context    = new RouteContext("/Some/awesome/RouTe");

            // Act
            routeTable.Route(context);

            // Assert
            Assert.NotNull(context.Handler);
        }
Esempio n. 4
0
        public void DoesNotMatchIfDifferentNumberOfSegments(string path)
        {
            // Arrange
            var routeTable = new TestRouteTableBuilder().AddRoute("/some/awesome/route/").Build();
            var context    = new RouteContext(path);

            // Act
            routeTable.Route(context);

            // Assert
            Assert.Null(context.Handler);
        }
Esempio n. 5
0
        public void DoesNotMatchIfConstraintDoesNotMatch(string template, string contextUrl)
        {
            // Arrange
            var routeTable = new TestRouteTableBuilder().AddRoute(template).Build();
            var context    = new RouteContext(contextUrl);

            // Act
            routeTable.Route(context);

            // Assert
            Assert.Null(context.Handler);
        }
Esempio n. 6
0
        public void DoesNotMatchIfSegmentsDontMatch()
        {
            // Arrange
            var routeTable = new TestRouteTableBuilder().AddRoute("/some/AWESOME/route/").Build();
            var context    = new RouteContext("/some/brilliant/route");

            // Act
            routeTable.Route(context);

            // Assert
            Assert.Null(context.Handler);
        }
Esempio n. 7
0
        public void CanMatchEncodedSegments()
        {
            // Arrange
            var routeTable = new TestRouteTableBuilder().AddRoute("/some/ünicõdē/🛣/").Build();
            var context    = new RouteContext("/some/%C3%BCnic%C3%B5d%C4%93/%F0%9F%9B%A3");

            // Act
            routeTable.Route(context);

            // Assert
            Assert.NotNull(context.Handler);
        }
Esempio n. 8
0
        public void CanMatchRootTemplate()
        {
            // Arrange
            var routeTable = new TestRouteTableBuilder().AddRoute("/").Build();
            var context    = new RouteContext("/");

            // Act
            routeTable.Route(context);

            // Assert
            Assert.NotNull(context.Handler);
        }
Esempio n. 9
0
        public void CanMatchTemplateWithMultipleLiterals()
        {
            // Arrange
            var routeTable = new TestRouteTableBuilder().AddRoute("/some/awesome/route/").Build();
            var context    = new RouteContext("/some/awesome/route");

            // Act
            routeTable.Route(context);

            // Assert
            Assert.NotNull(context.Handler);
        }
Esempio n. 10
0
        public void CanMatchCatchAllParameterTemplate(string path, string expectedValue)
        {
            // Arrange
            var routeTable = new TestRouteTableBuilder().AddRoute("/blog/{*parameter}").Build();
            var context    = new RouteContext(path);

            // Act
            routeTable.Route(context);

            // Assert
            Assert.NotNull(context.Handler);
            Assert.Single(context.Parameters, p => p.Key == "parameter" && (string)p.Value == expectedValue);
        }
Esempio n. 11
0
        public void CanMatchSegmentWithMultipleConstraints(string template, string contextUrl, object convertedValue)
        {
            // Arrange
            var routeTable = new TestRouteTableBuilder().AddRoute(template).Build();
            var context    = new RouteContext(contextUrl);

            // Act
            routeTable.Route(context);

            // Assert
            Assert.Equal(new Dictionary <string, object>
            {
                { "value", convertedValue }
            }, context.Parameters);
        }
Esempio n. 12
0
        public void PrefersLiteralTemplateOverTemplateWithOptionalParameters()
        {
            // Arrange
            var routeTable = new TestRouteTableBuilder()
                             .AddRoute("/users/1", typeof(TestHandler1))
                             .AddRoute("/users/{id?}", typeof(TestHandler2))
                             .Build();
            var context = new RouteContext("/users/1");

            // Act
            routeTable.Route(context);

            // Assert
            Assert.NotNull(context.Handler);
            Assert.Null(context.Parameters);
        }
Esempio n. 13
0
        public void PrefersLiteralTemplateOverTemplateWithParameters()
        {
            // Arrange
            var routeTable = new TestRouteTableBuilder()
                             .AddRoute("/an/awesome/path", typeof(TestHandler1))
                             .AddRoute("/{some}/awesome/{route}/", typeof(TestHandler2))
                             .Build();
            var context = new RouteContext("/an/awesome/path");

            // Act
            routeTable.Route(context);

            // Assert
            Assert.NotNull(context.Handler);
            Assert.Null(context.Parameters);
        }
Esempio n. 14
0
        public void SuppliesNullForUnusedHandlerParameters()
        {
            // Arrange
            var routeTable = new TestRouteTableBuilder()
                             .AddRoute("/", typeof(TestHandler1))
                             .AddRoute("/products/{param1:int}", typeof(TestHandler1))
                             .AddRoute("/products/{param2}/{PaRam1}", typeof(TestHandler1))
                             .AddRoute("/{unrelated}", typeof(TestHandler2))
                             .Build();
            var context = new RouteContext("/products/456");

            // Act
            routeTable.Route(context);

            // Assert
            Assert.Collection(routeTable.Routes,
                              route =>
            {
                Assert.Same(typeof(TestHandler1), route.Handler);
                Assert.Equal("/", route.Template.TemplateText);
                Assert.Equal(new[] { "param1", "param2" }, route.UnusedRouteParameterNames);
            },
                              route =>
            {
                Assert.Same(typeof(TestHandler2), route.Handler);
                Assert.Equal("{unrelated}", route.Template.TemplateText);
                Assert.Equal(Array.Empty <string>(), route.UnusedRouteParameterNames);
            },
                              route =>
            {
                Assert.Same(typeof(TestHandler1), route.Handler);
                Assert.Equal("products/{param1:int}", route.Template.TemplateText);
                Assert.Equal(new[] { "param2" }, route.UnusedRouteParameterNames);
            },
                              route =>
            {
                Assert.Same(typeof(TestHandler1), route.Handler);
                Assert.Equal("products/{param2}/{PaRam1}", route.Template.TemplateText);
                Assert.Equal(Array.Empty <string>(), route.UnusedRouteParameterNames);
            });
            Assert.Same(typeof(TestHandler1), context.Handler);
            Assert.Equal(new Dictionary <string, object>
            {
                { "param1", 456 },
                { "param2", null },
            }, context.Parameters);
        }
Esempio n. 15
0
        public void PrefersRoutesThatMatchMoreSegments()
        {
            // Arrange
            var routeTable = new TestRouteTableBuilder()
                             .AddRoute("/{anythingGoes}", typeof(TestHandler1))
                             .AddRoute("/users/{id?}", typeof(TestHandler2))
                             .Build();
            var context = new RouteContext("/users/1");

            // Act
            routeTable.Route(context);

            // Assert
            Assert.NotNull(context.Handler);
            Assert.Equal(typeof(TestHandler2), context.Handler);
            Assert.NotNull(context.Parameters);
        }
Esempio n. 16
0
        public void PrefersMoreConstraintsOverFewer()
        {
            // Arrange
            var routeTable = new TestRouteTableBuilder()
                             .AddRoute("/products/{id}")
                             .AddRoute("/products/{id:int}").Build();
            var context = new RouteContext("/products/456");

            // Act
            routeTable.Route(context);

            // Assert
            Assert.NotNull(context.Handler);
            Assert.Equal(context.Parameters, new Dictionary <string, object>
            {
                { "id", 456 }
            });
        }
Esempio n. 17
0
        public void PrefersLiteralTemplateOverParameterizedTemplates()
        {
            // Arrange
            var routeTable = new TestRouteTableBuilder()
                             .AddRoute("/users/1/friends", typeof(TestHandler1))
                             .AddRoute("/users/{id}/{location}", typeof(TestHandler2))
                             .AddRoute("/users/1/{location}", typeof(TestHandler2))
                             .Build();
            var context = new RouteContext("/users/1/friends");

            // Act
            routeTable.Route(context);

            // Assert
            Assert.NotNull(context.Handler);
            Assert.Equal(typeof(TestHandler1), context.Handler);
            Assert.Null(context.Parameters);
        }
Esempio n. 18
0
        public void CanMatchTemplateWithMultipleParameters()
        {
            // Arrange
            var routeTable = new TestRouteTableBuilder().AddRoute("/{some}/awesome/{route}/").Build();
            var context    = new RouteContext("/an/awesome/path");

            var expectedParameters = new Dictionary <string, object>
            {
                ["some"]  = "an",
                ["route"] = "path"
            };

            // Act
            routeTable.Route(context);

            // Assert
            Assert.NotNull(context.Handler);
            Assert.Equal(expectedParameters, context.Parameters);
        }
Esempio n. 19
0
        public void PrefersOptionalParamsOverNonOptionalParamsReverseOrder()
        {
            // Arrange
            var routeTable = new TestRouteTableBuilder()
                             .AddRoute("/users/{id}", typeof(TestHandler1))
                             .AddRoute("/users/{id?}", typeof(TestHandler2))
                             .Build();
            var contextWithParam    = new RouteContext("/users/1");
            var contextWithoutParam = new RouteContext("/users/");

            // Act
            routeTable.Route(contextWithParam);
            routeTable.Route(contextWithoutParam);

            // Assert
            Assert.NotNull(contextWithParam.Handler);
            Assert.Equal(typeof(TestHandler1), contextWithParam.Handler);

            Assert.NotNull(contextWithoutParam.Handler);
            Assert.Equal(typeof(TestHandler2), contextWithoutParam.Handler);
        }
Esempio n. 20
0
        internal void Match(RouteContext context)
        {
            string?catchAllValue = null;

            // If this template contains a catch-all parameter, we can concatenate the pathSegments
            // at and beyond the catch-all segment's position. For example:
            // Template:        /foo/bar/{*catchAll}
            // PathSegments:    /foo/bar/one/two/three
            if (Template.ContainsCatchAllSegment && context.Segments.Length >= Template.Segments.Length)
            {
                catchAllValue = string.Join('/', context.Segments[Range.StartAt(Template.Segments.Length - 1)]);
            }
            // 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.
            else 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 (segment.IsCatchAll)
                {
                    numMatchingSegments += 1;
                    parameters ??= new Dictionary <string, object>(StringComparer.Ordinal);
                    parameters[segment.Value] = catchAllValue;
                    break;
                }

                // 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 (!Template.ContainsCatchAllSegment && 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 (Template.ContainsCatchAllSegment || (allRouteSegmentsMatch && allNonOptionalSegmentsMatch))
            {
                context.Parameters = parameters;
                context.Handler    = Handler;
            }
        }