public async Task MatchAsync_ValidRouteConstraint_EndpointMatched()
        {
            // Arrange
            var endpointDataSource = new DefaultEndpointDataSource(new List <Endpoint>
            {
                CreateEndpoint("/{p:int}", 0)
            });

            var matcher = CreateDfaMatcher(endpointDataSource);

            var(httpContext, context) = CreateContext();
            httpContext.Request.Path  = "/1";

            // Act
            await matcher.MatchAsync(httpContext, context);

            // Assert
            Assert.NotNull(context.Endpoint);
        }
        public async Task MatchAsync_RequireValuesAndOptionalParameter_EndpointMatched()
        {
            // Arrange
            var endpoint = CreateEndpoint(
                "{controller}/{action}/{id?}",
                0,
                requiredValues: new { controller = "Home", action = "Index", area = (string)null, page = (string)null });

            var dataSource = new DefaultEndpointDataSource(new List <Endpoint>
            {
                endpoint
            });

            var matcher = CreateDfaMatcher(dataSource);

            var httpContext = CreateContext();

            httpContext.Request.Path = "/Home/Index/123";

            // Act
            await matcher.MatchAsync(httpContext);

            // Assert
            Assert.Same(endpoint, httpContext.GetEndpoint());

            Assert.Collection(
                httpContext.Request.RouteValues.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("id", kvp.Key);
                Assert.Equal("123", kvp.Value);
            });
        }
    public void Constructor_Enumerable_ShouldMakeCopyOfEndpoints()
    {
        // Arrange
        var endpoint1 = new Endpoint(TestConstants.EmptyRequestDelegate, EndpointMetadataCollection.Empty, "1");
        var endpoint2 = new Endpoint(TestConstants.EmptyRequestDelegate, EndpointMetadataCollection.Empty, "2");
        var endpoints = new List <Endpoint> {
            endpoint1, endpoint2
        };

        // Act
        var dataSource = new DefaultEndpointDataSource((IEnumerable <Endpoint>)endpoints);

        endpoints.RemoveAt(0);
        endpoints[0] = null;

        // Assert
        Assert.Equal(2, dataSource.Endpoints.Count);
        Assert.Contains(endpoint1, dataSource.Endpoints);
        Assert.Contains(endpoint2, dataSource.Endpoints);
    }
        public void Write_ExcludeRouteEndpointWithSuppressMatchingMetadata()
        {
            // Arrange
            var graphWriter         = CreateGraphWriter();
            var writer              = new StringWriter();
            var endpointsDataSource = new DefaultEndpointDataSource(
                new RouteEndpoint(
                    (context) => null,
                    RoutePatternFactory.Parse("/"),
                    0,
                    new EndpointMetadataCollection(new SuppressMatchingMetadata()),
                    string.Empty));

            // Act
            graphWriter.Write(endpointsDataSource, writer);

            // Assert
            Assert.Equal(String.Join(Environment.NewLine, @"digraph DFA {",
                                     @"0 [label=""/""]",
                                     @"}") + Environment.NewLine, writer.ToString());
        }
Exemple #5
0
    public async Task MatchAsync_ConstraintRejectsEndpoint_Logging()
    {
        // Arrange
        var endpointDataSource = new DefaultEndpointDataSource(new List <Endpoint>
        {
            CreateEndpoint("/{p:int}", 0)
        });

        var sink    = new TestSink();
        var matcher = CreateDfaMatcher(endpointDataSource, loggerFactory: new TestLoggerFactory(sink, enabled: true));

        var httpContext = CreateContext();

        httpContext.Request.Path = "/One";

        // Act
        await matcher.MatchAsync(httpContext);

        // Assert
        Assert.Null(httpContext.GetEndpoint());

        Assert.Collection(
            sink.Writes,
            (log) =>
        {
            Assert.Equal(1001, log.EventId);
            Assert.Equal("1 candidate(s) found for the request path '/One'", log.Message);
        },
            (log) =>
        {
            Assert.Equal(1003, log.EventId);
            Assert.Equal("Endpoint '/{p:int}' with route pattern '/{p:int}' was rejected by constraint 'p':'Microsoft.AspNetCore.Routing.Constraints.IntRouteConstraint' with value 'One' for the request path '/One'", log.Message);
        },
            (log) =>
        {
            Assert.Equal(1004, log.EventId);
            Assert.Equal("Endpoint '/{p:int}' with route pattern '/{p:int}' is not valid for the request path '/One'", log.Message);
        });
    }
