diff --git a/src/BootstrapBlazor/Components/Tab/BootstrapBlazorAuthorizeView.cs b/src/BootstrapBlazor/Components/Tab/BootstrapBlazorAuthorizeView.cs index fbf60ae680c..01b83611cb6 100644 --- a/src/BootstrapBlazor/Components/Tab/BootstrapBlazorAuthorizeView.cs +++ b/src/BootstrapBlazor/Components/Tab/BootstrapBlazorAuthorizeView.cs @@ -11,7 +11,7 @@ namespace BootstrapBlazor.Components; /// -/// +/// BootstrapBlazorAuthorizeView 组件 /// public class BootstrapBlazorAuthorizeView : ComponentBase { @@ -49,16 +49,14 @@ public class BootstrapBlazorAuthorizeView : ComponentBase [Inject] private IAuthorizationService? AuthorizationService { get; set; } -#if NET6_0_OR_GREATER [Inject] [NotNull] private NavigationManager? NavigationManager { get; set; } -#endif private bool Authorized { get; set; } /// - /// OnInitializedAsync 方法 + /// /// /// protected override async Task OnInitializedAsync() @@ -68,7 +66,7 @@ protected override async Task OnInitializedAsync() } /// - /// BuildRenderTree 方法 + /// /// /// protected override void BuildRenderTree(RenderTreeBuilder builder) @@ -82,9 +80,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) { builder.AddAttribute(index++, kv.Key, kv.Value); } -#if NET6_0_OR_GREATER BuildQueryParameters(); -#endif builder.CloseComponent(); } else @@ -92,7 +88,6 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) builder.AddContent(0, NotAuthorized); } -#if NET6_0_OR_GREATER void BuildQueryParameters() { var queryParameterSupplier = QueryParameterValueSupplier.ForType(Type); @@ -106,6 +101,5 @@ void BuildQueryParameters() queryParameterSupplier.RenderParametersFromQueryString(builder, query); } } -#endif } } diff --git a/src/BootstrapBlazor/Components/Tab/Route/IRouteTable.cs b/src/BootstrapBlazor/Components/Tab/Route/IRouteTable.cs deleted file mode 100644 index bad59907899..00000000000 --- a/src/BootstrapBlazor/Components/Tab/Route/IRouteTable.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -namespace Microsoft.AspNetCore.Components.Routing; - -#if NET5_0 -/// -/// Provides an abstraction over . -/// the legacy route matching logic is removed. -/// -internal interface IRouteTable -{ - void Route(RouteContext routeContext); -} -#endif diff --git a/src/BootstrapBlazor/Components/Tab/Route/RouteConstraint.cs b/src/BootstrapBlazor/Components/Tab/Route/RouteConstraint.cs index 3b3067e24a7..7139243ab1c 100644 --- a/src/BootstrapBlazor/Components/Tab/Route/RouteConstraint.cs +++ b/src/BootstrapBlazor/Components/Tab/Route/RouteConstraint.cs @@ -5,86 +5,6 @@ namespace Microsoft.AspNetCore.Components.Routing; [ExcludeFromCodeCoverage] -#if NET5_0 -internal abstract class RouteConstraint -{ - // 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 RouteConstraint 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?}`. - /// - /// String representation of the constraint - /// Type-specific RouteConstraint object - private static RouteConstraint? CreateRouteConstraint(string constraint) - { - switch (constraint) - { - case "bool": - return new TypeRouteConstraint(bool.TryParse); - case "datetime": - return new TypeRouteConstraint((string str, out DateTime result) - => DateTime.TryParse(str, CultureInfo.InvariantCulture, DateTimeStyles.None, out result)); - case "decimal": - return new TypeRouteConstraint((string str, out decimal result) - => decimal.TryParse(str, NumberStyles.Number, CultureInfo.InvariantCulture, out result)); - case "double": - return new TypeRouteConstraint((string str, out double result) - => double.TryParse(str, NumberStyles.Number, CultureInfo.InvariantCulture, out result)); - case "float": - return new TypeRouteConstraint((string str, out float result) - => float.TryParse(str, NumberStyles.Number, CultureInfo.InvariantCulture, out result)); - case "guid": - return new TypeRouteConstraint(Guid.TryParse); - case "int": - return new TypeRouteConstraint((string str, out int result) - => int.TryParse(str, NumberStyles.Integer, CultureInfo.InvariantCulture, out result)); - case "long": - return new TypeRouteConstraint((string str, out long result) - => long.TryParse(str, NumberStyles.Integer, CultureInfo.InvariantCulture, out result)); - default: - return null; - } - } -} -#else internal static class RouteConstraint { public static UrlValueConstraint Parse(string template, string segment, string constraint) @@ -116,4 +36,3 @@ public static UrlValueConstraint Parse(string template, string segment, string c _ => null, }; } -#endif diff --git a/src/BootstrapBlazor/Components/Tab/Route/RouteContext.cs b/src/BootstrapBlazor/Components/Tab/Route/RouteContext.cs index 41e77c24f5e..685b87a859c 100644 --- a/src/BootstrapBlazor/Components/Tab/Route/RouteContext.cs +++ b/src/BootstrapBlazor/Components/Tab/Route/RouteContext.cs @@ -23,9 +23,7 @@ public RouteContext(string path) public string[] Segments { get; } -#if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] -#endif public Type? Handler { get; set; } public IReadOnlyDictionary? Parameters { get; set; } diff --git a/src/BootstrapBlazor/Components/Tab/Route/RouteEntry.cs b/src/BootstrapBlazor/Components/Tab/Route/RouteEntry.cs index d32e78dc5cd..b14a2c3e31a 100644 --- a/src/BootstrapBlazor/Components/Tab/Route/RouteEntry.cs +++ b/src/BootstrapBlazor/Components/Tab/Route/RouteEntry.cs @@ -9,144 +9,6 @@ namespace Microsoft.AspNetCore.Components.Routing; [ExcludeFromCodeCoverage] -#if NET5_0 -[DebuggerDisplay("Handler = {Handler}, Template = {Template}")] -internal class RouteEntry -{ - public RouteEntry(RouteTemplate template, Type handler, string[] unusedRouteParameterNames) - { - Template = template; - UnusedRouteParameterNames = unusedRouteParameterNames; - Handler = handler; - } - - public RouteTemplate Template { get; } - - public string[] UnusedRouteParameterNames { get; } - - public Type Handler { get; } - - internal void Match(RouteContext context) - { - var pathIndex = 0; - var templateIndex = 0; - Dictionary parameters = null; - // We will iterate over the path segments and the template segments until we have consumed - // one of them. - // There are three cases we need to account here for: - // * Path is shorter than template -> - // * This can match only if we have t-p optional parameters at the end. - // * Path and template have the same number of segments - // * This can happen when the catch-all segment matches 1 segment - // * This can happen when an optional parameter has been specified. - // * This can happen when the route only contains literals and parameters. - // * Path is longer than template -> This can only match if the parameter has a catch-all at the end. - // * We still need to iterate over all the path segments if the catch-all is constrained. - // * We still need to iterate over all the template/path segments before the catch-all - while (pathIndex < context.Segments.Length && templateIndex < Template.Segments.Length) - { - var pathSegment = context.Segments[pathIndex]; - var templateSegment = Template.Segments[templateIndex]; - - var matches = templateSegment.Match(pathSegment, out var match); - if (!matches) - { - // A constraint or literal didn't match - return; - } - - if (!templateSegment.IsCatchAll) - { - // We were dealing with a literal or a parameter, so just advance both cursors. - pathIndex++; - templateIndex++; - - if (templateSegment.IsParameter) - { - parameters ??= new(StringComparer.OrdinalIgnoreCase); - parameters[templateSegment.Value] = match; - } - } - else - { - if (templateSegment.Constraints.Length == 0) - { - - // Unconstrained catch all, we can stop early - parameters ??= new(StringComparer.OrdinalIgnoreCase); - parameters[templateSegment.Value] = string.Join('/', context.Segments, pathIndex, context.Segments.Length - pathIndex); - - // Mark the remaining segments as consumed. - pathIndex = context.Segments.Length; - - // Catch-alls are always last. - templateIndex++; - - // We are done, so break out of the loop. - break; - } - else - { - // For constrained catch-alls, we advance the path index but keep the template index on the catch-all. - pathIndex++; - if (pathIndex == context.Segments.Length) - { - parameters ??= new(StringComparer.OrdinalIgnoreCase); - parameters[templateSegment.Value] = string.Join('/', context.Segments, templateIndex, context.Segments.Length - templateIndex); - - // This is important to signal that we consumed the entire template. - templateIndex++; - } - } - } - } - - var hasRemainingOptionalSegments = templateIndex < Template.Segments.Length && - RemainingSegmentsAreOptional(pathIndex, Template.Segments); - - if ((pathIndex == context.Segments.Length && templateIndex == Template.Segments.Length) || hasRemainingOptionalSegments) - { - if (hasRemainingOptionalSegments) - { - parameters ??= new Dictionary(StringComparer.Ordinal); - AddDefaultValues(parameters, templateIndex, Template.Segments); - } - if (UnusedRouteParameterNames?.Length > 0) - { - parameters ??= new Dictionary(StringComparer.Ordinal); - for (var i = 0; i < UnusedRouteParameterNames.Length; i++) - { - parameters[UnusedRouteParameterNames[i]] = null; - } - } - context.Handler = Handler; - context.Parameters = parameters; - } - } - - private void AddDefaultValues(Dictionary parameters, int templateIndex, TemplateSegment[] segments) - { - for (var i = templateIndex; i < segments.Length; i++) - { - var currentSegment = segments[i]; - parameters[currentSegment.Value] = null; - } - } - - private bool RemainingSegmentsAreOptional(int index, TemplateSegment[] segments) - { - for (var i = index; index < segments.Length - 1; index++) - { - if (!segments[i].IsOptional) - { - return false; - } - } - - return segments[^1].IsOptional || segments[^1].IsCatchAll; - } -} -#else [DebuggerDisplay("Handler = {Handler}, Template = {Template}")] internal class RouteEntry { @@ -284,4 +146,5 @@ private bool RemainingSegmentsAreOptional(int index, TemplateSegment[] segments) return segments[^1].IsOptional || segments[^1].IsCatchAll; } } -#endif + +#nullable restore warnings diff --git a/src/BootstrapBlazor/Components/Tab/Route/RouteKey.cs b/src/BootstrapBlazor/Components/Tab/Route/RouteKey.cs index a619b7447d5..d35d2c2aaff 100644 --- a/src/BootstrapBlazor/Components/Tab/Route/RouteKey.cs +++ b/src/BootstrapBlazor/Components/Tab/Route/RouteKey.cs @@ -5,7 +5,6 @@ namespace Microsoft.AspNetCore.Components.Routing; -#if NET6_0_OR_GREATER [ExcludeFromCodeCoverage] internal readonly struct RouteKey : IEquatable { @@ -61,4 +60,3 @@ public override int GetHashCode() return HashCode.Combine(AppAssembly, AdditionalAssemblies.Count); } } -#endif diff --git a/src/BootstrapBlazor/Components/Tab/Route/RouteTable.cs b/src/BootstrapBlazor/Components/Tab/Route/RouteTable.cs index 94ab0bc298c..c2f6018ec46 100644 --- a/src/BootstrapBlazor/Components/Tab/Route/RouteTable.cs +++ b/src/BootstrapBlazor/Components/Tab/Route/RouteTable.cs @@ -5,11 +5,7 @@ namespace Microsoft.AspNetCore.Components.Routing; [ExcludeFromCodeCoverage] -#if NET5_0 -internal class RouteTable : IRouteTable -#else internal class RouteTable -#endif { public RouteTable(RouteEntry[] routes) { diff --git a/src/BootstrapBlazor/Components/Tab/Route/RouteTableFactory.cs b/src/BootstrapBlazor/Components/Tab/Route/RouteTableFactory.cs index e6b86ea3f60..ab6c3b17da4 100644 --- a/src/BootstrapBlazor/Components/Tab/Route/RouteTableFactory.cs +++ b/src/BootstrapBlazor/Components/Tab/Route/RouteTableFactory.cs @@ -7,234 +7,6 @@ namespace Microsoft.AspNetCore.Components.Routing; -#if NET5_0 -/// -/// Resolves components for an application. -/// -[ExcludeFromCodeCoverage] -internal static class RouteTableFactory - { - private static readonly ConcurrentDictionary Cache = - new ConcurrentDictionary(); - public static readonly IComparer RoutePrecedence = Comparer.Create(RouteComparison); - - public static RouteTable 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 RouteTable 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 RouteTable Create(Dictionary templatesByHandler) - { - var routes = new List(); - foreach (var keyValuePair in templatesByHandler) - { - var parsedTemplates = keyValuePair.Value.Select(v => TemplateParser.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 RouteEntry(parsedTemplate, keyValuePair.Key, unusedRouteParameterNames); - routes.Add(entry); - } - } - - return new RouteTable(routes.OrderBy(id => id, RoutePrecedence).ToArray()); - } - - private static string[] GetParameterNames(RouteTemplate 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(RouteEntry x, RouteEntry y) - { - if (ReferenceEquals(x, y)) - { - return 0; - } - - var xTemplate = x.Template; - var yTemplate = y.Template; - var minSegments = Math.Min(xTemplate.Segments.Length, yTemplate.Segments.Length); - var currentResult = 0; - for (var i = 0; i < minSegments; i++) - { - var xSegment = xTemplate.Segments[i]; - var ySegment = yTemplate.Segments[i]; - - var xRank = GetRank(xSegment); - var yRank = GetRank(ySegment); - - currentResult = xRank.CompareTo(yRank); - - // If they are both literals we can disambiguate - if ((xRank, yRank) == (0, 0)) - { - currentResult = StringComparer.OrdinalIgnoreCase.Compare(xSegment.Value, ySegment.Value); - } - - if (currentResult != 0) - { - break; - } - } - - if (currentResult == 0) - { - currentResult = xTemplate.Segments.Length.CompareTo(yTemplate.Segments.Length); - } - - if (currentResult == 0) - { - throw new InvalidOperationException($@"The following routes are ambiguous: - '{x.Template.TemplateText}' in '{x.Handler.FullName}' - '{y.Template.TemplateText}' in '{y.Handler.FullName}' - "); - } - - return currentResult; - } - - private static int GetRank(TemplateSegment xSegment) - { - return xSegment switch - { - // Literal - { IsParameter: false } => 0, - // Parameter with constraints - { IsParameter: true, IsCatchAll: false, Constraints: { Length: > 0 } } => 1, - // Parameter without constraints - { IsParameter: true, IsCatchAll: false, Constraints: { Length: 0 } } => 2, - // Catch all parameter with constraints - { IsParameter: true, IsCatchAll: true, Constraints: { Length: > 0 } } => 3, - // Catch all parameter without constraints - { IsParameter: true, IsCatchAll: true, Constraints: { Length: 0 } } => 4, - // The segment is not correct - _ => throw new InvalidOperationException($"Unknown segment definition '{xSegment}.") - }; - } - - 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(); - } - } - } -#else /// /// Resolves components for an application. /// @@ -468,4 +240,3 @@ private static int GetRank(TemplateSegment xSegment) }; } } -#endif diff --git a/src/BootstrapBlazor/Components/Tab/Route/RouteTemplate.cs b/src/BootstrapBlazor/Components/Tab/Route/RouteTemplate.cs index cde3c57bbf0..bd07dc010b9 100644 --- a/src/BootstrapBlazor/Components/Tab/Route/RouteTemplate.cs +++ b/src/BootstrapBlazor/Components/Tab/Route/RouteTemplate.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Website: https://www.blazor.zone or https://argozhang.github.io/ - using System.Diagnostics; namespace Microsoft.AspNetCore.Components.Routing; @@ -16,10 +15,6 @@ public RouteTemplate(string templateText, TemplateSegment[] segments) TemplateText = templateText; Segments = segments; -#if NET5_0 - OptionalSegmentsCount = segments.Count(template => template.IsOptional); - ContainsCatchAllSegment = segments.Any(template => template.IsCatchAll); -#else for (var i = 0; i < segments.Length; i++) { var segment = segments[i]; @@ -32,7 +27,6 @@ public RouteTemplate(string templateText, TemplateSegment[] segments) ContainsCatchAllSegment = true; } } -#endif } public string TemplateText { get; } diff --git a/src/BootstrapBlazor/Components/Tab/Route/StringSegmentAccumulator.cs b/src/BootstrapBlazor/Components/Tab/Route/StringSegmentAccumulator.cs index 1fd988a302b..b1e4cd8fd5e 100644 --- a/src/BootstrapBlazor/Components/Tab/Route/StringSegmentAccumulator.cs +++ b/src/BootstrapBlazor/Components/Tab/Route/StringSegmentAccumulator.cs @@ -4,7 +4,6 @@ namespace Microsoft.AspNetCore.Components.Routing; -#if NET6_0_OR_GREATER // This is very similar to Microsoft.Extensions.Primitives.StringValues, except it works in terms // of ReadOnlyMemory rather than string, so the querystring handling logic doesn't need to // allocate per-value when tracking things that will be parsed as value types. @@ -64,4 +63,3 @@ public void Add(ReadOnlyMemory value) } } } -#endif diff --git a/src/BootstrapBlazor/Components/Tab/Route/TemplateSegment.cs b/src/BootstrapBlazor/Components/Tab/Route/TemplateSegment.cs index b889439f530..93f2ab7a9f6 100644 --- a/src/BootstrapBlazor/Components/Tab/Route/TemplateSegment.cs +++ b/src/BootstrapBlazor/Components/Tab/Route/TemplateSegment.cs @@ -5,146 +5,6 @@ namespace Microsoft.AspNetCore.Components.Routing; [ExcludeFromCodeCoverage] -#if NET5_0 -internal class TemplateSegment -{ - public TemplateSegment(string template, string segment, bool isParameter) - { - IsParameter = isParameter; - - IsCatchAll = isParameter && segment.StartsWith('*'); - - if (IsCatchAll) - { - // Only one '*' currently allowed - Value = segment[1..]; - - var invalidCharacterIndex = Value.IndexOf('*'); - if (invalidCharacterIndex != -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 parameters that do not contain a token separating a type constraint. - if (IsParameter) - { - if (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. - var questionMarkIndex = Value.IndexOf('?'); - if (questionMarkIndex == Value.Length - 1) - { - IsOptional = true; - Value = Value[0..^1]; - } - // If the `?` optional marker shows up in the segment but not at the very end, - // then throw an error. - else if (questionMarkIndex >= 0) - { - 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."); - } - - Value = tokens[0]; - IsOptional = tokens[^1].EndsWith('?'); - if (IsOptional) - { - tokens[^1] = tokens[^1][0..^1]; - } - - Constraints = new RouteConstraint[tokens.Length - 1]; - for (var i = 1; i < tokens.Length; i++) - { - Constraints[i - 1] = RouteConstraint.Parse(template, segment, tokens[i]); - } - } - } - else - { - Constraints = Array.Empty(); - } - - 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 RouteConstraint[] 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); - } - } - - public override string ToString() => this switch - { - { IsParameter: true, IsOptional: false, IsCatchAll: false, Constraints: { Length: 0 } } => $"{{{Value}}}", - { IsParameter: true, IsOptional: false, IsCatchAll: false, Constraints: { Length: > 0 } } => $"{{{Value}:{string.Join(':', Constraints.Select(c => c.ToString()))}}}", - { IsParameter: true, IsOptional: true, Constraints: { Length: 0 } } => $"{{{Value}?}}", - { IsParameter: true, IsOptional: true, Constraints: { Length: > 0 } } => $"{{{Value}:{string.Join(':', Constraints.Select(c => c.ToString()))}?}}", - { IsParameter: true, IsCatchAll: true, Constraints: { Length: 0 } } => $"{{*{Value}}}", - { IsParameter: true, IsCatchAll: true, Constraints: { Length: > 0 } } => $"{{*{Value}:{string.Join(':', Constraints.Select(c => c.ToString()))}?}}", - { IsParameter: false } => Value, - _ => throw new InvalidOperationException("Invalid template segment.") - }; -} -#else internal class TemplateSegment { public TemplateSegment(string template, string segment, bool isParameter) @@ -283,4 +143,3 @@ public bool Match(string pathSegment, out object? matchedParameterValue) _ => throw new InvalidOperationException("Invalid template segment.") }; } -#endif diff --git a/src/BootstrapBlazor/Components/Tab/Route/TypeRouteConstraint.cs b/src/BootstrapBlazor/Components/Tab/Route/TypeRouteConstraint.cs deleted file mode 100644 index 3295a461c6f..00000000000 --- a/src/BootstrapBlazor/Components/Tab/Route/TypeRouteConstraint.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -namespace Microsoft.AspNetCore.Components.Routing; - -#if NET5_0 -/// -/// A route constraint that requires the value to be parseable as a specified type. -/// -/// The type to which the value must be parseable. -[ExcludeFromCodeCoverage] -internal class TypeRouteConstraint : RouteConstraint -{ - public delegate bool TryParseDelegate(string str, [MaybeNullWhen(false)] out T result); - - private readonly TryParseDelegate _parser; - - public TypeRouteConstraint(TryParseDelegate 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; - } - } - - public override string ToString() => typeof(T) switch - { - var x when x == typeof(bool) => "bool", - var x when x == typeof(DateTime) => "datetime", - var x when x == typeof(decimal) => "decimal", - var x when x == typeof(double) => "double", - var x when x == typeof(float) => "float", - var x when x == typeof(Guid) => "guid", - var x when x == typeof(int) => "int", - var x when x == typeof(long) => "long", - var x => x.Name.ToLowerInvariant() - }; -} -#endif diff --git a/src/BootstrapBlazor/Components/Tab/Route/UrlValueConstraint.cs b/src/BootstrapBlazor/Components/Tab/Route/UrlValueConstraint.cs index c3f3ee164e3..5ed24420434 100644 --- a/src/BootstrapBlazor/Components/Tab/Route/UrlValueConstraint.cs +++ b/src/BootstrapBlazor/Components/Tab/Route/UrlValueConstraint.cs @@ -7,7 +7,6 @@ namespace Microsoft.AspNetCore.Components.Routing; -#if NET6_0_OR_GREATER /// /// Shared logic for parsing tokens from route values and querystring values. /// @@ -183,4 +182,3 @@ bool TryParseNullable(ReadOnlySpan value, [MaybeNullWhen(false)] out T? re } } } -#endif