Skip to content
Merged
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
4 changes: 4 additions & 0 deletions src/OpenTelemetry.Instrumentation.AspNet/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

* Improved performance of replacing static tokens with actual values
in the route template.
([#3241](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/3241))

## 1.13.0-beta.1

Released 2025-Oct-15
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

using System.Runtime.CompilerServices;
using System.Text;
using System.Web;
using System.Web.Routing;

Expand Down Expand Up @@ -48,6 +50,20 @@ internal sealed class HttpRequestRouteHelper
return template;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool CompareStringToSubstring(string example, string target, int start)
{
for (int i = 0; i < example.Length; i++)
{
if (target[start + 1 + i] != example[i])
{
return false;
}
}

return true;
}

private static string PrepareRouteTemplate(Route route, RouteData routeData)
{
const string controllerToken = "controller";
Expand All @@ -56,32 +72,58 @@ private static string PrepareRouteTemplate(Route route, RouteData routeData)
var template = route.Url;
var controller = (string)routeData.Values[controllerToken];
var action = (string)routeData.Values[actionToken];
var hasController = !string.IsNullOrWhiteSpace(controller);
var hasAction = !string.IsNullOrWhiteSpace(action);
var sb = new StringBuilder(template.Length);

if (!string.IsNullOrWhiteSpace(controller))
int i = 0;
while (i < template.Length)
{
template = template.Replace($"{{{controllerToken}}}", controller);
}
if (template[i] == '{')
{
int end = template.IndexOf('}', i + 1);
if (end != -1)
{
if (hasController && CompareStringToSubstring(controllerToken, template, i))
{
sb.Append(controller);
}
else if (hasAction && CompareStringToSubstring(actionToken, template, i))
{
sb.Append(action);
}
else
{
var defaults = route.Defaults;
var values = routeData.Values;
var token = template.Substring(i + 1, end - i - 1);

if (defaults.ContainsKey(token) && !values.ContainsKey(token))
{
// Ignore defaults with no values.
}
else
{
sb.Append('{').Append(token).Append('}');
}
}

i = end + 1;
continue;
}
}

if (!string.IsNullOrWhiteSpace(action))
{
template = template.Replace($"{{{actionToken}}}", action);
sb.Append(template[i]);
i++;
}

// Remove defaults with no values.
var defaultKeys = route.Defaults.Keys;
var valueKeys = routeData.Values.Keys;

foreach (var token in defaultKeys)
// Normalizes endings by removing trailing slashes.
int len = sb.Length;
while (len > 0 && sb[len - 1] == '/')
{
if (valueKeys.Contains(token))
{
continue;
}

template = template.Replace($"{{{token}}}", string.Empty);
len--;
}

return template
.TrimEnd('/'); // Normalizes endings
return sb.ToString(0, len);
}
}