Exemple #6
0
        public async Task MatchAsync_InvalidRouteConstraint_NoEndpointMatched()
        {
            // Arrange
            var endpointDataSource = new DefaultEndpointDataSource(new List <Endpoint>
            {
                CreateEndpoint("/{p:int}", 0)
            });

            var matcher = CreateDfaMatcher(endpointDataSource);

            var httpContext = new DefaultHttpContext();

            httpContext.Request.Path = "/One";

            var endpointFeature = new EndpointFeature();

            // Act
            await matcher.MatchAsync(httpContext, endpointFeature);

            // Assert
            Assert.Null(endpointFeature.Endpoint);
        }
        public async Task MatchAsync_ComplexSegmentRejectsEndpoint_Logging()
        {
            // Arrange
            var endpointDataSource = new DefaultEndpointDataSource(new List <Endpoint>
            {
                CreateEndpoint("/x-{id}-y", 0)
            });

            var sink    = new TestSink();
            var matcher = CreateDfaMatcher(endpointDataSource, loggerFactory: new TestLoggerFactory(sink, enabled: true));

            var httpContext = CreateContext();

            httpContext.Request.Path = "/One";

            // Act
            await matcher.MatchAsync(httpContext);

            // Assert
            Assert.Null(httpContext.GetEndpoint());

            Assert.Collection(
                sink.Writes,
                (log) =>
            {
                Assert.Equal(DfaMatcher.EventIds.CandidatesFound, log.EventId);
                Assert.Equal("1 candidate(s) found for the request path '/One'", log.Message);
            },
                (log) =>
            {
                Assert.Equal(DfaMatcher.EventIds.CandidateRejectedByComplexSegment, log.EventId);
                Assert.Equal("Endpoint '/x-{id}-y' with route pattern '/x-{id}-y' was rejected by complex segment 'x-{id}-y' for the request path '/One'", log.Message);
            },
                (log) =>
            {
                Assert.Equal(DfaMatcher.EventIds.CandidateNotValid, log.EventId);
                Assert.Equal("Endpoint '/x-{id}-y' with route pattern '/x-{id}-y' is not valid for the request path '/One'", log.Message);
            });
        }
        public async Task MatchAsync_MultipleEndpointsWithDifferentRequiredValues_EndpointMatched()
        {
            // Arrange
            var endpoint1 = CreateEndpoint(
                "{controller}/{action}/{id?}",
                0,
                requiredValues: new { controller = "Home", action = "Index", area = (string)null, page = (string)null });
            var endpoint2 = CreateEndpoint(
                "{controller}/{action}/{id?}",
                0,
                requiredValues: new { controller = "Login", action = "Index", area = (string)null, page = (string)null });

            var dataSource = new DefaultEndpointDataSource(new List <Endpoint>
            {
                endpoint1,
                endpoint2
            });

            var matcher = CreateDfaMatcher(dataSource);

            var httpContext = CreateContext();

            httpContext.Request.Path = "/Home/Index/123";

            // Act 1
            await matcher.MatchAsync(httpContext);

            // Assert 1
            Assert.Same(endpoint1, httpContext.GetEndpoint());

            httpContext.Request.Path = "/Login/Index/123";

            // Act 2
            await matcher.MatchAsync(httpContext);

            // Assert 2
            Assert.Same(endpoint2, httpContext.GetEndpoint());
        }
        public async Task MatchAsync_ParameterTransformer_EndpointMatched()
        {
            // Arrange
            var endpoint = CreateEndpoint(
                "ConventionalTransformerRoute/{controller:slugify}/{action=Index}/{param:slugify?}",
                0,
                requiredValues: new { controller = "ConventionalTransformer", action = "Index", area = (string)null, page = (string)null });

            var dataSource = new DefaultEndpointDataSource(new List <Endpoint>
            {
                endpoint
            });

            var matcher = CreateDfaMatcher(dataSource);

            var httpContext = CreateContext();

            httpContext.Request.Path = "/ConventionalTransformerRoute/conventional-transformer/Index";

            // Act
            await matcher.MatchAsync(httpContext);

            // Assert
            Assert.Same(endpoint, httpContext.GetEndpoint());

            Assert.Collection(
                httpContext.Request.RouteValues.OrderBy(kvp => kvp.Key),
                (kvp) =>
            {
                Assert.Equal("action", kvp.Key);
                Assert.Equal("Index", kvp.Value);
            },
                (kvp) =>
            {
                Assert.Equal("controller", kvp.Key);
                Assert.Equal("ConventionalTransformer", kvp.Value);
            });
        }
        public async Task MatchAsync_DifferentDefaultCase_RouteValueUsesDefaultCase()
        {
            // Arrange
            var endpoint = CreateEndpoint(
                "{controller}/{action=TESTACTION}/{id?}",
                0,
                requiredValues: new { controller = "TestController", action = "TestAction" });

            var dataSource = new DefaultEndpointDataSource(new List <Endpoint>
            {
                endpoint
            });

            var matcher = CreateDfaMatcher(dataSource);

            var httpContext = CreateContext();

            httpContext.Request.Path = "/TestController";

            // Act
            await matcher.MatchAsync(httpContext);

            // Assert
            Assert.Same(endpoint, httpContext.GetEndpoint());

            Assert.Collection(
                httpContext.Request.RouteValues.OrderBy(kvp => kvp.Key),
                (kvp) =>
            {
                Assert.Equal("action", kvp.Key);
                Assert.Equal("TESTACTION", kvp.Value);
            },
                (kvp) =>
            {
                Assert.Equal("controller", kvp.Key);
                Assert.Equal("TestController", kvp.Value);
            });
        }
