Browse Source

Remove legacy routing (#31295)

Pranav K 5 years ago
parent
commit
8403d676e7
32 changed files with 22 additions and 2029 deletions
  1. 0 15
      src/Components/Components/src/Routing/IRouteTable.cs
  2. 0 45
      src/Components/Components/src/Routing/LegacyRouteMatching/LegacyOptionalTypeRouteConstraint.cs
  3. 0 113
      src/Components/Components/src/Routing/LegacyRouteMatching/LegacyRouteConstraint.cs
  4. 0 147
      src/Components/Components/src/Routing/LegacyRouteMatching/LegacyRouteEntry.cs
  5. 0 31
      src/Components/Components/src/Routing/LegacyRouteMatching/LegacyRouteTable.cs
  6. 0 236
      src/Components/Components/src/Routing/LegacyRouteMatching/LegacyRouteTableFactory.cs
  7. 0 29
      src/Components/Components/src/Routing/LegacyRouteMatching/LegacyRouteTemplate.cs
  8. 0 115
      src/Components/Components/src/Routing/LegacyRouteMatching/LegacyTemplateParser.cs
  9. 0 123
      src/Components/Components/src/Routing/LegacyRouteMatching/LegacyTemplateSegment.cs
  10. 0 37
      src/Components/Components/src/Routing/LegacyRouteMatching/LegacyTypeRouteConstraint.cs
  11. 1 1
      src/Components/Components/src/Routing/RouteTable.cs
  12. 3 13
      src/Components/Components/src/Routing/Router.cs
  13. 0 46
      src/Components/Components/test/LegacyRouteMatching/LegacyRouteConstraintTest.cs
  14. 0 741
      src/Components/Components/test/LegacyRouteMatching/LegacyRouteTableFactoryTests.cs
  15. 0 295
      src/Components/Components/test/LegacyRouteMatching/LegacyTemplateParserTests.cs
  16. 0 24
      src/Components/Components/test/Routing/RouterTest.cs
  17. 1 1
      src/Components/Samples/BlazorServerApp/App.razor
  18. 1 1
      src/Components/WebAssembly/Samples/HostedBlazorWebassemblyApp/Client/App.razor
  19. 1 1
      src/Components/WebAssembly/testassets/StandaloneApp/App.razor
  20. 1 1
      src/Components/WebAssembly/testassets/Wasm.Authentication.Client/App.razor
  21. 1 1
      src/Components/WebView/Samples/BlazorWinFormsApp/Main.razor
  22. 1 1
      src/Components/WebView/Samples/BlazorWpfApp/Main.razor
  23. 1 1
      src/Components/benchmarkapps/BlazingPizza.Server/App.razor
  24. 1 1
      src/Components/benchmarkapps/Wasm.Performance/TestApp/App.razor
  25. 1 1
      src/Components/test/testassets/BasicTestApp/AuthTest/AuthRouter.razor
  26. 1 1
      src/Components/test/testassets/BasicTestApp/RouterTest/TestRouter.razor
  27. 1 1
      src/Components/test/testassets/BasicTestApp/RouterTest/TestRouterWithAdditionalAssembly.razor
  28. 1 1
      src/Components/test/testassets/BasicTestApp/RouterTest/TestRouterWithLazyAssembly.razor
  29. 1 1
      src/Components/test/testassets/BasicTestApp/RouterTest/TestRouterWithOnNavigate.razor
  30. 1 1
      src/Components/test/testassets/ComponentsApp.App/App.razor
  31. 2 2
      src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/App.razor
  32. 2 2
      src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/App.razor

+ 0 - 15
src/Components/Components/src/Routing/IRouteTable.cs

@@ -1,15 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-namespace Microsoft.AspNetCore.Components.Routing
-{
-    /// <summary>
-    /// Provides an abstraction over <see cref="RouteTable"/> and <see cref="LegacyRouteMatching.LegacyRouteTable"/>.
-    /// This is only an internal implementation detail of <see cref="Router"/> and can be removed once
-    /// the legacy route matching logic is removed.
-    /// </summary>
-    internal interface IRouteTable
-    {
-        void Route(RouteContext routeContext);
-    }
-}

+ 0 - 45
src/Components/Components/src/Routing/LegacyRouteMatching/LegacyOptionalTypeRouteConstraint.cs

@@ -1,45 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-namespace Microsoft.AspNetCore.Components.LegacyRouteMatching
-{
-    /// <summary>
-    /// A route constraint that allows the value to be null or parseable as the specified
-    /// type.
-    /// </summary>
-    /// <typeparam name="T">The type to which the value must be parseable.</typeparam>
-    internal class LegacyOptionalTypeRouteConstraint<T> : LegacyRouteConstraint
-    {
-        public delegate bool LegacyTryParseDelegate(string str, out T result);
-
-        private readonly LegacyTryParseDelegate _parser;
-
-        public LegacyOptionalTypeRouteConstraint(LegacyTryParseDelegate parser)
-        {
-            _parser = parser;
-        }
-
-        public override bool Match(string pathSegment, out object? convertedValue)
-        {
-            // Unset values are set to null in the Parameters object created in
-            // the RouteContext. To match this pattern, unset optional parameters
-            // are converted to null.
-            if (string.IsNullOrEmpty(pathSegment))
-            {
-                convertedValue = null;
-                return true;
-            }
-
-            if (_parser(pathSegment, out var result))
-            {
-                convertedValue = result;
-                return true;
-            }
-            else
-            {
-                convertedValue = null;
-                return false;
-            }
-        }
-    }
-}

+ 0 - 113
src/Components/Components/src/Routing/LegacyRouteMatching/LegacyRouteConstraint.cs

@@ -1,113 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-using System;
-using System.Collections.Concurrent;
-using System.Globalization;
-
-namespace Microsoft.AspNetCore.Components.LegacyRouteMatching
-{
-    internal abstract class LegacyRouteConstraint
-    {
-        // note: the things that prevent this cache from growing unbounded is that
-        // we're the only caller to this code path, and the fact that there are only
-        // 8 possible instances that we create.
-        //
-        // The values passed in here for parsing are always static text defined in route attributes.
-        private static readonly ConcurrentDictionary<string, LegacyRouteConstraint> _cachedConstraints
-            = new ConcurrentDictionary<string, LegacyRouteConstraint>();
-
-        public abstract bool Match(string pathSegment, out object? convertedValue);
-
-        public static LegacyRouteConstraint Parse(string template, string segment, string constraint)
-        {
-            if (string.IsNullOrEmpty(constraint))
-            {
-                throw new ArgumentException($"Malformed segment '{segment}' in route '{template}' contains an empty constraint.");
-            }
-
-            if (_cachedConstraints.TryGetValue(constraint, out var cachedInstance))
-            {
-                return cachedInstance;
-            }
-            else
-            {
-                var newInstance = CreateRouteConstraint(constraint);
-                if (newInstance != null)
-                {
-                    // We've done to the work to create the constraint now, but it's possible
-                    // we're competing with another thread. GetOrAdd can ensure only a single
-                    // instance is returned so that any extra ones can be GC'ed.
-                    return _cachedConstraints.GetOrAdd(constraint, newInstance);
-                }
-                else
-                {
-                    throw new ArgumentException($"Unsupported constraint '{constraint}' in route '{template}'.");
-                }
-            }
-        }
-
-        /// <summary>
-        /// Creates a structured RouteConstraint object given a string that contains
-        /// the route constraint. A constraint is the place after the colon in a
-        /// parameter definition, for example `{age:int?}`.
-        ///
-        /// If the constraint denotes an optional, this method will return an
-        /// <see cref="LegacyOptionalTypeRouteConstraint{T}" /> which handles the appropriate checks.
-        /// </summary>
-        /// <param name="constraint">String representation of the constraint</param>
-        /// <returns>Type-specific RouteConstraint object</returns>
-        private static LegacyRouteConstraint? CreateRouteConstraint(string constraint)
-        {
-            switch (constraint)
-            {
-                case "bool":
-                    return new LegacyTypeRouteConstraint<bool>(bool.TryParse);
-                case "bool?":
-                    return new LegacyOptionalTypeRouteConstraint<bool>(bool.TryParse);
-                case "datetime":
-                    return new LegacyTypeRouteConstraint<DateTime>((string str, out DateTime result)
-                        => DateTime.TryParse(str, CultureInfo.InvariantCulture, DateTimeStyles.None, out result));
-                case "datetime?":
-                    return new LegacyOptionalTypeRouteConstraint<DateTime>((string str, out DateTime result)
-                        => DateTime.TryParse(str, CultureInfo.InvariantCulture, DateTimeStyles.None, out result));
-                case "decimal":
-                    return new LegacyTypeRouteConstraint<decimal>((string str, out decimal result)
-                        => decimal.TryParse(str, NumberStyles.Number, CultureInfo.InvariantCulture, out result));
-                case "decimal?":
-                    return new LegacyOptionalTypeRouteConstraint<decimal>((string str, out decimal result)
-                        => decimal.TryParse(str, NumberStyles.Number, CultureInfo.InvariantCulture, out result));
-                case "double":
-                    return new LegacyTypeRouteConstraint<double>((string str, out double result)
-                        => double.TryParse(str, NumberStyles.Number, CultureInfo.InvariantCulture, out result));
-                case "double?":
-                    return new LegacyOptionalTypeRouteConstraint<double>((string str, out double result)
-                        => double.TryParse(str, NumberStyles.Number, CultureInfo.InvariantCulture, out result));
-                case "float":
-                    return new LegacyTypeRouteConstraint<float>((string str, out float result)
-                        => float.TryParse(str, NumberStyles.Number, CultureInfo.InvariantCulture, out result));
-                case "float?":
-                    return new LegacyOptionalTypeRouteConstraint<float>((string str, out float result)
-                        => float.TryParse(str, NumberStyles.Number, CultureInfo.InvariantCulture, out result));
-                case "guid":
-                    return new LegacyTypeRouteConstraint<Guid>(Guid.TryParse);
-                case "guid?":
-                    return new LegacyOptionalTypeRouteConstraint<Guid>(Guid.TryParse);
-                case "int":
-                    return new LegacyTypeRouteConstraint<int>((string str, out int result)
-                        => int.TryParse(str, NumberStyles.Integer, CultureInfo.InvariantCulture, out result));
-                case "int?":
-                    return new LegacyOptionalTypeRouteConstraint<int>((string str, out int result)
-                        => int.TryParse(str, NumberStyles.Integer, CultureInfo.InvariantCulture, out result));
-                case "long":
-                    return new LegacyTypeRouteConstraint<long>((string str, out long result)
-                        => long.TryParse(str, NumberStyles.Integer, CultureInfo.InvariantCulture, out result));
-                case "long?":
-                    return new LegacyOptionalTypeRouteConstraint<long>((string str, out long result)
-                        => long.TryParse(str, NumberStyles.Integer, CultureInfo.InvariantCulture, out result));
-                default:
-                    return null;
-            }
-        }
-    }
-}

+ 0 - 147
src/Components/Components/src/Routing/LegacyRouteMatching/LegacyRouteEntry.cs

@@ -1,147 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-#nullable disable warnings
-
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-
-// Avoid referencing the whole Microsoft.AspNetCore.Components.Routing namespace to
-// avoid the risk of accidentally relying on the non-legacy types in the legacy fork
-using RouteContext = Microsoft.AspNetCore.Components.Routing.RouteContext;
-
-namespace Microsoft.AspNetCore.Components.LegacyRouteMatching
-{
-    [DebuggerDisplay("Handler = {Handler}, Template = {Template}")]
-    internal class LegacyRouteEntry
-    {
-        public LegacyRouteEntry(LegacyRouteTemplate template, Type handler, string[] unusedRouteParameterNames)
-        {
-            Template = template;
-            UnusedRouteParameterNames = unusedRouteParameterNames;
-            Handler = handler;
-        }
-
-        public LegacyRouteTemplate Template { get; }
-
-        public string[] UnusedRouteParameterNames { get; }
-
-        public Type Handler { get; }
-
-        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)
-            {
-                int startIndex = Template.Segments.Length - 1;
-                catchAllValue = string.Join('/', context.Segments, startIndex, context.Segments.Length - startIndex);
-            }
-            // 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;
-            }
-        }
-    }
-}

