Skip to content

Commit 8b5340d

Browse files
[Instrumentation.AspNet] Improved route template normalization performance (#3241)
Co-authored-by: Martin Costello <[email protected]>
1 parent 3a5b8ee commit 8b5340d

File tree

2 files changed

+65
-19
lines changed

2 files changed

+65
-19
lines changed

src/OpenTelemetry.Instrumentation.AspNet/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
* Improved performance of replacing static tokens with actual values
6+
in the route template.
7+
([#3241](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/3241))
8+
59
## 1.13.0-beta.1
610

711
Released 2025-Oct-15

src/OpenTelemetry.Instrumentation.AspNet/Implementation/HttpRequestRouteHelper.cs

Lines changed: 61 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright The OpenTelemetry Authors
22
// SPDX-License-Identifier: Apache-2.0
33

4+
using System.Runtime.CompilerServices;
5+
using System.Text;
46
using System.Web;
57
using System.Web.Routing;
68

@@ -48,6 +50,20 @@ internal sealed class HttpRequestRouteHelper
4850
return template;
4951
}
5052

53+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
54+
private static bool CompareStringToSubstring(string example, string target, int start)
55+
{
56+
for (int i = 0; i < example.Length; i++)
57+
{
58+
if (target[start + 1 + i] != example[i])
59+
{
60+
return false;
61+
}
62+
}
63+
64+
return true;
65+
}
66+
5167
private static string PrepareRouteTemplate(Route route, RouteData routeData)
5268
{
5369
const string controllerToken = "controller";
@@ -56,32 +72,58 @@ private static string PrepareRouteTemplate(Route route, RouteData routeData)
5672
var template = route.Url;
5773
var controller = (string)routeData.Values[controllerToken];
5874
var action = (string)routeData.Values[actionToken];
75+
var hasController = !string.IsNullOrWhiteSpace(controller);
76+
var hasAction = !string.IsNullOrWhiteSpace(action);
77+
var sb = new StringBuilder(template.Length);
5978

60-
if (!string.IsNullOrWhiteSpace(controller))
79+
int i = 0;
80+
while (i < template.Length)
6181
{
62-
template = template.Replace($"{{{controllerToken}}}", controller);
63-
}
82+
if (template[i] == '{')
83+
{
84+
int end = template.IndexOf('}', i + 1);
85+
if (end != -1)
86+
{
87+
if (hasController && CompareStringToSubstring(controllerToken, template, i))
88+
{
89+
sb.Append(controller);
90+
}
91+
else if (hasAction && CompareStringToSubstring(actionToken, template, i))
92+
{
93+
sb.Append(action);
94+
}
95+
else
96+
{
97+
var defaults = route.Defaults;
98+
var values = routeData.Values;
99+
var token = template.Substring(i + 1, end - i - 1);
100+
101+
if (defaults.ContainsKey(token) && !values.ContainsKey(token))
102+
{
103+
// Ignore defaults with no values.
104+
}
105+
else
106+
{
107+
sb.Append('{').Append(token).Append('}');
108+
}
109+
}
110+
111+
i = end + 1;
112+
continue;
113+
}
114+
}
64115

65-
if (!string.IsNullOrWhiteSpace(action))
66-
{
67-
template = template.Replace($"{{{actionToken}}}", action);
116+
sb.Append(template[i]);
117+
i++;
68118
}
69119

70-
// Remove defaults with no values.
71-
var defaultKeys = route.Defaults.Keys;
72-
var valueKeys = routeData.Values.Keys;
73-
74-
foreach (var token in defaultKeys)
120+
// Normalizes endings by removing trailing slashes.
121+
int len = sb.Length;
122+
while (len > 0 && sb[len - 1] == '/')
75123
{
76-
if (valueKeys.Contains(token))
77-
{
78-
continue;
79-
}
80-
81-
template = template.Replace($"{{{token}}}", string.Empty);
124+
len--;
82125
}
83126

84-
return template
85-
.TrimEnd('/'); // Normalizes endings
127+
return sb.ToString(0, len);
86128
}
87129
}

0 commit comments

Comments
 (0)