Exemple #11
0
        public async Task MatchAsync_DuplicateTemplatesAndDifferentOrder_LowerOrderEndpointMatched()
        {
            // Arrange
            var higherOrderEndpoint = CreateEndpoint("/Teams", 1);
            var lowerOrderEndpoint  = CreateEndpoint("/Teams", 0);

            var endpointDataSource = new DefaultEndpointDataSource(new List <Endpoint>
            {
                higherOrderEndpoint,
                lowerOrderEndpoint
            });

            var matcher = CreateDfaMatcher(endpointDataSource);

            var(httpContext, context) = CreateContext();
            httpContext.Request.Path  = "/Teams";

            // Act
            await matcher.MatchAsync(httpContext, context);

            // Assert
            Assert.Equal(lowerOrderEndpoint, context.Endpoint);
        }
Exemple #12
0
    public void MatchAsync_ConstrainedParameter_MiddleSegment_EndpointNotMatched()
    {
        // Arrange
        var endpoint1 = CreateEndpoint("a/b/d", 0);
        var endpoint2 = CreateEndpoint("a/{param:length(2)}/c", 0);

        var dataSource = new DefaultEndpointDataSource(new List <Endpoint>
        {
            endpoint1,
            endpoint2
        });

        var matcher = (DfaMatcher)CreateDfaMatcher(dataSource).CurrentMatcher;
        var buffer  = new PathSegment[3];

        var(context, path, count) = CreateMatchingContext("/a/b/c", buffer);

        // Act
        var set = matcher.FindCandidateSet(context, path, buffer.AsSpan().Slice(0, count));

        // Assert
        // We expect no candidates here since we trimmed the branch (a -> b -> c = (a/{param:length(2)}/c)) for the parameter based on `b` not meeting the length(2) constraint.
        Assert.Empty(set.candidates);
    }
        public async Task MatchAsync_RequireValuesAndDifferentPath_NoEndpointMatched()
        {
            // Arrange
            var endpoint = CreateEndpoint(
                "{controller}/{action}",
                0,
                requiredValues: new { controller = "Home", action = "Index", area = (string)null, page = (string)null });

            var dataSource = new DefaultEndpointDataSource(new List <Endpoint>
            {
                endpoint
            });

            var matcher = CreateDfaMatcher(dataSource);

            var(httpContext, context) = CreateContext();
            httpContext.Request.Path  = "/Login/Index";

            // Act
            await matcher.MatchAsync(httpContext, context);

            // Assert
            Assert.Null(context.Endpoint);
        }
