Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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(Separator, 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;
}
}

// 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) &&
requiredValue is not null &&
!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);
builder.Append(')');
}
Comment thread
JamesNK marked this conversation as resolved.
Resolved
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
Loading