| File: Routing\RouteTable.cs | Web Access |
| Project: src\aspnetcore\src\Components\Components\src\Microsoft.AspNetCore.Components.csproj (Microsoft.AspNetCore.Components) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; using System.Globalization; using Microsoft.AspNetCore.Components.HotReload; using Microsoft.AspNetCore.Routing.Tree; namespace Microsoft.AspNetCore.Components.Routing; internal sealed class RouteTable(TreeRouter treeRouter) { private readonly TreeRouter _router = treeRouter; private static readonly ConcurrentDictionary<(Type, string), InboundRouteEntry> _routeEntryCache = new(); static RouteTable() { if (HotReloadManager.IsSupported) { HotReloadManager.Default.OnDeltaApplied += _routeEntryCache.Clear; } } public TreeRouter? TreeRouter => _router; [UnconditionalSuppressMessage( "Trimming", "IL2077:Target parameter argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method. The source field does not have matching annotations.", Justification = "We don't trim the user assemblies and this code is only used on the server.")] internal static RouteData ProcessParameters(RouteData endpointRouteData) { if (endpointRouteData.Template != null) { var entry = _routeEntryCache.GetOrAdd( (endpointRouteData.PageType, endpointRouteData.Template), ((Type page, string template) key) => RouteTableFactory.CreateEntry(key.page, key.template)); var routeValueDictionary = new RouteValueDictionary(endpointRouteData.RouteValues); foreach (var kvp in endpointRouteData.RouteValues) { if (kvp.Value is string value) { // At this point the values have already been URL decoded, but we might not have decoded '/' characters. // as that can cause issues when routing the request (You wouldn't be able to accept parameters that contained '/'). // To be consistent with existing Blazor quirks that used Uri.UnescapeDataString, we'll replace %2F with /. // We don't want to call Uri.UnescapeDataString here as that would decode other characters that we don't want to decode, // for example, any value that was "double" encoded (for whatever reason) within the original URL. routeValueDictionary[kvp.Key] = value.Replace("%2F", "/", StringComparison.OrdinalIgnoreCase); } } ProcessParameters(entry, routeValueDictionary); return new RouteData(endpointRouteData.PageType, routeValueDictionary) { Template = endpointRouteData.Template }; } else { return endpointRouteData; } } public void Route(RouteContext routeContext) { _router.Route(routeContext); if (routeContext.Entry != null) { ProcessParameters(routeContext.Entry, routeContext.RouteValues); } if (routeContext.RouteValues != null && routeContext.RouteValues.Count == 0) { routeContext.RouteValues = null!; } return; } private static void ProcessParameters(InboundRouteEntry entry, RouteValueDictionary routeValues) { // Add null values for unused route parameters. if (entry.UnusedRouteParameterNames != null) { foreach (var parameter in entry.UnusedRouteParameterNames) { routeValues[parameter] = null; } } foreach (var kvp in routeValues) { if (kvp.Value is string value) { // At this point the values have already been URL decoded, but we might not have decoded '/' characters. // as that can cause issues when routing the request (You wouldn't be able to accept parameters that contained '/'). // To be consistent with existing Blazor quirks that used Uri.UnescapeDataString, we'll replace %2F with /. // We don't want to call Uri.UnescapeDataString here as that would decode other characters that we don't want to decode, // for example, any value that was "double" encoded (for whatever reason) within the original URL. routeValues[kvp.Key] = value.Replace("%2F", "/", StringComparison.OrdinalIgnoreCase); } } foreach (var parameter in entry.RoutePattern.Parameters) { // Add null values for optional route parameters that weren't provided. if (!routeValues.TryGetValue(parameter.Name, out var parameterValue)) { routeValues.Add(parameter.Name, null); } else if (parameter.ParameterPolicies.Count > 0 && !parameter.IsCatchAll) { // If the parameter has some well-known set of route constraints, then we need to convert the value // to the target type. for (var i = 0; i < parameter.ParameterPolicies.Count; i++) { var policy = parameter.ParameterPolicies[i]; switch (policy.Content) { case "bool": routeValues[parameter.Name] = bool.Parse((string)parameterValue!); break; case "datetime": routeValues[parameter.Name] = DateTime.Parse((string)parameterValue!, CultureInfo.InvariantCulture); break; case "decimal": routeValues[parameter.Name] = decimal.Parse((string)parameterValue!, CultureInfo.InvariantCulture); break; case "double": routeValues[parameter.Name] = double.Parse((string)parameterValue!, CultureInfo.InvariantCulture); break; case "float": routeValues[parameter.Name] = float.Parse((string)parameterValue!, CultureInfo.InvariantCulture); break; case "guid": routeValues[parameter.Name] = Guid.Parse((string)parameterValue!, CultureInfo.InvariantCulture); break; case "int": routeValues[parameter.Name] = int.Parse((string)parameterValue!, CultureInfo.InvariantCulture); break; case "long": routeValues[parameter.Name] = long.Parse((string)parameterValue!, CultureInfo.InvariantCulture); break; default: continue; } } } } } }