+ 0 - 31
src/Components/Components/src/Routing/LegacyRouteMatching/LegacyRouteTable.cs

@@ -1,31 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-// Avoid referencing the whole Microsoft.AspNetCore.Components.Routing namespace to
-// avoid the risk of accidentally relying on the non-legacy types in the legacy fork
-using RouteContext = Microsoft.AspNetCore.Components.Routing.RouteContext;
-
-namespace Microsoft.AspNetCore.Components.LegacyRouteMatching
-{
-    internal class LegacyRouteTable : Routing.IRouteTable
-    {
-        public LegacyRouteTable(LegacyRouteEntry[] routes)
-        {
-            Routes = routes;
-        }
-
-        public LegacyRouteEntry[] Routes { get; }
-
-        public void Route(RouteContext routeContext)
-        {
-            for (var i = 0; i < Routes.Length; i++)
-            {
-                Routes[i].Match(routeContext);
-                if (routeContext.Handler != null)
-                {
-                    return;
-                }
-            }
-        }
-    }
-}

+ 0 - 236
src/Components/Components/src/Routing/LegacyRouteMatching/LegacyRouteTableFactory.cs

@@ -1,236 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
-
-namespace Microsoft.AspNetCore.Components.LegacyRouteMatching
-{
-    /// <summary>
-    /// Resolves components for an application.
-    /// </summary>
-    internal static class LegacyRouteTableFactory
-    {
-        private static readonly ConcurrentDictionary<Key, LegacyRouteTable> Cache =
-            new ConcurrentDictionary<Key, LegacyRouteTable>();
-        public static readonly IComparer<LegacyRouteEntry> RoutePrecedence = Comparer<LegacyRouteEntry>.Create(RouteComparison);
-
-        public static LegacyRouteTable Create(IEnumerable<Assembly> assemblies)
-        {
-            var key = new Key(assemblies.OrderBy(a => a.FullName).ToArray());
-            if (Cache.TryGetValue(key, out var resolvedComponents))
-            {
-                return resolvedComponents;
-            }
-
-            var componentTypes = key.Assemblies.SelectMany(a => a.ExportedTypes.Where(t => typeof(IComponent).IsAssignableFrom(t)));
-            var routeTable = Create(componentTypes);
-            Cache.TryAdd(key, routeTable);
-            return routeTable;
-        }
-
-        internal static LegacyRouteTable Create(IEnumerable<Type> componentTypes)
-        {
-            var templatesByHandler = new Dictionary<Type, string[]>();
-            foreach (var componentType in componentTypes)
-            {
-                // We're deliberately using inherit = false here.
-                //
-                // RouteAttribute is defined as non-inherited, because inheriting a route attribute always causes an
-                // ambiguity. You end up with two components (base class and derived class) with the same route.
-                var routeAttributes = componentType.GetCustomAttributes<RouteAttribute>(inherit: false);
-
-                var templates = routeAttributes.Select(t => t.Template).ToArray();
-                templatesByHandler.Add(componentType, templates);
-            }
-            return Create(templatesByHandler);
-        }
-
-        internal static LegacyRouteTable Create(Dictionary<Type, string[]> templatesByHandler)
-        {
-            var routes = new List<LegacyRouteEntry>();
-            foreach (var keyValuePair in templatesByHandler)
-            {
-                var parsedTemplates = keyValuePair.Value.Select(v => LegacyTemplateParser.ParseTemplate(v)).ToArray();
-                var allRouteParameterNames = parsedTemplates
-                    .SelectMany(GetParameterNames)
-                    .Distinct(StringComparer.OrdinalIgnoreCase)
-                    .ToArray();
-
-                foreach (var parsedTemplate in parsedTemplates)
-                {
-                    var unusedRouteParameterNames = allRouteParameterNames
-                        .Except(GetParameterNames(parsedTemplate), StringComparer.OrdinalIgnoreCase)
-                        .ToArray();
-                    var entry = new LegacyRouteEntry(parsedTemplate, keyValuePair.Key, unusedRouteParameterNames);
-                    routes.Add(entry);
-                }
-            }
-
-            return new LegacyRouteTable(routes.OrderBy(id => id, RoutePrecedence).ToArray());
-        }
-
-        private static string[] GetParameterNames(LegacyRouteTemplate routeTemplate)
-        {
-            return routeTemplate.Segments
-                .Where(s => s.IsParameter)
-                .Select(s => s.Value)
-                .ToArray();
-        }
-
-        /// <summary>
-        /// Route precedence algorithm.
-        /// We collect all the routes and sort them from most specific to
-        /// less specific. The specificity of a route is given by the specificity
-        /// of its segments and the position of those segments in the route.
-        /// * A literal segment is more specific than a parameter segment.
-        /// * A parameter segment with more constraints is more specific than one with fewer constraints
-        /// * Segment earlier in the route are evaluated before segments later in the route.
-        /// For example:
-        /// /Literal is more specific than /Parameter
-        /// /Route/With/{parameter} is more specific than /{multiple}/With/{parameters}
-        /// /Product/{id:int} is more specific than /Product/{id}
-        ///
-        /// Routes can be ambiguous if:
-        /// They are composed of literals and those literals have the same values (case insensitive)
-        /// They are composed of a mix of literals and parameters, in the same relative order and the
-        /// literals have the same values.
-        /// For example:
-        /// * /literal and /Literal
-        /// /{parameter}/literal and /{something}/literal
-        /// /{parameter:constraint}/literal and /{something:constraint}/literal
-        ///
-        /// To calculate the precedence we sort the list of routes as follows:
-        /// * Shorter routes go first.
-        /// * A literal wins over a parameter in precedence.
-        /// * For literals with different values (case insensitive) we choose the lexical order
-        /// * For parameters with different numbers of constraints, the one with more wins
-        /// If we get to the end of the comparison routing we've detected an ambiguous pair of routes.
-        /// </summary>
-        internal static int RouteComparison(LegacyRouteEntry x, LegacyRouteEntry y)
-        {
-            if (ReferenceEquals(x, y))
-            {
-                return 0;
-            }
-
-            var xTemplate = x.Template;
-            var yTemplate = y.Template;
-            if (xTemplate.Segments.Length != y.Template.Segments.Length)
-            {
-                return xTemplate.Segments.Length < y.Template.Segments.Length ? -1 : 1;
-            }
-            else
-            {
-                for (var i = 0; i < xTemplate.Segments.Length; i++)
-                {
-                    var xSegment = xTemplate.Segments[i];
-                    var ySegment = yTemplate.Segments[i];
-                    if (!xSegment.IsParameter && ySegment.IsParameter)
-                    {
-                        return -1;
-                    }
-                    if (xSegment.IsParameter && !ySegment.IsParameter)
-                    {
-                        return 1;
-                    }
-
-                    if (xSegment.IsParameter)
-                    {
-                        // Always favor non-optional parameters over optional ones
-                        if (!xSegment.IsOptional && ySegment.IsOptional)
-                        {
-                            return -1;
-                        }
-
-                        if (xSegment.IsOptional && !ySegment.IsOptional)
-                        {
-                            return 1;
-                        }
-
-                        if (xSegment.Constraints.Length > ySegment.Constraints.Length)
-                        {
-                            return -1;
-                        }
-                        else if (xSegment.Constraints.Length < ySegment.Constraints.Length)
-                        {
-                            return 1;
-                        }
-                    }
-                    else
-                    {
-                        var comparison = string.Compare(xSegment.Value, ySegment.Value, StringComparison.OrdinalIgnoreCase);
-                        if (comparison != 0)
-                        {
-                            return comparison;
-                        }
-                    }
-                }
-
-                throw new InvalidOperationException($@"The following routes are ambiguous:
-'{x.Template.TemplateText}' in '{x.Handler.FullName}'
-'{y.Template.TemplateText}' in '{y.Handler.FullName}'
-");
-            }
-        }
-
-        private readonly struct Key : IEquatable<Key>
-        {
-            public readonly Assembly[] Assemblies;
-
-            public Key(Assembly[] assemblies)
-            {
-                Assemblies = assemblies;
-            }
-
-            public override bool Equals(object? obj)
-            {
-                return obj is Key other ? base.Equals(other) : false;
-            }
-
-            public bool Equals(Key other)
-            {
-                if (Assemblies == null && other.Assemblies == null)
-                {
-                    return true;
-                }
-                else if ((Assemblies == null) || (other.Assemblies == null))
-                {
-                    return false;
-                }
-                else if (Assemblies.Length != other.Assemblies.Length)
-                {
-                    return false;
-                }
-
-                for (var i = 0; i < Assemblies.Length; i++)
-                {
-                    if (!Assemblies[i].Equals(other.Assemblies[i]))
-                    {
-                        return false;
-                    }
-                }
-
-                return true;
-            }
-
-            public override int GetHashCode()
-            {
-                var hash = new HashCode();
-
-                if (Assemblies != null)
-                {
-                    for (var i = 0; i < Assemblies.Length; i++)
-                    {
-                        hash.Add(Assemblies[i]);
-                    }
-                }
-
-                return hash.ToHashCode();
-            }
-        }
-    }
-}

+ 0 - 29
src/Components/Components/src/Routing/LegacyRouteMatching/LegacyRouteTemplate.cs

@@ -1,29 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-
-using System.Diagnostics;
-using System.Linq;
-
-namespace Microsoft.AspNetCore.Components.LegacyRouteMatching
-{
-    [DebuggerDisplay("{TemplateText}")]
-    internal class LegacyRouteTemplate
-    {
-        public LegacyRouteTemplate(string templateText, LegacyTemplateSegment[] segments)
-        {
-            TemplateText = templateText;
-            Segments = segments;
-            OptionalSegmentsCount = segments.Count(template => template.IsOptional);
-            ContainsCatchAllSegment = segments.Any(template => template.IsCatchAll);
-        }
-
-        public string TemplateText { get; }
-
-        public LegacyTemplateSegment[] Segments { get; }
-
-        public int OptionalSegmentsCount { get; }
-
-        public bool ContainsCatchAllSegment { get; }
-    }
-}

+ 0 - 115
src/Components/Components/src/Routing/LegacyRouteMatching/LegacyTemplateParser.cs

@@ -1,115 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-using System;
-
-namespace Microsoft.AspNetCore.Components.LegacyRouteMatching
-{
-    // This implementation is temporary, in the future we'll want to have
-    // a more performant/properly designed routing set of abstractions.
-    // To be more precise these are some things we are scoping out:
-    // * We are not doing link generation.
-    // * We are not supporting all the route constraint formats supported by ASP.NET server-side routing.
-    // The class in here just takes care of parsing a route and extracting
-    // simple parameters from it.
-    // Some differences with ASP.NET Core routes are:
-    // * We don't support complex segments.
-    // The things that we support are:
-    // * Literal path segments. (Like /Path/To/Some/Page)
-    // * Parameter path segments (Like /Customer/{Id}/Orders/{OrderId})
-    // * Catch-all parameters (Like /blog/{*slug})
-    internal class LegacyTemplateParser
-    {
-        public static readonly char[] InvalidParameterNameCharacters =
-            new char[] { '{', '}', '=', '.' };
-
-        internal static LegacyRouteTemplate ParseTemplate(string template)
-        {
-            var originalTemplate = template;
-            template = template.Trim('/');
-            if (template == string.Empty)
-            {
-                // Special case "/";
-                return new LegacyRouteTemplate("/", Array.Empty<LegacyTemplateSegment>());
-            }
-
-            var segments = template.Split('/');
-            var templateSegments = new LegacyTemplateSegment[segments.Length];
-            for (int i = 0; i < segments.Length; i++)
-            {
-                var segment = segments[i];
-                if (string.IsNullOrEmpty(segment))
-                {
-                    throw new InvalidOperationException(
-                        $"Invalid template '{template}'. Empty segments are not allowed.");
-                }
-
-                if (segment[0] != '{')
-                {
-                    if (segment[segment.Length - 1] == '}')
-                    {
-                        throw new InvalidOperationException(
-                            $"Invalid template '{template}'. Missing '{{' in parameter segment '{segment}'.");
-                    }
-                    templateSegments[i] = new LegacyTemplateSegment(originalTemplate, segment, isParameter: false);
-                }
-                else
-                {
-                    if (segment[segment.Length - 1] != '}')
-                    {
-                        throw new InvalidOperationException(
-                            $"Invalid template '{template}'. Missing '}}' in parameter segment '{segment}'.");
-                    }
-
-                    if (segment.Length < 3)
-                    {
-                        throw new InvalidOperationException(
-                            $"Invalid template '{template}'. Empty parameter name in segment '{segment}' is not allowed.");
-                    }
-
-                    var invalidCharacter = segment.IndexOfAny(InvalidParameterNameCharacters, 1, segment.Length - 2);
-                    if (invalidCharacter != -1)
-                    {
-                        throw new InvalidOperationException(
-                            $"Invalid template '{template}'. The character '{segment[invalidCharacter]}' in parameter segment '{segment}' is not allowed.");
-                    }
-
-                    templateSegments[i] = new LegacyTemplateSegment(originalTemplate, segment.Substring(1, segment.Length - 2), isParameter: true);
-                }
-            }
-
-            for (int i = 0; i < templateSegments.Length; i++)
-            {
-                var currentSegment = templateSegments[i];
-
-                if (currentSegment.IsCatchAll && i != templateSegments.Length - 1)
-                {
-                    throw new InvalidOperationException($"Invalid template '{template}'. A catch-all parameter can only appear as the last segment of the route template.");
-                }
-
-                if (!currentSegment.IsParameter)
-                {
-                    continue;
-                }
-
-                for (int j = i + 1; j < templateSegments.Length; j++)
-                {
-                    var nextSegment = templateSegments[j];
-
-                    if (currentSegment.IsOptional && !nextSegment.IsOptional)
-                    {
-                        throw new InvalidOperationException($"Invalid template '{template}'. Non-optional parameters or literal routes cannot appear after optional parameters.");
-                    }
-
-                    if (string.Equals(currentSegment.Value, nextSegment.Value, StringComparison.OrdinalIgnoreCase))
-                    {
-                        throw new InvalidOperationException(
-                            $"Invalid template '{template}'. The parameter '{currentSegment}' appears multiple times.");
-                    }
-                }
-            }
-
-            return new LegacyRouteTemplate(template, templateSegments);
-        }
-    }
-}

+ 0 - 123
src/Components/Components/src/Routing/LegacyRouteMatching/LegacyTemplateSegment.cs

@@ -1,123 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-using System;
-using System.Linq;
-
-namespace Microsoft.AspNetCore.Components.LegacyRouteMatching
-{
-    internal class LegacyTemplateSegment
-    {
-        public LegacyTemplateSegment(string template, string segment, bool isParameter)
-        {
-            IsParameter = isParameter;
-
-            IsCatchAll = segment.StartsWith('*');
-
-            if (IsCatchAll)
-            {
-                // Only one '*' currently allowed
-                Value = segment.Substring(1);
-
-                var invalidCharacter = Value.IndexOf('*');
-                if (Value.IndexOf('*') != -1)
-                {
-                    throw new InvalidOperationException($"Invalid template '{template}'. A catch-all parameter may only have one '*' at the beginning of the segment.");
-                }
-            }
-            else
-            {
-                Value = segment;
-            }
-
-            // Process segments that are not parameters or do not contain
-            // a token separating a type constraint.
-            if (!isParameter || Value.IndexOf(':') < 0)
-            {
-                // Set the IsOptional flag to true for segments that contain
-                // a parameter with no type constraints but optionality set
-                // via the '?' token.
-                if (Value.IndexOf('?') == Value.Length - 1)
-                {
-                    IsOptional = true;
-                    Value = Value.Substring(0, Value.Length - 1);
-                }
-                // If the `?` optional marker shows up in the segment but not at the very end,
-                // then throw an error.
-                else if (Value.IndexOf('?') >= 0 && Value.IndexOf('?') != Value.Length - 1)
-                {
-                    throw new ArgumentException($"Malformed parameter '{segment}' in route '{template}'. '?' character can only appear at the end of parameter name.");
-                }
-
-                Constraints = Array.Empty<LegacyRouteConstraint>();
-            }
-            else
-            {
-                var tokens = Value.Split(':');
-                if (tokens[0].Length == 0)
-                {
-                    throw new ArgumentException($"Malformed parameter '{segment}' in route '{template}' has no name before the constraints list.");
-                }
-
-                // Set the IsOptional flag to true if any type constraints
-                // for this parameter are designated as optional.
-                IsOptional = tokens.Skip(1).Any(token => token.EndsWith('?'));
-
-                Value = tokens[0];
-                Constraints = tokens.Skip(1)
-                    .Select(token => LegacyRouteConstraint.Parse(template, segment, token))
-                    .ToArray();
-            }
-
-            if (IsParameter)
-            {
-                if (IsOptional && IsCatchAll)
-                {
-                    throw new InvalidOperationException($"Invalid segment '{segment}' in route '{template}'. A catch-all parameter cannot be marked optional.");
-                }
-
-                // Moving the check for this here instead of TemplateParser so we can allow catch-all.
-                // We checked for '*' up above specifically for catch-all segments, this one checks for all others
-                if (Value.IndexOf('*') != -1)
-                {
-                    throw new InvalidOperationException($"Invalid template '{template}'. The character '*' in parameter segment '{{{segment}}}' is not allowed.");
-                }
-            }
-        }
-
-        // The value of the segment. The exact text to match when is a literal.
-        // The parameter name when its a segment
-        public string Value { get; }
-
-        public bool IsParameter { get; }
-
-        public bool IsOptional { get;  }
-
-        public bool IsCatchAll { get; }
-
-        public LegacyRouteConstraint[] Constraints { get; }
-
-        public bool Match(string pathSegment, out object? matchedParameterValue)
-        {
-            if (IsParameter)
-            {
-                matchedParameterValue = pathSegment;
-
-                foreach (var constraint in Constraints)
-                {
-                    if (!constraint.Match(pathSegment, out matchedParameterValue))
-                    {
-                        return false;
-                    }
-                }
-
-                return true;
-            }
-            else
-            {
-                matchedParameterValue = null;
-                return string.Equals(Value, pathSegment, StringComparison.OrdinalIgnoreCase);
-            }
-        }
-    }
-}

+ 0 - 37
src/Components/Components/src/Routing/LegacyRouteMatching/LegacyTypeRouteConstraint.cs

@@ -1,37 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-using System.Diagnostics.CodeAnalysis;
-
-namespace Microsoft.AspNetCore.Components.LegacyRouteMatching
-{
-    /// <summary>
-    /// A route constraint that requires the value to be parseable as a specified type.
-    /// </summary>
-    /// <typeparam name="T">The type to which the value must be parseable.</typeparam>
-    internal class LegacyTypeRouteConstraint<T> : LegacyRouteConstraint
-    {
-        public delegate bool LegacyTryParseDelegate(string str, [MaybeNullWhen(false)] out T result);
-
-        private readonly LegacyTryParseDelegate _parser;
-
-        public LegacyTypeRouteConstraint(LegacyTryParseDelegate parser)
-        {
-            _parser = parser;
-        }
-
-        public override bool Match(string pathSegment, out object? convertedValue)
-        {
-            if (_parser(pathSegment, out var result))
-            {
-                convertedValue = result;
-                return true;
-            }
-            else
-            {
-                convertedValue = null;
-                return false;
-            }
-        }
-    }
-}

+ 1 - 1
src/Components/Components/src/Routing/RouteTable.cs

@@ -3,7 +3,7 @@
 
 namespace Microsoft.AspNetCore.Components.Routing
 {
-    internal class RouteTable : IRouteTable
+    internal class RouteTable
     {
         public RouteTable(RouteEntry[] routes)
         {

+ 3 - 13
src/Components/Components/src/Routing/Router.cs

@@ -6,13 +6,11 @@
 using System;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
-using System.Diagnostics.CodeAnalysis;
 using System.Linq;
 using System.Reflection;
 using System.Runtime.ExceptionServices;
 using System.Threading;
 using System.Threading.Tasks;
-using Microsoft.AspNetCore.Components.LegacyRouteMatching;
 using Microsoft.Extensions.Logging;
 
 namespace Microsoft.AspNetCore.Components.Routing
@@ -80,17 +78,11 @@ namespace Microsoft.AspNetCore.Components.Routing
         /// <summary>
         /// Gets or sets a flag to indicate whether route matching should prefer exact matches
         /// over wildcards.
+        /// <para>This property is obsolete and configuring it does nothing.</para>
         /// </summary>
-        /// <remarks>
-        /// <para>
-        /// Important: all applications should explicitly set this to true. The option to set it to false
-        /// (or leave unset, which defaults to false) is only provided for backward compatibility.
-        /// In .NET 6, this option will be removed and the router will always prefer exact matches.
-        /// </para>
-        /// </remarks>
         [Parameter] public bool PreferExactMatches { get; set; }
 
-        private IRouteTable Routes { get; set; }
+        private RouteTable Routes { get; set; }
 
         /// <inheritdoc />
         public void Attach(RenderHandle renderHandle)
@@ -157,9 +149,7 @@ namespace Microsoft.AspNetCore.Components.Routing
 
             if (!_assemblies.SetEquals(assembliesSet))
             {
-                Routes = PreferExactMatches
-                    ? RouteTableFactory.Create(assemblies)
-                    : LegacyRouteTableFactory.Create(assemblies);
+                Routes = RouteTableFactory.Create(assemblies);
                 _assemblies.Clear();
                 _assemblies.UnionWith(assembliesSet);
             }

+ 0 - 46
src/Components/Components/test/LegacyRouteMatching/LegacyRouteConstraintTest.cs

@@ -1,46 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-using Xunit;
-
-namespace Microsoft.AspNetCore.Components.LegacyRouteMatching
-{
-    public class LegacyRouteConstraintTest
-    {
-        [Fact]
-        public void Parse_CreatesDifferentConstraints_ForDifferentKinds()
-        {
-            // Arrange
-            var original = LegacyRouteConstraint.Parse("ignore", "ignore", "int");
-
-            // Act
-            var another = LegacyRouteConstraint.Parse("ignore", "ignore", "guid");
-
-            // Assert
-            Assert.NotSame(original, another);
-        }
-
-        [Fact]
-        public void Parse_CachesCreatedConstraint_ForSameKind()
-        {
-            // Arrange
-            var original = LegacyRouteConstraint.Parse("ignore", "ignore", "int");
-
-            // Act
-            var another = LegacyRouteConstraint.Parse("ignore", "ignore", "int");
-
-            // Assert
-            Assert.Same(original, another);
-        }
-
-        [Fact]
-        public void Parse_DoesNotThrowIfOptionalConstraint()
-        {
-            // Act
-            var exceptions = Record.Exception(() => LegacyRouteConstraint.Parse("ignore", "ignore", "int?"));
-
-            // Assert
-            Assert.Null(exceptions);
-        }
-    }
-}

+ 0 - 741
src/Components/Components/test/LegacyRouteMatching/LegacyRouteTableFactoryTests.cs

@@ -1,741 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using Xunit;
-
-// Avoid referencing the whole Microsoft.AspNetCore.Components.Routing namespace to
-// avoid the risk of accidentally relying on the non-legacy types in the legacy fork
-using RouteContext = Microsoft.AspNetCore.Components.Routing.RouteContext;
-
-namespace Microsoft.AspNetCore.Components.LegacyRouteMatching
-{
-    public class LegacyRouteTableFactoryTests
-    {
-        [Fact]
-        public void CanCacheRouteTable()
-        {
-            // Arrange
-            var routes1 = LegacyRouteTableFactory.Create(new[] { GetType().Assembly, });
-
-            // Act
-            var routes2 = LegacyRouteTableFactory.Create(new[] { GetType().Assembly, });
-
-            // Assert
-            Assert.Same(routes1, routes2);
-        }
-
-        [Fact]
-        public void CanCacheRouteTableWithDifferentAssembliesAndOrder()
-        {
-            // Arrange
-            var routes1 = LegacyRouteTableFactory.Create(new[] { typeof(object).Assembly, GetType().Assembly, });
-
-            // Act
-            var routes2 = LegacyRouteTableFactory.Create(new[] { GetType().Assembly, typeof(object).Assembly, });
-
-            // Assert
-            Assert.Same(routes1, routes2);
-        }
-
-        [Fact]
-        public void DoesNotCacheRouteTableForDifferentAssemblies()
-        {
-            // Arrange
-            var routes1 = LegacyRouteTableFactory.Create(new[] { GetType().Assembly, });
-
-            // Act
-            var routes2 = LegacyRouteTableFactory.Create(new[] { GetType().Assembly, typeof(object).Assembly, });
-
-            // Assert
-            Assert.NotSame(routes1, routes2);
-        }
-
-        [Fact]
-        public void CanDiscoverRoute()
-        {
-            // Arrange & Act
-            var routes = LegacyRouteTableFactory.Create(new[] { typeof(MyComponent), });
-
-            // Assert
-            Assert.Equal("Test1", Assert.Single(routes.Routes).Template.TemplateText);
-        }
-
-        [Route("Test1")]
-        private class MyComponent : ComponentBase
-        {
-        }
-
-        [Fact]
-        public void CanDiscoverRoutes_WithInheritance()
-        {
-            // Arrange & Act
-            var routes = LegacyRouteTableFactory.Create(new[] { typeof(MyComponent), typeof(MyInheritedComponent), });
-
-            // Assert
-            Assert.Collection(
-                routes.Routes.OrderBy(r => r.Template.TemplateText),
-                r => Assert.Equal("Test1", r.Template.TemplateText),
-                r => Assert.Equal("Test2", r.Template.TemplateText));
-        }
-
-        [Route("Test2")]
-        private class MyInheritedComponent : MyComponent
-        {
-        }
-
-        [Fact]
-        public void CanMatchRootTemplate()
-        {
-            // Arrange
-            var routeTable = new TestRouteTableBuilder().AddRoute("/").Build();
-            var context = new RouteContext("/");
-
-            // Act
-            routeTable.Route(context);
-
-            // Assert
-            Assert.NotNull(context.Handler);
-        }
-
-        [Fact]
-        public void CanMatchLiteralTemplate()
-        {
-            // Arrange
-            var routeTable = new TestRouteTableBuilder().AddRoute("/literal").Build();
-            var context = new RouteContext("/literal/");
-
-            // Act
-            routeTable.Route(context);
-
-            // Assert
-            Assert.NotNull(context.Handler);
-        }
-
-        [Fact]
-        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);
-        }
-
-        [Fact]
-        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);
-        }
-
-        [Fact]
-        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);
-        }
-
-        [Fact]
-        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);
-        }
-
-        [Theory]
-        [InlineData("/{value:bool}", "/maybe")]
-        [InlineData("/{value:datetime}", "/1955-01-32")]
-        [InlineData("/{value:decimal}", "/hello")]
-        [InlineData("/{value:double}", "/0.1.2")]
-        [InlineData("/{value:float}", "/0.1.2")]
-        [InlineData("/{value:guid}", "/not-a-guid")]
-        [InlineData("/{value:int}", "/3.141")]
-        [InlineData("/{value:long}", "/3.141")]
-        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);
-        }
-
-        [Theory]
-        [InlineData("/some")]
-        [InlineData("/some/awesome/route/with/extra/segments")]
-        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);
-        }
-
-        [Theory]
-        [InlineData("/value1", "value1")]
-        [InlineData("/value2/", "value2")]
-        [InlineData("/d%C3%A9j%C3%A0%20vu", "déjà vu")]
-        [InlineData("/d%C3%A9j%C3%A0%20vu/", "déjà vu")]
-        [InlineData("/d%C3%A9j%C3%A0+vu", "déjà+vu")]
-        public void CanMatchParameterTemplate(string path, string expectedValue)
-        {
-            // Arrange
-            var routeTable = new TestRouteTableBuilder().AddRoute("/{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);
-        }
-
-        [Theory]
-        [InlineData("/blog/value1", "value1")]
-        [InlineData("/blog/value1/foo%20bar", "value1/foo bar")]
-        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);
-        }
-
-        [Fact]
-        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);
-        }
-
-
-        [Fact]
-        public void CanMatchTemplateWithMultipleParametersAndCatchAllParameter()
-        {
-            // Arrange
-            var routeTable = new TestRouteTableBuilder().AddRoute("/{some}/awesome/{route}/with/{*catchAll}").Build();
-            var context = new RouteContext("/an/awesome/path/with/some/catch/all/stuff");
-
-            var expectedParameters = new Dictionary<string, object>
-            {
-                ["some"] = "an",
-                ["route"] = "path",
-                ["catchAll"] = "some/catch/all/stuff"
-            };
-
-            // Act
-            routeTable.Route(context);
-
-            // Assert
-            Assert.NotNull(context.Handler);
-            Assert.Equal(expectedParameters, context.Parameters);
-        }
-
-        public static IEnumerable<object[]> CanMatchParameterWithConstraintCases() => new object[][]
-        {
-            new object[] { "/{value:bool}", "/true", true },
-            new object[] { "/{value:bool}", "/false", false },
-            new object[] { "/{value:datetime}", "/1955-01-30", new DateTime(1955, 1, 30) },
-            new object[] { "/{value:decimal}", "/5.3", 5.3m },
-            new object[] { "/{value:double}", "/0.1", 0.1d },
-            new object[] { "/{value:float}", "/0.1", 0.1f },
-            new object[] { "/{value:guid}", "/1FCEF085-884F-416E-B0A1-71B15F3E206B", Guid.Parse("1FCEF085-884F-416E-B0A1-71B15F3E206B") },
-            new object[] { "/{value:int}", "/123", 123 },
-            new object[] { "/{value:int}", "/-123", -123},
-            new object[] { "/{value:long}", "/9223372036854775807", long.MaxValue },
-            new object[] { "/{value:long}", $"/-9223372036854775808", long.MinValue },
-        };
-
-        [Theory]
-        [MemberData(nameof(CanMatchParameterWithConstraintCases))]
-        public void CanMatchParameterWithConstraint(string template, string contextUrl, object convertedValue)
-        {
-            // Arrange
-            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 }
-            }, context.Parameters);
-        }
-
-        [Fact]
-        public void CanMatchOptionalParameterWithoutConstraints()
-        {
-            // Arrange
-            var template = "/optional/{value?}";
-            var contextUrl = "/optional/";
-            string 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 }
-            }, context.Parameters);
-        }
-
-        public static IEnumerable<object[]> CanMatchOptionalParameterWithConstraintCases() => new object[][]
-{
-            new object[] { "/optional/{value:bool?}", "/optional/", null },
-            new object[] { "/optional/{value:datetime?}", "/optional/", null },
-            new object[] { "/optional/{value:decimal?}", "/optional/", null },
-};
-
-        [Theory]
-        [MemberData(nameof(CanMatchOptionalParameterWithConstraintCases))]
-        public void CanMatchOptionalParameterWithConstraint(string template, string contextUrl, object convertedValue)
-        {
-            // Arrange
-            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 }
-            }, context.Parameters);
-        }
-
-        [Fact]
-        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);
-        }
-
-        public static IEnumerable<object[]> CanMatchSegmentWithMultipleConstraintsCases() => new object[][]
-{
-            new object[] { "/{value:double:int}/", "/15", 15 },
-            new object[] { "/{value:double?:int?}/", "/", null },
-};
-
-        [Theory]
-        [MemberData(nameof(CanMatchSegmentWithMultipleConstraintsCases))]
-        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);
-        }
-
-        [Fact]
-        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);
-        }
-
-        [Fact]
-        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);
-        }
-
-        [Fact]
-        public void PrefersOptionalParamsOverNonOptionalParams()
-        {
-            // 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);
-        }
-
-        [Fact]
-        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);
-        }
-
-
-        [Fact]
-        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);
-        }
-
-        [Fact]
-        public void PrefersShorterRoutesOverLongerRoutes()
-        {
-            // Arrange & Act
-            var handler = typeof(int);
-            var routeTable = new TestRouteTableBuilder()
-                .AddRoute("/an/awesome/path")
-                .AddRoute("/an/awesome/", handler).Build();
-
-            // Act
-            Assert.Equal("an/awesome", routeTable.Routes[0].Template.TemplateText);
-        }
-
-        [Fact]
-        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 }
-            });
-        }
-
-        [Fact]
-        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);
-        }
-
-        [Fact]
-        public void ProducesAStableOrderForNonAmbiguousRoutes()
-        {
-            // Arrange & Act
-            var handler = typeof(int);
-            var routeTable = new TestRouteTableBuilder()
-                .AddRoute("/an/awesome/", handler)
-                .AddRoute("/a/brilliant/").Build();
-
-            // Act
-            Assert.Equal("a/brilliant", routeTable.Routes[0].Template.TemplateText);
-        }
-
-        [Fact]
-        public void DoesNotThrowIfStableSortComparesRouteWithItself()
-        {
-            // Test for https://github.com/dotnet/aspnetcore/issues/13313
-            // Arrange & Act
-            var builder = new TestRouteTableBuilder();
-            builder.AddRoute("r16");
-            builder.AddRoute("r05");
-            builder.AddRoute("r09");
-            builder.AddRoute("r00");
-            builder.AddRoute("r13");
-            builder.AddRoute("r02");
-            builder.AddRoute("r03");
-            builder.AddRoute("r10");
-            builder.AddRoute("r15");
-            builder.AddRoute("r14");
-            builder.AddRoute("r12");
-            builder.AddRoute("r07");
-            builder.AddRoute("r11");
-            builder.AddRoute("r08");
-            builder.AddRoute("r06");
-            builder.AddRoute("r04");
-            builder.AddRoute("r01");
-
-            var routeTable = builder.Build();
-
-            // Act
-            Assert.Equal(17, routeTable.Routes.Length);
-            for (var i = 0; i < 17; i++)
-            {
-                var templateText = "r" + i.ToString(CultureInfo.InvariantCulture).PadLeft(2, '0');
-                Assert.Equal(templateText, routeTable.Routes[i].Template.TemplateText);
-            }
-        }
-
-        [Theory]
-        [InlineData("/literal", "/Literal/")]
-        [InlineData("/{parameter}", "/{parameter}/")]
-        [InlineData("/literal/{parameter}", "/Literal/{something}")]
-        [InlineData("/{parameter}/literal/{something}", "{param}/Literal/{else}")]
-        public void DetectsAmbiguousRoutes(string left, string right)
-        {
-            // Arrange
-            var expectedMessage = $@"The following routes are ambiguous:
-'{left.Trim('/')}' in '{typeof(object).FullName}'
-'{right.Trim('/')}' in '{typeof(object).FullName}'
-";
-            // Act
-            var exception = Assert.Throws<InvalidOperationException>(() => new TestRouteTableBuilder()
-                .AddRoute(left)
-                .AddRoute(right).Build());
-
-            Assert.Equal(expectedMessage, exception.Message);
-        }
-
-        [Fact]
-        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);
-        }
-
-        private class TestRouteTableBuilder
-        {
-            IList<(string Template, Type Handler)> _routeTemplates = new List<(string, Type)>();
-            Type _handler = typeof(object);
-
-            public TestRouteTableBuilder AddRoute(string template, Type handler = null)
-            {
-                _routeTemplates.Add((template, handler ?? _handler));
-                return this;
-            }
-
-            public LegacyRouteTable Build()
-            {
-                try
-                {
-                    var templatesByHandler = _routeTemplates
-                        .GroupBy(rt => rt.Handler)
-                        .ToDictionary(group => group.Key, group => group.Select(g => g.Template).ToArray());
-                    return LegacyRouteTableFactory.Create(templatesByHandler);
-                }
-                catch (InvalidOperationException ex) when (ex.InnerException is InvalidOperationException)
-                {
-                    // ToArray() will wrap our exception in its own.
-                    throw ex.InnerException;
-                }
-            }
-        }
-
-        class TestHandler1 { }
-        class TestHandler2 { }
-    }
-}

