diff --git a/src/Components/Components/src/Routing/IRouteTable.cs b/src/Components/Components/src/Routing/IRouteTable.cs
deleted file mode 100644
index 15f5e51dddcf..000000000000
--- a/src/Components/Components/src/Routing/IRouteTable.cs
+++ /dev/null
@@ -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
-{
- ///
- /// Provides an abstraction over and .
- /// This is only an internal implementation detail of and can be removed once
- /// the legacy route matching logic is removed.
- ///
- internal interface IRouteTable
- {
- void Route(RouteContext routeContext);
- }
-}
diff --git a/src/Components/Components/src/Routing/LegacyRouteMatching/LegacyOptionalTypeRouteConstraint.cs b/src/Components/Components/src/Routing/LegacyRouteMatching/LegacyOptionalTypeRouteConstraint.cs
deleted file mode 100644
index 74cde946ab85..000000000000
--- a/src/Components/Components/src/Routing/LegacyRouteMatching/LegacyOptionalTypeRouteConstraint.cs
+++ /dev/null
@@ -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
-{
- ///
- /// A route constraint that allows the value to be null or parseable as the specified
- /// type.
- ///
- /// The type to which the value must be parseable.
- internal class LegacyOptionalTypeRouteConstraint : 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;
- }
- }
- }
-}
diff --git a/src/Components/Components/src/Routing/LegacyRouteMatching/LegacyRouteConstraint.cs b/src/Components/Components/src/Routing/LegacyRouteMatching/LegacyRouteConstraint.cs
deleted file mode 100644
index caad4b906862..000000000000
--- a/src/Components/Components/src/Routing/LegacyRouteMatching/LegacyRouteConstraint.cs
+++ /dev/null
@@ -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 _cachedConstraints
- = new ConcurrentDictionary();
-
- 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}'.");
- }
- }
- }
-
- ///
- /// 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
- /// which handles the appropriate checks.
- ///
- /// String representation of the constraint
- /// Type-specific RouteConstraint object
- private static LegacyRouteConstraint? CreateRouteConstraint(string constraint)
- {
- switch (constraint)
- {
- case "bool":
- return new LegacyTypeRouteConstraint(bool.TryParse);
- case "bool?":
- return new LegacyOptionalTypeRouteConstraint(bool.TryParse);
- case "datetime":
- return new LegacyTypeRouteConstraint((string str, out DateTime result)
- => DateTime.TryParse(str, CultureInfo.InvariantCulture, DateTimeStyles.None, out result));
- case "datetime?":
- return new LegacyOptionalTypeRouteConstraint((string str, out DateTime result)
- => DateTime.TryParse(str, CultureInfo.InvariantCulture, DateTimeStyles.None, out result));
- case "decimal":
- return new LegacyTypeRouteConstraint((string str, out decimal result)
- => decimal.TryParse(str, NumberStyles.Number, CultureInfo.InvariantCulture, out result));
- case "decimal?":
- return new LegacyOptionalTypeRouteConstraint((string str, out decimal result)
- => decimal.TryParse(str, NumberStyles.Number, CultureInfo.InvariantCulture, out result));
- case "double":
- return new LegacyTypeRouteConstraint((string str, out double result)
- => double.TryParse(str, NumberStyles.Number, CultureInfo.InvariantCulture, out result));
- case "double?":
- return new LegacyOptionalTypeRouteConstraint((string str, out double result)
- => double.TryParse(str, NumberStyles.Number, CultureInfo.InvariantCulture, out result));
- case "float":
- return new LegacyTypeRouteConstraint((string str, out float result)
- => float.TryParse(str, NumberStyles.Number, CultureInfo.InvariantCulture, out result));
- case "float?":
- return new LegacyOptionalTypeRouteConstraint((string str, out float result)
- => float.TryParse(str, NumberStyles.Number, CultureInfo.InvariantCulture, out result));
- case "guid":
- return new LegacyTypeRouteConstraint(Guid.TryParse);
- case "guid?":
- return new LegacyOptionalTypeRouteConstraint(Guid.TryParse);
- case "int":
- return new LegacyTypeRouteConstraint((string str, out int result)
- => int.TryParse(str, NumberStyles.Integer, CultureInfo.InvariantCulture, out result));
- case "int?":
- return new LegacyOptionalTypeRouteConstraint((string str, out int result)
- => int.TryParse(str, NumberStyles.Integer, CultureInfo.InvariantCulture, out result));
- case "long":
- return new LegacyTypeRouteConstraint((string str, out long result)
- => long.TryParse(str, NumberStyles.Integer, CultureInfo.InvariantCulture, out result));
- case "long?":
- return new LegacyOptionalTypeRouteConstraint((string str, out long result)
- => long.TryParse(str, NumberStyles.Integer, CultureInfo.InvariantCulture, out result));
- default:
- return null;
- }
- }
- }
-}
diff --git a/src/Components/Components/src/Routing/LegacyRouteMatching/LegacyRouteEntry.cs b/src/Components/Components/src/Routing/LegacyRouteMatching/LegacyRouteEntry.cs
deleted file mode 100644
index c5febdf2b860..000000000000
--- a/src/Components/Components/src/Routing/LegacyRouteMatching/LegacyRouteEntry.cs
+++ /dev/null
@@ -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 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(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(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(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;
- }
- }
- }
-}
diff --git a/src/Components/Components/src/Routing/LegacyRouteMatching/LegacyRouteTable.cs b/src/Components/Components/src/Routing/LegacyRouteMatching/LegacyRouteTable.cs
deleted file mode 100644
index de8fc9ef9048..000000000000
--- a/src/Components/Components/src/Routing/LegacyRouteMatching/LegacyRouteTable.cs
+++ /dev/null
@@ -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;
- }
- }
- }
- }
-}
diff --git a/src/Components/Components/src/Routing/LegacyRouteMatching/LegacyRouteTableFactory.cs b/src/Components/Components/src/Routing/LegacyRouteMatching/LegacyRouteTableFactory.cs
deleted file mode 100644
index a87f5adc9d1e..000000000000
--- a/src/Components/Components/src/Routing/LegacyRouteMatching/LegacyRouteTableFactory.cs
+++ /dev/null
@@ -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
-{
- ///
- /// Resolves components for an application.
- ///
- internal static class LegacyRouteTableFactory
- {
- private static readonly ConcurrentDictionary Cache =
- new ConcurrentDictionary();
- public static readonly IComparer RoutePrecedence = Comparer.Create(RouteComparison);
-
- public static LegacyRouteTable Create(IEnumerable 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 componentTypes)
- {
- var templatesByHandler = new Dictionary();
- 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(inherit: false);
-
- var templates = routeAttributes.Select(t => t.Template).ToArray();
- templatesByHandler.Add(componentType, templates);
- }
- return Create(templatesByHandler);
- }
-
- internal static LegacyRouteTable Create(Dictionary templatesByHandler)
- {
- var routes = new List();
- 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();
- }
-
- ///
- /// 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.
- ///
- 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
- {
- 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();
- }
- }
- }
-}
diff --git a/src/Components/Components/src/Routing/LegacyRouteMatching/LegacyRouteTemplate.cs b/src/Components/Components/src/Routing/LegacyRouteMatching/LegacyRouteTemplate.cs
deleted file mode 100644
index a033d8ddff3b..000000000000
--- a/src/Components/Components/src/Routing/LegacyRouteMatching/LegacyRouteTemplate.cs
+++ /dev/null
@@ -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; }
- }
-}
diff --git a/src/Components/Components/src/Routing/LegacyRouteMatching/LegacyTemplateParser.cs b/src/Components/Components/src/Routing/LegacyRouteMatching/LegacyTemplateParser.cs
deleted file mode 100644
index c9312947f2ce..000000000000
--- a/src/Components/Components/src/Routing/LegacyRouteMatching/LegacyTemplateParser.cs
+++ /dev/null
@@ -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());
- }
-
- 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);
- }
- }
-}
diff --git a/src/Components/Components/src/Routing/LegacyRouteMatching/LegacyTemplateSegment.cs b/src/Components/Components/src/Routing/LegacyRouteMatching/LegacyTemplateSegment.cs
deleted file mode 100644
index 5d912ef27f00..000000000000
--- a/src/Components/Components/src/Routing/LegacyRouteMatching/LegacyTemplateSegment.cs
+++ /dev/null
@@ -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();
- }
- 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);
- }
- }
- }
-}
diff --git a/src/Components/Components/src/Routing/LegacyRouteMatching/LegacyTypeRouteConstraint.cs b/src/Components/Components/src/Routing/LegacyRouteMatching/LegacyTypeRouteConstraint.cs
deleted file mode 100644
index 47a9777a459a..000000000000
--- a/src/Components/Components/src/Routing/LegacyRouteMatching/LegacyTypeRouteConstraint.cs
+++ /dev/null
@@ -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
-{
- ///
- /// A route constraint that requires the value to be parseable as a specified type.
- ///
- /// The type to which the value must be parseable.
- internal class LegacyTypeRouteConstraint : 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;
- }
- }
- }
-}
diff --git a/src/Components/Components/src/Routing/RouteTable.cs b/src/Components/Components/src/Routing/RouteTable.cs
index 0daa00ec02ce..f7bcf11ddfec 100644
--- a/src/Components/Components/src/Routing/RouteTable.cs
+++ b/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)
{
diff --git a/src/Components/Components/src/Routing/Router.cs b/src/Components/Components/src/Routing/Router.cs
index d2dc133ff1ea..eb40814605e6 100644
--- a/src/Components/Components/src/Routing/Router.cs
+++ b/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 @@ static readonly ReadOnlyDictionary _emptyParametersDictionary
///
/// Gets or sets a flag to indicate whether route matching should prefer exact matches
/// over wildcards.
+ /// This property is obsolete and configuring it does nothing.
///
- ///
- ///
- /// 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.
- ///
- ///
[Parameter] public bool PreferExactMatches { get; set; }
- private IRouteTable Routes { get; set; }
+ private RouteTable Routes { get; set; }
///
public void Attach(RenderHandle renderHandle)
@@ -157,9 +149,7 @@ private void RefreshRouteTable()
if (!_assemblies.SetEquals(assembliesSet))
{
- Routes = PreferExactMatches
- ? RouteTableFactory.Create(assemblies)
- : LegacyRouteTableFactory.Create(assemblies);
+ Routes = RouteTableFactory.Create(assemblies);
_assemblies.Clear();
_assemblies.UnionWith(assembliesSet);
}
diff --git a/src/Components/Components/test/LegacyRouteMatching/LegacyRouteConstraintTest.cs b/src/Components/Components/test/LegacyRouteMatching/LegacyRouteConstraintTest.cs
deleted file mode 100644
index 501f2ea1d8a1..000000000000
--- a/src/Components/Components/test/LegacyRouteMatching/LegacyRouteConstraintTest.cs
+++ /dev/null
@@ -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);
- }
- }
-}
diff --git a/src/Components/Components/test/LegacyRouteMatching/LegacyRouteTableFactoryTests.cs b/src/Components/Components/test/LegacyRouteMatching/LegacyRouteTableFactoryTests.cs
deleted file mode 100644
index 51ae33e9f94f..000000000000
--- a/src/Components/Components/test/LegacyRouteMatching/LegacyRouteTableFactoryTests.cs
+++ /dev/null
@@ -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
- {
- ["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
- {
- ["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