Skip to content

Commit a70e12c

Browse files
refactor(sitemap): simplify ExtractIndexableRoutes to derive from static routes
Address PR feedback: - Remove IsApiController() and ContainsRouteParameters() helpers — eliminates code duplication between ExtractStaticRoutes and ExtractIndexableRoutes - Derive indexable routes from _StaticRoutes via LINQ filters, so the two sets can never diverge unintentionally - Add Identity route exclusion (was lost when inline filter was removed) - Cache route-parameter regex as static readonly to avoid repeated Regex construction on each call (no more Regex.IsMatch with a pattern literal) - Remove 'using Microsoft.AspNetCore.Mvc.Abstractions' — no longer needed
1 parent 31dbfdc commit a70e12c

1 file changed

Lines changed: 8 additions & 76 deletions

File tree

Lines changed: 8 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
using Microsoft.AspNetCore.Mvc;
2-
using Microsoft.AspNetCore.Mvc.Abstractions;
31
using Microsoft.AspNetCore.Mvc.Infrastructure;
42
using System.Text.RegularExpressions;
53

64
namespace EssentialCSharp.Web.Services;
75

86
public class RouteConfigurationService : IRouteConfigurationService
97
{
8+
private static readonly Regex s_routeParameterRegex =
9+
new(@"\{[^}]+\}|\[[^\]]+\]", RegexOptions.Compiled);
10+
1011
private readonly IActionDescriptorCollectionProvider _ActionDescriptorCollectionProvider;
1112
private readonly HashSet<string> _StaticRoutes;
1213
private readonly HashSet<string> _IndexableRoutes;
@@ -72,80 +73,11 @@ private HashSet<string> ExtractStaticRoutes()
7273

7374
private HashSet<string> ExtractIndexableRoutes()
7475
{
75-
var indexableRoutes = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
76-
77-
// Get all action descriptors
78-
var actionDescriptors = _ActionDescriptorCollectionProvider.ActionDescriptors.Items;
79-
80-
foreach (var actionDescriptor in actionDescriptors)
81-
{
82-
// Skip if controller is marked with [ApiController]
83-
if (IsApiController(actionDescriptor))
84-
continue;
85-
86-
// Look for route attributes
87-
if (actionDescriptor.AttributeRouteInfo?.Template != null)
88-
{
89-
string template = actionDescriptor.AttributeRouteInfo.Template;
90-
91-
// Skip routes with parameters (e.g., {chapter}, {id:guid}, [optional])
92-
if (ContainsRouteParameters(template))
93-
continue;
94-
95-
// Skip routes starting with /api/
96-
if (template.StartsWith("/api/", StringComparison.OrdinalIgnoreCase))
97-
continue;
98-
99-
// Remove leading slash and add to our set
100-
string routePath = template.TrimStart('/').ToLowerInvariant();
101-
indexableRoutes.Add(routePath);
102-
}
103-
104-
// Skip the default fallback route (Index action in HomeController)
105-
if (actionDescriptor.RouteValues.TryGetValue("action", out var action) && action == "Index")
106-
continue;
107-
108-
// Skip Error actions
109-
if (action == "Error")
110-
continue;
111-
112-
// For actions without attribute routes, use conventional routing
113-
if (actionDescriptor.AttributeRouteInfo?.Template == null &&
114-
actionDescriptor.RouteValues.TryGetValue("action", out var actionName) &&
115-
actionDescriptor.RouteValues.TryGetValue("controller", out var controllerName) &&
116-
controllerName?.Equals("Home", StringComparison.OrdinalIgnoreCase) == true &&
117-
actionName != null)
118-
{
119-
// Use the action name directly as the route
120-
indexableRoutes.Add(actionName.ToLowerInvariant());
121-
}
122-
}
123-
124-
return indexableRoutes;
125-
}
126-
127-
private static bool IsApiController(ActionDescriptor actionDescriptor)
128-
{
129-
// Check for [ApiController] attribute
130-
if (actionDescriptor.EndpointMetadata?.OfType<ApiControllerAttribute>().Any() == true)
131-
return true;
132-
133-
// Check if controller inherits from ControllerBase (not Controller)
134-
if (actionDescriptor.RouteValues.TryGetValue("controller", out var controllerName))
135-
{
136-
// Known API controllers
137-
var apiControllers = new[] { "ListingSourceCode", "Chat", "McpToken", "MCP" };
138-
if (apiControllers.Contains(controllerName, StringComparer.OrdinalIgnoreCase))
139-
return true;
140-
}
141-
142-
return false;
143-
}
144-
145-
private static bool ContainsRouteParameters(string template)
146-
{
147-
// Match {param}, {param:constraint}, [optional], etc.
148-
return Regex.IsMatch(template, @"\{[^}]+\}|\[[^\]]+\]");
76+
return _StaticRoutes
77+
.Where(route => !route.StartsWith("api/", StringComparison.OrdinalIgnoreCase))
78+
.Where(route => !s_routeParameterRegex.IsMatch(route))
79+
.Where(route => !route.Contains("identity", StringComparison.OrdinalIgnoreCase))
80+
.ToHashSet(StringComparer.OrdinalIgnoreCase);
14981
}
15082
}
15183

0 commit comments

Comments
 (0)