+ 0 - 295
src/Components/Components/test/LegacyRouteMatching/LegacyTemplateParserTests.cs

@@ -1,295 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using Xunit;
-
-namespace Microsoft.AspNetCore.Components.LegacyRouteMatching
-{
-    public class LegacyTemplateParserTests
-    {
-        [Fact]
-        public void Parse_SingleLiteral()
-        {
-            // Arrange
-            var expected = new ExpectedTemplateBuilder().Literal("awesome");
-
-            // Act
-            var actual = LegacyTemplateParser.ParseTemplate("awesome");
-
-            // Assert
-            Assert.Equal(expected, actual, LegacyRouteTemplateTestComparer.Instance);
-        }
-
-        [Fact]
-        public void Parse_SingleParameter()
-        {
-            // Arrange
-            var template = "{p}";
-
-            var expected = new ExpectedTemplateBuilder().Parameter("p");
-
-            // Act
-            var actual = LegacyTemplateParser.ParseTemplate(template);
-
-            // Assert
-            Assert.Equal(expected, actual, LegacyRouteTemplateTestComparer.Instance);
-        }
-
-        [Fact]
-        public void Parse_MultipleLiterals()
-        {
-            // Arrange
-            var template = "awesome/cool/super";
-
-            var expected = new ExpectedTemplateBuilder().Literal("awesome").Literal("cool").Literal("super");
-
-            // Act
-            var actual = LegacyTemplateParser.ParseTemplate(template);
-
-            // Assert
-            Assert.Equal(expected, actual, LegacyRouteTemplateTestComparer.Instance);
-        }
-
-        [Fact]
-        public void Parse_MultipleParameters()
-        {
-            // Arrange
-            var template = "{p1}/{p2}/{p3}";
-
-            var expected = new ExpectedTemplateBuilder().Parameter("p1").Parameter("p2").Parameter("p3");
-
-            // Act
-            var actual = LegacyTemplateParser.ParseTemplate(template);
-
-            // Assert
-            Assert.Equal(expected, actual, LegacyRouteTemplateTestComparer.Instance);
-        }
-
-        [Fact]
-        public void Parse_MultipleOptionalParameters()
-        {
-            // Arrange
-            var template = "{p1?}/{p2?}/{p3?}";
-
-            var expected = new ExpectedTemplateBuilder().Parameter("p1?").Parameter("p2?").Parameter("p3?");
-
-            // Act
-            var actual = LegacyTemplateParser.ParseTemplate(template);
-
-            // Assert
-            Assert.Equal(expected, actual, LegacyRouteTemplateTestComparer.Instance);
-        }
-
-        [Fact]
-        public void Parse_SingleCatchAllParameter()
-        {
-            // Arrange
-            var expected = new ExpectedTemplateBuilder().Parameter("p");
-
-            // Act
-            var actual = LegacyTemplateParser.ParseTemplate("{*p}");
-
-            // Assert
-            Assert.Equal(expected, actual, LegacyRouteTemplateTestComparer.Instance);
-        }
-
-        [Fact]
-        public void Parse_MixedLiteralAndCatchAllParameter()
-        {
-            // Arrange
-            var expected = new ExpectedTemplateBuilder().Literal("awesome").Literal("wow").Parameter("p");
-
-            // Act
-            var actual = LegacyTemplateParser.ParseTemplate("awesome/wow/{*p}");
-
-            // Assert
-            Assert.Equal(expected, actual, LegacyRouteTemplateTestComparer.Instance);
-        }
-
-        [Fact]
-        public void Parse_MixedLiteralParameterAndCatchAllParameter()
-        {
-            // Arrange
-            var expected = new ExpectedTemplateBuilder().Literal("awesome").Parameter("p1").Parameter("p2");
-
-            // Act
-            var actual = LegacyTemplateParser.ParseTemplate("awesome/{p1}/{*p2}");
-
-            // Assert
-            Assert.Equal(expected, actual, LegacyRouteTemplateTestComparer.Instance);
-        }
-
-        [Fact]
-        public void InvalidTemplate_WithRepeatedParameter()
-        {
-            var ex = Assert.Throws<InvalidOperationException>(
-                () => LegacyTemplateParser.ParseTemplate("{p1}/literal/{p1}"));
-
-            var expectedMessage = "Invalid template '{p1}/literal/{p1}'. The parameter 'Microsoft.AspNetCore.Components.LegacyRouteMatching.LegacyTemplateSegment' appears multiple times.";
-
-            Assert.Equal(expectedMessage, ex.Message);
-        }
-
-        [Theory]
-        [InlineData("p}", "Invalid template 'p}'. Missing '{' in parameter segment 'p}'.")]
-        [InlineData("{p", "Invalid template '{p'. Missing '}' in parameter segment '{p'.")]
-        [InlineData("Literal/p}", "Invalid template 'Literal/p}'. Missing '{' in parameter segment 'p}'.")]
-        [InlineData("Literal/{p", "Invalid template 'Literal/{p'. Missing '}' in parameter segment '{p'.")]
-        [InlineData("p}/Literal", "Invalid template 'p}/Literal'. Missing '{' in parameter segment 'p}'.")]
-        [InlineData("{p/Literal", "Invalid template '{p/Literal'. Missing '}' in parameter segment '{p'.")]
-        [InlineData("Another/p}/Literal", "Invalid template 'Another/p}/Literal'. Missing '{' in parameter segment 'p}'.")]
-        [InlineData("Another/{p/Literal", "Invalid template 'Another/{p/Literal'. Missing '}' in parameter segment '{p'.")]
-
-        public void InvalidTemplate_WithMismatchedBraces(string template, string expectedMessage)
-        {
-            var ex = Assert.Throws<InvalidOperationException>(
-                () => LegacyTemplateParser.ParseTemplate(template));
-
-            Assert.Equal(expectedMessage, ex.Message);
-        }
-
-        [Theory]
-        // * is only allowed at beginning for catch-all parameters
-        [InlineData("{p*}", "Invalid template '{p*}'. The character '*' in parameter segment '{p*}' is not allowed.")]
-        [InlineData("{{}", "Invalid template '{{}'. The character '{' in parameter segment '{{}' is not allowed.")]
-        [InlineData("{}}", "Invalid template '{}}'. The character '}' in parameter segment '{}}' is not allowed.")]
-        [InlineData("{=}", "Invalid template '{=}'. The character '=' in parameter segment '{=}' is not allowed.")]
-        [InlineData("{.}", "Invalid template '{.}'. The character '.' in parameter segment '{.}' is not allowed.")]
-        public void ParseRouteParameter_ThrowsIf_ParameterContainsSpecialCharacters(string template, string expectedMessage)
-        {
-            // Act & Assert
-            var ex = Assert.Throws<InvalidOperationException>(() => LegacyTemplateParser.ParseTemplate(template));
-
-            Assert.Equal(expectedMessage, ex.Message);
-        }
-
-        [Fact]
-        public void InvalidTemplate_InvalidParameterNameWithEmptyNameThrows()
-        {
-            var ex = Assert.Throws<InvalidOperationException>(() => LegacyTemplateParser.ParseTemplate("{a}/{}/{z}"));
-
-            var expectedMessage = "Invalid template '{a}/{}/{z}'. Empty parameter name in segment '{}' is not allowed.";
-
-            Assert.Equal(expectedMessage, ex.Message);
-        }
-
-        [Fact]
-        public void InvalidTemplate_ConsecutiveSeparatorsSlashSlashThrows()
-        {
-            var ex = Assert.Throws<InvalidOperationException>(() => LegacyTemplateParser.ParseTemplate("{a}//{z}"));
-
-            var expectedMessage = "Invalid template '{a}//{z}'. Empty segments are not allowed.";
-
-            Assert.Equal(expectedMessage, ex.Message);
-        }
-
-        [Fact]
-        public void InvalidTemplate_LiteralAfterOptionalParam()
-        {
-            var ex = Assert.Throws<InvalidOperationException>(() => LegacyTemplateParser.ParseTemplate("/test/{a?}/test"));
-
-            var expectedMessage = "Invalid template 'test/{a?}/test'. Non-optional parameters or literal routes cannot appear after optional parameters.";
-
-            Assert.Equal(expectedMessage, ex.Message);
-        }
-
-        [Fact]
-        public void InvalidTemplate_NonOptionalParamAfterOptionalParam()
-        {
-            var ex = Assert.Throws<InvalidOperationException>(() => LegacyTemplateParser.ParseTemplate("/test/{a?}/{b}"));
-
-            var expectedMessage = "Invalid template 'test/{a?}/{b}'. Non-optional parameters or literal routes cannot appear after optional parameters.";
-
-            Assert.Equal(expectedMessage, ex.Message);
-        }
-
-        [Fact]
-        public void InvalidTemplate_CatchAllParamWithMultipleAsterisks()
-        {
-            var ex = Assert.Throws<InvalidOperationException>(() => LegacyTemplateParser.ParseTemplate("/test/{a}/{**b}"));
-
-            var expectedMessage = "Invalid template '/test/{a}/{**b}'. A catch-all parameter may only have one '*' at the beginning of the segment.";
-
-            Assert.Equal(expectedMessage, ex.Message);
-        }
-
-        [Fact]
-        public void InvalidTemplate_CatchAllParamNotLast()
-        {
-            var ex = Assert.Throws<InvalidOperationException>(() => LegacyTemplateParser.ParseTemplate("/test/{*a}/{b}"));
-
-            var expectedMessage = "Invalid template 'test/{*a}/{b}'. A catch-all parameter can only appear as the last segment of the route template.";
-
-            Assert.Equal(expectedMessage, ex.Message);
-        }
-
-        [Fact]
-        public void InvalidTemplate_BadOptionalCharacterPosition()
-        {
-            var ex = Assert.Throws<ArgumentException>(() => LegacyTemplateParser.ParseTemplate("/test/{a?bc}/{b}"));
-
-            var expectedMessage = "Malformed parameter 'a?bc' in route '/test/{a?bc}/{b}'. '?' character can only appear at the end of parameter name.";
-
-            Assert.Equal(expectedMessage, ex.Message);
-        }
-
-        private class ExpectedTemplateBuilder
-        {
-            public IList<LegacyTemplateSegment> Segments { get; set; } = new List<LegacyTemplateSegment>();
-
-            public ExpectedTemplateBuilder Literal(string value)
-            {
-                Segments.Add(new LegacyTemplateSegment("testtemplate", value, isParameter: false));
-                return this;
-            }
-
-            public ExpectedTemplateBuilder Parameter(string value)
-            {
-                Segments.Add(new LegacyTemplateSegment("testtemplate", value, isParameter: true));
-                return this;
-            }
-
-            public LegacyRouteTemplate Build() => new LegacyRouteTemplate(string.Join('/', Segments), Segments.ToArray());
-
-            public static implicit operator LegacyRouteTemplate(ExpectedTemplateBuilder builder) => builder.Build();
-        }
-
-        private class LegacyRouteTemplateTestComparer : IEqualityComparer<LegacyRouteTemplate>
-        {
-            public static LegacyRouteTemplateTestComparer Instance { get; } = new LegacyRouteTemplateTestComparer();
-
-            public bool Equals(LegacyRouteTemplate x, LegacyRouteTemplate y)
-            {
-                if (x.Segments.Length != y.Segments.Length)
-                {
-                    return false;
-                }
-
-                for (var i = 0; i < x.Segments.Length; i++)
-                {
-                    var xSegment = x.Segments[i];
-                    var ySegment = y.Segments[i];
-                    if (xSegment.IsParameter != ySegment.IsParameter)
-                    {
-                        return false;
-                    }
-                    if (xSegment.IsOptional != ySegment.IsOptional)
-                    {
-                        return false;
-                    }
-                    if (!string.Equals(xSegment.Value, ySegment.Value, StringComparison.OrdinalIgnoreCase))
-                    {
-                        return false;
-                    }
-                }
-
-                return true;
-            }
-
-            public int GetHashCode(LegacyRouteTemplate obj) => 0;
-        }
-    }
-}