Exemple #14
0
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddRouting();

            var endpointDataSource = new DefaultEndpointDataSource(new[]
            {
                new MatcherEndpoint(
                    invoker: (next) => (httpContext) =>
                {
                    var response           = httpContext.Response;
                    var payloadLength      = _helloWorldPayload.Length;
                    response.StatusCode    = 200;
                    response.ContentType   = "text/plain";
                    response.ContentLength = payloadLength;
                    return(response.Body.WriteAsync(_helloWorldPayload, 0, payloadLength));
                },
                    routePattern: RoutePatternFactory.Parse("/plaintext"),
                    order: 0,
                    metadata: EndpointMetadataCollection.Empty,
                    displayName: "Plaintext"),
            });

            services.TryAddEnumerable(ServiceDescriptor.Singleton <EndpointDataSource>(endpointDataSource));
        }
        public async Task MatchAsync_RunsApplicableEndpointSelectorPolicies()
        {
            // Arrange
            var dataSource = new DefaultEndpointDataSource(new List <Endpoint>
            {
                CreateEndpoint("/test/{id:alpha}", 0),
                CreateEndpoint("/test/{id:int}", 0),
                CreateEndpoint("/test/{id}", 0),
            });

            var policy = new Mock <MatcherPolicy>();

            policy
            .As <IEndpointSelectorPolicy>()
            .Setup(p => p.AppliesToEndpoints(It.IsAny <IReadOnlyList <Endpoint> >())).Returns(true);
            policy
            .As <IEndpointSelectorPolicy>()
            .Setup(p => p.ApplyAsync(It.IsAny <HttpContext>(), It.IsAny <CandidateSet>()))
            .Returns <HttpContext, CandidateSet>((c, cs) =>
            {
                cs.SetValidity(1, false);
                return(Task.CompletedTask);
            });

            var matcher = CreateDfaMatcher(dataSource, policies: new[] { policy.Object, });

            var httpContext = CreateContext();

            httpContext.Request.Path = "/test/17";

            // Act
            await matcher.MatchAsync(httpContext);

            // Assert
            Assert.Same(dataSource.Endpoints[2], httpContext.GetEndpoint());
        }
        public DynamicControllerEndpointMatcherPolicyTest()
        {
            var dataSourceKey = new ControllerEndpointDataSourceIdMetadata(1);
            var actions       = new ActionDescriptor[]
            {
                new ControllerActionDescriptor()
                {
                    RouteValues = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase)
                    {
                        ["action"]     = "Index",
                        ["controller"] = "Home",
                    },
                },
                new ControllerActionDescriptor()
                {
                    RouteValues = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase)
                    {
                        ["action"]     = "About",
                        ["controller"] = "Home",
                    },
                },
                new ControllerActionDescriptor()
                {
                    RouteValues = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase)
                    {
                        ["action"]     = "Index",
                        ["controller"] = "Blog",
                    },
                }
            };

            ControllerEndpoints = new[]
            {
                new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(actions[0]), "Test1"),
                new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(actions[1]), "Test2"),
                new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(actions[2]), "Test3"),
            };

            DynamicEndpoint = new Endpoint(
                _ => Task.CompletedTask,
                new EndpointMetadataCollection(new object[]
            {
                new DynamicControllerRouteValueTransformerMetadata(typeof(CustomTransformer), State),
                dataSourceKey
            }),
                "dynamic");

            DataSource = new DefaultEndpointDataSource(ControllerEndpoints);

            SelectorCache = new TestDynamicControllerEndpointSelectorCache(DataSource, 1);

            var services = new ServiceCollection();

            services.AddRouting();
            services.AddTransient <CustomTransformer>(s =>
            {
                var transformer       = new CustomTransformer();
                transformer.Transform = (c, values, state) => Transform(c, values, state);
                transformer.Filter    = (c, values, state, candidates) => Filter(c, values, state, candidates);
                return(transformer);
            });
            Services = services.BuildServiceProvider();

            Comparer = Services.GetRequiredService <EndpointMetadataComparer>();
        }
