Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions src/Http/Routing/src/Patterns/RoutePattern.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Linq;
using Microsoft.AspNetCore.Routing.Template;

namespace Microsoft.AspNetCore.Routing.Patterns;
Expand Down Expand Up @@ -35,8 +34,6 @@ internal static bool IsRequiredValueAny(object? value)
return object.ReferenceEquals(RequiredValueAny, value);
}

private const string SeparatorString = "/";

internal RoutePattern(
string? rawText,
IReadOnlyDictionary<string, object?> defaults,
Expand Down Expand Up @@ -158,10 +155,7 @@ internal RoutePattern(
// 1. RoutePattern debug string.
// 2. Default IRouteDiagnosticsMetadata value.
// 3. RouteEndpoint display name.
internal string DebuggerToString()
{
return RawText ?? string.Join(SeparatorString, PathSegments.Select(s => s.DebuggerToString()));
}
internal string DebuggerToString() => RoutePatternDebugStringFormatter.Format(this);

[DebuggerDisplay("{DebuggerToString(),nq}")]
private sealed class RequiredValueAnySentinal
Expand Down
103 changes: 103 additions & 0 deletions src/Http/Routing/src/Patterns/RoutePatternDebugStringFormatter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;

namespace Microsoft.AspNetCore.Routing.Patterns;

internal static class RoutePatternDebugStringFormatter
{
private const char Separator = '/';
private const string SeparatorString = "/";

public static string Format(RoutePattern pattern)
{
// If there are no required values that match parameters, use the simple approach
if (pattern.RawText is { Length: > 0 } rawText && !HasMatchingRequiredValues(pattern))
{
return rawText;
}

// Build the string replacing parameters with their required values when available
var segments = new string[pattern.PathSegments.Count];
for (var i = 0; i < pattern.PathSegments.Count; i++)
{
var segment = pattern.PathSegments[i];
var segmentString = GetSegmentDebuggerToString(pattern, segment);
segments[i] = segmentString;
}

var result = string.Join(SeparatorString, segments);

// Preserve leading slash from raw text
if (pattern.RawText is { Length: > 0 } rt && rt[0] == Separator)
{
result = Separator + result;
}

// Return "/" for empty results
if (result.Length == 0)
{
return SeparatorString;
}

return result;
}

private static bool HasMatchingRequiredValues(RoutePattern pattern)
{
if (pattern.RequiredValues.Count == 0)
{
return false;
}

for (var i = 0; i < pattern.Parameters.Count; i++)
{
if (TryGetRequiredValue(pattern, pattern.Parameters[i].Name, out _))
{
return true;
}
}

return false;
}

private static string GetSegmentDebuggerToString(RoutePattern pattern, RoutePatternPathSegment segment)
{
// Simple segment with single parameter that has a required value - just return the required value
if (segment.IsSimple && segment.Parts[0] is RoutePatternParameterPart parameter)
{
if (TryGetRequiredValue(pattern, parameter.Name, out var requiredValue))
{
return requiredValue;
}
return parameter.DebuggerToString();
}

// For complex segments, build the string part by part
var parts = new string[segment.Parts.Count];
for (var i = 0; i < segment.Parts.Count; i++)
{
var part = segment.Parts[i];
parts[i] = part is RoutePatternParameterPart paramPart && TryGetRequiredValue(pattern, paramPart.Name, out var value)
? value
: part.DebuggerToString();
}

return string.Join(string.Empty, parts);
}

private static bool TryGetRequiredValue(RoutePattern pattern, string parameterName, [NotNullWhen(true)] out string? value)
{
if (pattern.RequiredValues.TryGetValue(parameterName, out var requiredValue) &&
!RoutePattern.IsRequiredValueAny(requiredValue) &&
requiredValue?.ToString() is { Length: > 0 } v)
{
value = v;
return true;
}

value = null;
return false;
}
}
17 changes: 15 additions & 2 deletions src/Http/Routing/src/Patterns/RoutePatternParameterPart.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,23 @@ internal override string DebuggerToString()
foreach (var constraint in ParameterPolicies)
{
builder.Append(':');
builder.Append(constraint.ParameterPolicy);
if (constraint.Content is not null)
{
builder.Append(constraint.Content);
}
else if (constraint.ParameterPolicy is Constraints.RegexRouteConstraint regexConstraint)
{
builder.Append("regex(");
builder.Append(regexConstraint.Constraint.ToString());
builder.Append(')');
}
else if (constraint.ParameterPolicy is not null)
{
builder.Append(constraint.ParameterPolicy);
}
}

if (Default != null)
if (Default is not null)
{
builder.Append('=');
builder.Append(Default);
Expand Down
Loading