+ 0 - 24
src/Components/Components/test/Routing/RouterTest.cs

@@ -182,29 +182,6 @@ namespace Microsoft.AspNetCore.Components.Test.Routing
             await feb;
         }
 
-        [Fact]
-        public async Task UsesLegacyRouteMatchingByDefault()
-        {
-            // Arrange
-            // Legacy routing prefers {*someWildcard} over any other pattern than has more segments,
-            // even if the other pattern is an exact match
-            _navigationManager.NotifyLocationChanged("https://www.example.com/subdir/a/b", false);
-            var parameters = new Dictionary<string, object>
-            {
-                { nameof(Router.AppAssembly), typeof(RouterTest).Assembly },
-                { nameof(Router.NotFound), (RenderFragment)(builder => { }) },
-            };
-
-            // Act
-            await _renderer.Dispatcher.InvokeAsync(() =>
-                _router.SetParametersAsync(ParameterView.FromDictionary(parameters)));
-
-            // Assert
-            var renderedFrame = _renderer.Batches.First().ReferenceFrames.First();
-            Assert.Equal(RenderTreeFrameType.Text, renderedFrame.FrameType);
-            Assert.Equal($"Rendering route matching {typeof(MatchAnythingComponent)}", renderedFrame.TextContent);
-        }
-
         [Fact]
         public async Task UsesCurrentRouteMatchingIfSpecified()
         {
@@ -216,7 +193,6 @@ namespace Microsoft.AspNetCore.Components.Test.Routing
             {
                 { nameof(Router.AppAssembly), typeof(RouterTest).Assembly },
                 { nameof(Router.NotFound), (RenderFragment)(builder => { }) },
-                { nameof(Router.PreferExactMatches), true },
             };
 
             // Act

+ 1 - 1
src/Components/Samples/BlazorServerApp/App.razor

@@ -1,4 +1,4 @@
-<Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="true">
+<Router AppAssembly="@typeof(Program).Assembly">
     <Found Context="routeData">
         <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
     </Found>

+ 1 - 1
src/Components/WebAssembly/Samples/HostedBlazorWebassemblyApp/Client/App.razor

@@ -1,4 +1,4 @@
-<Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
+<Router AppAssembly="@typeof(Program).Assembly">
     <Found Context="routeData">
         <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
     </Found>

+ 1 - 1
src/Components/WebAssembly/testassets/StandaloneApp/App.razor

@@ -1,4 +1,4 @@
-<Router AppAssembly="@typeof(StandaloneApp.Program).Assembly" PreferExactMatches="true">
+<Router AppAssembly="@typeof(StandaloneApp.Program).Assembly">
     <Found Context="routeData">
         <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
     </Found>

+ 1 - 1
src/Components/WebAssembly/testassets/Wasm.Authentication.Client/App.razor

@@ -1,5 +1,5 @@
 <CascadingAuthenticationState>
-    <Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="true">
+    <Router AppAssembly="@typeof(Program).Assembly">
         <Found Context="routeData">
             <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
                 <NotAuthorized>

+ 1 - 1
src/Components/WebView/Samples/BlazorWinFormsApp/Main.razor

@@ -1,4 +1,4 @@
-<Router AppAssembly="@GetType().Assembly" PreferExactMatches="true">
+<Router AppAssembly="@GetType().Assembly">
     <Found Context="routeData">
         <NavLink href="" Match="NavLinkMatch.All">Home</NavLink> |
         <NavLink href="other">Other</NavLink>

+ 1 - 1
src/Components/WebView/Samples/BlazorWpfApp/Main.razor

@@ -1,4 +1,4 @@
-<Router AppAssembly="@GetType().Assembly" PreferExactMatches="true">
+<Router AppAssembly="@GetType().Assembly">
     <Found Context="routeData">
         <NavLink href="" Match="NavLinkMatch.All">Home</NavLink> |
         <NavLink href="other">Other</NavLink>

+ 1 - 1
src/Components/benchmarkapps/BlazingPizza.Server/App.razor

@@ -1,4 +1,4 @@
-<Router AppAssembly="typeof(Program).Assembly" PreferExactMatches="true">
+<Router AppAssembly="typeof(Program).Assembly">
     <NotFound>Page not found</NotFound>
     <Found Context="routeData">
         <RouteView RouteData="routeData" DefaultLayout="typeof(MainLayout)"></RouteView>

+ 1 - 1
src/Components/benchmarkapps/Wasm.Performance/TestApp/App.razor

@@ -1,4 +1,4 @@
-<Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="true">
+<Router AppAssembly="@typeof(Program).Assembly">
     <Found Context="routeData">
         <RouteView RouteData="@routeData" />
     </Found>

+ 1 - 1
src/Components/test/testassets/BasicTestApp/AuthTest/AuthRouter.razor

@@ -9,7 +9,7 @@
     and @page authorization rules.
 *@
 
-<Router AppAssembly="@typeof(BasicTestApp.Program).Assembly" PreferExactMatches="true">
+<Router AppAssembly="@typeof(BasicTestApp.Program).Assembly">
     <Found Context="routeData">
         <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(AuthRouterLayout)">
             <Authorizing>Authorizing...</Authorizing>

+ 1 - 1
src/Components/test/testassets/BasicTestApp/RouterTest/TestRouter.razor

@@ -1,5 +1,5 @@
 @using Microsoft.AspNetCore.Components.Routing
-<Router AppAssembly="@typeof(BasicTestApp.Program).Assembly" PreferExactMatches="true">
+<Router AppAssembly="@typeof(BasicTestApp.Program).Assembly">
     <Found Context="routeData">
         <RouteView RouteData="@routeData" />
     </Found>

+ 1 - 1
src/Components/test/testassets/BasicTestApp/RouterTest/TestRouterWithAdditionalAssembly.razor

@@ -1,5 +1,5 @@
 @using Microsoft.AspNetCore.Components.Routing
-<Router AppAssembly="@typeof(BasicTestApp.Program).Assembly" AdditionalAssemblies="@(new[] { typeof(TestContentPackage.RouteableComponentFromPackage).Assembly, })" PreferExactMatches="true">
+<Router AppAssembly="@typeof(BasicTestApp.Program).Assembly" AdditionalAssemblies="@(new[] { typeof(TestContentPackage.RouteableComponentFromPackage).Assembly, })">
     <Found Context="routeData">
         <RouteView RouteData="@routeData" />
     </Found>

+ 1 - 1
src/Components/test/testassets/BasicTestApp/RouterTest/TestRouterWithLazyAssembly.razor

@@ -4,7 +4,7 @@
 
 @inject LazyAssemblyLoader lazyLoader
 
-<Router AppAssembly="@typeof(BasicTestApp.Program).Assembly" AdditionalAssemblies="@lazyLoadedAssemblies" OnNavigateAsync="@OnNavigateAsync" PreferExactMatches="true">
+<Router AppAssembly="@typeof(BasicTestApp.Program).Assembly" AdditionalAssemblies="@lazyLoadedAssemblies" OnNavigateAsync="@OnNavigateAsync">
     <Navigating>
         <div style="padding: 20px;background-color:blue;color:white;" id="loading-banner">
             <p>Loading the requested page...</p>

+ 1 - 1
src/Components/test/testassets/BasicTestApp/RouterTest/TestRouterWithOnNavigate.razor

@@ -4,7 +4,7 @@
 
 <button @onclick="TriggerRerender" id="trigger-rerender">Trigger Rerender</button>
 
-<Router AppAssembly="@typeof(BasicTestApp.Program).Assembly" OnNavigateAsync="@OnNavigateAsync" PreferExactMatches="true">
+<Router AppAssembly="@typeof(BasicTestApp.Program).Assembly" OnNavigateAsync="@OnNavigateAsync">
     <Navigating>
         <div style="padding: 20px;background-color:blue;color:white;" id="loading-banner">
             <p>Loading the requested page...</p>

+ 1 - 1
src/Components/test/testassets/ComponentsApp.App/App.razor

@@ -1,6 +1,6 @@
 @using Microsoft.AspNetCore.Components;
 <CascadingValue Value="Name" Name="Name" IsFixed=true>
-    <Router AppAssembly="@typeof(ComponentsApp.App.App).Assembly" PreferExactMatches="true">
+    <Router AppAssembly="@typeof(ComponentsApp.App.App).Assembly">
         <Found Context="routeData">
             <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
         </Found>

+ 2 - 2
src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/App.razor

@@ -1,5 +1,5 @@
 @*#if (NoAuth)
-<Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
+<Router AppAssembly="@typeof(Program).Assembly">
     <Found Context="routeData">
         <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
     </Found>
@@ -11,7 +11,7 @@
 </Router>
 #else
 <CascadingAuthenticationState>
-    <Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
+    <Router AppAssembly="@typeof(Program).Assembly">
         <Found Context="routeData">
             <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
         </Found>

+ 2 - 2
src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/App.razor

@@ -1,5 +1,5 @@
 @*#if (NoAuth)
-<Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
+<Router AppAssembly="@typeof(Program).Assembly">
     <Found Context="routeData">
         <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
     </Found>
@@ -11,7 +11,7 @@
 </Router>
 #else
 <CascadingAuthenticationState>
-    <Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
+    <Router AppAssembly="@typeof(Program).Assembly">
         <Found Context="routeData">
             <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
                 <NotAuthorized>