Exemple #17
0
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient <EndsWithStringRouteConstraint>();

            services.AddRouting(options =>
            {
                options.ConstraintMap.Add("endsWith", typeof(EndsWithStringRouteConstraint));
            });

            var endpointDataSource = new DefaultEndpointDataSource(new[]
            {
                new RouteEndpoint((httpContext) =>
                {
                    var response           = httpContext.Response;
                    var payloadLength      = _homePayload.Length;
                    response.StatusCode    = 200;
                    response.ContentType   = "text/plain";
                    response.ContentLength = payloadLength;
                    return(response.Body.WriteAsync(_homePayload, 0, payloadLength));
                },
                                  RoutePatternFactory.Parse("/"),
                                  0,
                                  EndpointMetadataCollection.Empty,
                                  "Home"),
                new RouteEndpoint((httpContext) =>
                {
                    var response           = httpContext.Response;
                    var payloadLength      = _helloWorldPayload.Length;
                    response.StatusCode    = 200;
                    response.ContentType   = "text/plain";
                    response.ContentLength = payloadLength;
                    return(response.Body.WriteAsync(_helloWorldPayload, 0, payloadLength));
                },
                                  RoutePatternFactory.Parse("/plaintext"),
                                  0,
                                  EndpointMetadataCollection.Empty,
                                  "Plaintext"),
                new RouteEndpoint((httpContext) =>
                {
                    var response         = httpContext.Response;
                    response.StatusCode  = 200;
                    response.ContentType = "text/plain";
                    return(response.WriteAsync("WithConstraints"));
                },
                                  RoutePatternFactory.Parse("/withconstraints/{id:endsWith(_001)}"),
                                  0,
                                  EndpointMetadataCollection.Empty,
                                  "withconstraints"),
                new RouteEndpoint((httpContext) =>
                {
                    var response         = httpContext.Response;
                    response.StatusCode  = 200;
                    response.ContentType = "text/plain";
                    return(response.WriteAsync("withoptionalconstraints"));
                },
                                  RoutePatternFactory.Parse("/withoptionalconstraints/{id:endsWith(_001)?}"),
                                  0,
                                  EndpointMetadataCollection.Empty,
                                  "withoptionalconstraints"),
                new RouteEndpoint((httpContext) =>
                {
                    using (var writer = new StreamWriter(httpContext.Response.Body, Encoding.UTF8, 1024, leaveOpen: true))
                    {
                        var graphWriter = httpContext.RequestServices.GetRequiredService <DfaGraphWriter>();
                        var dataSource  = httpContext.RequestServices.GetRequiredService <CompositeEndpointDataSource>();
                        graphWriter.Write(dataSource, writer);
                    }

                    return(Task.CompletedTask);
                },
                                  RoutePatternFactory.Parse("/graph"),
                                  0,
                                  new EndpointMetadataCollection(new HttpMethodMetadata(new[] { "GET", })),
                                  "DFA Graph"),
                new RouteEndpoint((httpContext) =>
                {
                    var linkGenerator = httpContext.RequestServices.GetRequiredService <LinkGenerator>();

                    var response         = httpContext.Response;
                    response.StatusCode  = 200;
                    response.ContentType = "text/plain";
                    return(response.WriteAsync(
                               "Link: " + linkGenerator.GetPathByRouteValues(httpContext, "WithSingleAsteriskCatchAll", new { })));
                },
                                  RoutePatternFactory.Parse("/WithSingleAsteriskCatchAll/{*path}"),
                                  0,
                                  new EndpointMetadataCollection(
                                      new RouteValuesAddressMetadata(
                                          routeName: "WithSingleAsteriskCatchAll",
                                          requiredValues: new RouteValueDictionary())),
                                  "WithSingleAsteriskCatchAll"),
                new RouteEndpoint((httpContext) =>
                {
                    var linkGenerator = httpContext.RequestServices.GetRequiredService <LinkGenerator>();

                    var response         = httpContext.Response;
                    response.StatusCode  = 200;
                    response.ContentType = "text/plain";
                    return(response.WriteAsync(
                               "Link: " + linkGenerator.GetPathByRouteValues(httpContext, "WithDoubleAsteriskCatchAll", new { })));
                },
                                  RoutePatternFactory.Parse("/WithDoubleAsteriskCatchAll/{**path}"),
                                  0,
                                  new EndpointMetadataCollection(
                                      new RouteValuesAddressMetadata(
                                          routeName: "WithDoubleAsteriskCatchAll",
                                          requiredValues: new RouteValueDictionary())),
                                  "WithDoubleAsteriskCatchAll"),
            });

            services.TryAddEnumerable(ServiceDescriptor.Singleton <EndpointDataSource>(endpointDataSource));
        }
        public DynamicPageEndpointMatcherPolicyTest()
        {
            var actions = new ActionDescriptor[]
            {
                new PageActionDescriptor()
                {
                    RouteValues = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase)
                    {
                        ["page"] = "/Index",
                    },
                    DisplayName = "/Index",
                },
                new PageActionDescriptor()
                {
                    RouteValues = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase)
                    {
                        ["page"] = "/About",
                    },
                    DisplayName = "/About"
                },
            };

            PageEndpoints = new[]
            {
                new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(actions[0]), "Test1"),
                new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(actions[1]), "Test2"),
            };

            DynamicEndpoint = new Endpoint(
                _ => Task.CompletedTask,
                new EndpointMetadataCollection(new object[]
            {
                new DynamicPageRouteValueTransformerMetadata(typeof(CustomTransformer), State),
                new PageEndpointDataSourceIdMetadata(1),
            }),
                "dynamic");

            DataSource = new DefaultEndpointDataSource(PageEndpoints);

            SelectorCache = new TestDynamicPageEndpointSelectorCache(DataSource);

            var services = new ServiceCollection();

            services.AddRouting();
            services.AddTransient <CustomTransformer>(s =>
            {
                var transformer       = new CustomTransformer();
                transformer.Transform = (c, values, state) => Transform(c, values, state);
                transformer.Filter    = (c, values, state, endpoints) => Filter(c, values, state, endpoints);
                return(transformer);
            });
            Services = services.BuildServiceProvider();

            Comparer = Services.GetRequiredService <EndpointMetadataComparer>();

            LoadedEndpoints = new[]
            {
                new Endpoint(_ => Task.CompletedTask, EndpointMetadataCollection.Empty, "Test1"),
                new Endpoint(_ => Task.CompletedTask, EndpointMetadataCollection.Empty, "Test2"),
                new Endpoint(_ => Task.CompletedTask, EndpointMetadataCollection.Empty, "ReplacedLoaded")
            };

            var loader = new Mock <PageLoader>();

            loader
            .Setup(l => l.LoadAsync(It.IsAny <PageActionDescriptor>(), It.IsAny <EndpointMetadataCollection>()))
            .Returns((PageActionDescriptor descriptor, EndpointMetadataCollection endpoint) => Task.FromResult(new CompiledPageActionDescriptor
            {
                Endpoint = descriptor.DisplayName switch
                {
                    "/Index" => LoadedEndpoints[0],
                    "/About" => LoadedEndpoints[1],
                    "/ReplacedEndpoint" => LoadedEndpoints[2],
                    _ => throw new InvalidOperationException($"Invalid endpoint '{descriptor.DisplayName}'.")
                }
            }));
        public DynamicPageEndpointMatcherPolicyTest()
        {
            var actions = new ActionDescriptor[]
            {
                new PageActionDescriptor()
                {
                    RouteValues = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase)
                    {
                        ["page"] = "/Index",
                    },
                },
                new PageActionDescriptor()
                {
                    RouteValues = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase)
                    {
                        ["page"] = "/About",
                    },
                },
            };

            PageEndpoints = new[]
            {
                new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(actions[0]), "Test1"),
                new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(actions[1]), "Test2"),
            };

            DynamicEndpoint = new Endpoint(
                _ => Task.CompletedTask,
                new EndpointMetadataCollection(new object[]
            {
                new DynamicPageRouteValueTransformerMetadata(typeof(CustomTransformer)),
            }),
                "dynamic");

            DataSource = new DefaultEndpointDataSource(PageEndpoints);

            Selector = new TestDynamicPageEndpointSelector(DataSource);

            var services = new ServiceCollection();

            services.AddRouting();
            services.AddScoped <CustomTransformer>(s =>
            {
                var transformer       = new CustomTransformer();
                transformer.Transform = (c, values) => Transform(c, values);
                return(transformer);
            });
            Services = services.BuildServiceProvider();

            Comparer = Services.GetRequiredService <EndpointMetadataComparer>();

            LoadedEndpoint = new Endpoint(_ => Task.CompletedTask, EndpointMetadataCollection.Empty, "Loaded");

            var loader = new Mock <PageLoader>();

            loader
            .Setup(l => l.LoadAsync(It.IsAny <PageActionDescriptor>()))
            .Returns(Task.FromResult(new CompiledPageActionDescriptor()
            {
                Endpoint = LoadedEndpoint,
            }));
            Loader = loader.Object;
        }