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 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 - { - { "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 - { - { "value", convertedValue } - }, context.Parameters); - } - - public static IEnumerable 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 - { - { "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 - { - { "value", convertedValue }, - { "value2", convertedValue } - }, context.Parameters); - } - - public static IEnumerable 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 - { - { "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 - { - { "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(() => 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(), 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(), route.UnusedRouteParameterNames); - }); - Assert.Same(typeof(TestHandler1), context.Handler); - Assert.Equal(new Dictionary - { - { "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 { } - } -} diff --git a/src/Components/Components/test/LegacyRouteMatching/LegacyTemplateParserTests.cs b/src/Components/Components/test/LegacyRouteMatching/LegacyTemplateParserTests.cs deleted file mode 100644 index d38b7403a7b6..000000000000 --- a/src/Components/Components/test/LegacyRouteMatching/LegacyTemplateParserTests.cs +++ /dev/null @@ -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( - () => 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( - () => 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(() => LegacyTemplateParser.ParseTemplate(template)); - - Assert.Equal(expectedMessage, ex.Message); - } - - [Fact] - public void InvalidTemplate_InvalidParameterNameWithEmptyNameThrows() - { - var ex = Assert.Throws(() => 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(() => 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(() => 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(() => 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(() => 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(() => 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(() => 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 Segments { get; set; } = new List(); - - 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 - { - 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; - } - } -} diff --git a/src/Components/Components/test/Routing/RouterTest.cs b/src/Components/Components/test/Routing/RouterTest.cs index 08f413d23c34..3d5ee09f465a 100644 --- a/src/Components/Components/test/Routing/RouterTest.cs +++ b/src/Components/Components/test/Routing/RouterTest.cs @@ -182,29 +182,6 @@ public async Task RefreshesOnceOnCancelledOnNavigateAsync() 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 - { - { 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 @@ public async Task UsesCurrentRouteMatchingIfSpecified() { { nameof(Router.AppAssembly), typeof(RouterTest).Assembly }, { nameof(Router.NotFound), (RenderFragment)(builder => { }) }, - { nameof(Router.PreferExactMatches), true }, }; // Act diff --git a/src/Components/Samples/BlazorServerApp/App.razor b/src/Components/Samples/BlazorServerApp/App.razor index 9dcf59800af3..1c360b7121a7 100644 --- a/src/Components/Samples/BlazorServerApp/App.razor +++ b/src/Components/Samples/BlazorServerApp/App.razor @@ -1,4 +1,4 @@ - + diff --git a/src/Components/WebAssembly/Samples/HostedBlazorWebassemblyApp/Client/App.razor b/src/Components/WebAssembly/Samples/HostedBlazorWebassemblyApp/Client/App.razor index 3a2af5302417..6f67a6ea61f1 100644 --- a/src/Components/WebAssembly/Samples/HostedBlazorWebassemblyApp/Client/App.razor +++ b/src/Components/WebAssembly/Samples/HostedBlazorWebassemblyApp/Client/App.razor @@ -1,4 +1,4 @@ - + diff --git a/src/Components/WebAssembly/testassets/StandaloneApp/App.razor b/src/Components/WebAssembly/testassets/StandaloneApp/App.razor index 8ace1f34be9e..e962d961918b 100644 --- a/src/Components/WebAssembly/testassets/StandaloneApp/App.razor +++ b/src/Components/WebAssembly/testassets/StandaloneApp/App.razor @@ -1,4 +1,4 @@ - + diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/App.razor b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/App.razor index 01446d9d713a..a298515d5ae6 100644 --- a/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/App.razor +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/App.razor @@ -1,5 +1,5 @@  - + diff --git a/src/Components/WebView/Samples/BlazorWinFormsApp/Main.razor b/src/Components/WebView/Samples/BlazorWinFormsApp/Main.razor index b9fe989af765..1d31f12420a5 100644 --- a/src/Components/WebView/Samples/BlazorWinFormsApp/Main.razor +++ b/src/Components/WebView/Samples/BlazorWinFormsApp/Main.razor @@ -1,4 +1,4 @@ - + Home | Other diff --git a/src/Components/WebView/Samples/BlazorWpfApp/Main.razor b/src/Components/WebView/Samples/BlazorWpfApp/Main.razor index b9fe989af765..1d31f12420a5 100644 --- a/src/Components/WebView/Samples/BlazorWpfApp/Main.razor +++ b/src/Components/WebView/Samples/BlazorWpfApp/Main.razor @@ -1,4 +1,4 @@ - + Home | Other diff --git a/src/Components/benchmarkapps/BlazingPizza.Server/App.razor b/src/Components/benchmarkapps/BlazingPizza.Server/App.razor index fad248f32518..33fa47ea70c8 100644 --- a/src/Components/benchmarkapps/BlazingPizza.Server/App.razor +++ b/src/Components/benchmarkapps/BlazingPizza.Server/App.razor @@ -1,4 +1,4 @@ - + Page not found diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/App.razor b/src/Components/benchmarkapps/Wasm.Performance/TestApp/App.razor index 66a1f641be13..5f3b40882ba8 100644 --- a/src/Components/benchmarkapps/Wasm.Performance/TestApp/App.razor +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/App.razor @@ -1,4 +1,4 @@ - + diff --git a/src/Components/test/testassets/BasicTestApp/AuthTest/AuthRouter.razor b/src/Components/test/testassets/BasicTestApp/AuthTest/AuthRouter.razor index d1e07e0f3b8b..c6a3a8fe9d1c 100644 --- a/src/Components/test/testassets/BasicTestApp/AuthTest/AuthRouter.razor +++ b/src/Components/test/testassets/BasicTestApp/AuthTest/AuthRouter.razor @@ -9,7 +9,7 @@ and @page authorization rules. *@ - + Authorizing... diff --git a/src/Components/test/testassets/BasicTestApp/RouterTest/TestRouter.razor b/src/Components/test/testassets/BasicTestApp/RouterTest/TestRouter.razor index 5d2fe94ca636..1506433462e2 100644 --- a/src/Components/test/testassets/BasicTestApp/RouterTest/TestRouter.razor +++ b/src/Components/test/testassets/BasicTestApp/RouterTest/TestRouter.razor @@ -1,5 +1,5 @@ @using Microsoft.AspNetCore.Components.Routing - + diff --git a/src/Components/test/testassets/BasicTestApp/RouterTest/TestRouterWithAdditionalAssembly.razor b/src/Components/test/testassets/BasicTestApp/RouterTest/TestRouterWithAdditionalAssembly.razor index b41085551e64..7e18c960c716 100644 --- a/src/Components/test/testassets/BasicTestApp/RouterTest/TestRouterWithAdditionalAssembly.razor +++ b/src/Components/test/testassets/BasicTestApp/RouterTest/TestRouterWithAdditionalAssembly.razor @@ -1,5 +1,5 @@ @using Microsoft.AspNetCore.Components.Routing - + diff --git a/src/Components/test/testassets/BasicTestApp/RouterTest/TestRouterWithLazyAssembly.razor b/src/Components/test/testassets/BasicTestApp/RouterTest/TestRouterWithLazyAssembly.razor index 2c4edfc337dd..ccdd22f2b5d4 100644 --- a/src/Components/test/testassets/BasicTestApp/RouterTest/TestRouterWithLazyAssembly.razor +++ b/src/Components/test/testassets/BasicTestApp/RouterTest/TestRouterWithLazyAssembly.razor @@ -4,7 +4,7 @@ @inject LazyAssemblyLoader lazyLoader - +

Loading the requested page...

diff --git a/src/Components/test/testassets/BasicTestApp/RouterTest/TestRouterWithOnNavigate.razor b/src/Components/test/testassets/BasicTestApp/RouterTest/TestRouterWithOnNavigate.razor index 51e6f5ac98eb..933512d2bf5b 100644 --- a/src/Components/test/testassets/BasicTestApp/RouterTest/TestRouterWithOnNavigate.razor +++ b/src/Components/test/testassets/BasicTestApp/RouterTest/TestRouterWithOnNavigate.razor @@ -4,7 +4,7 @@ - +

Loading the requested page...

diff --git a/src/Components/test/testassets/ComponentsApp.App/App.razor b/src/Components/test/testassets/ComponentsApp.App/App.razor index e29d2cfddb98..128b9d99fcc0 100644 --- a/src/Components/test/testassets/ComponentsApp.App/App.razor +++ b/src/Components/test/testassets/ComponentsApp.App/App.razor @@ -1,6 +1,6 @@ @using Microsoft.AspNetCore.Components; - + diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/App.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/App.razor index 0e1ea89371b8..7b58ea096f72 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/App.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/App.razor @@ -1,5 +1,5 @@ @*#if (NoAuth) - + @@ -11,7 +11,7 @@ #else - + diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/App.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/App.razor index 9cbaddae5409..48da6e96c988 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/App.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/App.razor @@ -1,5 +1,5 @@ @*#if (NoAuth) - + @@ -11,7 +11,7 @@ #else - +