From e1ced4deab57eabc4b7ca904e2b76649d88c397b Mon Sep 17 00:00:00 2001 From: Benjamin Michaelis Date: Sun, 17 May 2026 08:50:04 -0700 Subject: [PATCH 1/3] Extract hard-coded routes into centralized constants (issue #747) - Add Constants/RouteConstants.cs with StaticPages string constants, NonContentRoutes HashSet, and SeoMetadata dictionary for sitemap config - Add src/constants/routes.js mirroring the C# constants for the frontend, exporting ROUTES, NON_CONTENT_ROUTES, NAVIGATION_LINKS, and isContentPagePath - Refactor useSiteShell.js isContentPage to use NON_CONTENT_ROUTES.some() with Array path matching as suggested in issue #747, with percentComplete as a secondary check - Refactor SidebarPanel.vue to import NAVIGATION_LINKS from routes.js, removing the inline hard-coded navLinks definition - Refactor SitemapXmlHelpers.cs to use RouteConstants.SeoMetadata.RouteConfig dictionary lookups instead of switch expressions - Update HomeController.cs Route attributes to reference RouteConstants.StaticPages string constants instead of hard-coded string literals --- .../Constants/RouteConstants.cs | 76 +++++++++++++++++++ .../Controllers/HomeController.cs | 11 +-- .../Helpers/SitemapXmlHelpers.cs | 29 +++---- .../src/components/SidebarPanel.vue | 28 +------ .../src/composables/useSiteShell.js | 17 ++++- EssentialCSharp.Web/src/constants/routes.js | 74 ++++++++++++++++++ 6 files changed, 189 insertions(+), 46 deletions(-) create mode 100644 EssentialCSharp.Web/Constants/RouteConstants.cs create mode 100644 EssentialCSharp.Web/src/constants/routes.js diff --git a/EssentialCSharp.Web/Constants/RouteConstants.cs b/EssentialCSharp.Web/Constants/RouteConstants.cs new file mode 100644 index 00000000..3a96d828 --- /dev/null +++ b/EssentialCSharp.Web/Constants/RouteConstants.cs @@ -0,0 +1,76 @@ +namespace EssentialCSharp.Web.Constants; + +/// +/// Centralized definition of application routes and their metadata. +/// This constant provides a single source of truth for route paths, +/// making it easier to maintain and update routes across the application. +/// +public static class RouteConstants +{ + /// + /// Static page routes that are not content pages (e.g., informational, utility pages). + /// Content pages are dynamically loaded from sitemap.json. + /// + public static class StaticPages + { + public const string Home = "/home"; + public const string About = "/about"; + public const string Guidelines = "/guidelines"; + public const string Announcements = "/announcements"; + public const string TermsOfService = "/termsofservice"; + } + + /// + /// Set of non-content route paths. Use this to determine if a requested path + /// is a static page (non-content) or a content page (from sitemap). + /// + public static readonly HashSet NonContentRoutes = new(StringComparer.OrdinalIgnoreCase) + { + StaticPages.Home, + StaticPages.About, + StaticPages.Guidelines, + StaticPages.Announcements, + StaticPages.TermsOfService + }; + + /// + /// SEO metadata for routes used in sitemap generation. + /// Maps route paths to their change frequency and priority values. + /// + public static class SeoMetadata + { + public enum ChangeFrequency + { + Always, + Hourly, + Daily, + Weekly, + Monthly, + Yearly, + Never + } + + /// + /// Maps route paths to (ChangeFrequency, Priority) tuples for sitemap.xml generation. + /// Priority is a decimal value between 0.0 and 1.0 (0.5 is the default). + /// + public static readonly Dictionary RouteConfig = + new(StringComparer.OrdinalIgnoreCase) + { + { StaticPages.Home, (ChangeFrequency.Monthly, 0.5m) }, + { StaticPages.About, (ChangeFrequency.Monthly, 0.5m) }, + { StaticPages.Guidelines, (ChangeFrequency.Monthly, 0.9m) }, + { StaticPages.Announcements, (ChangeFrequency.Monthly, 0.5m) }, + { StaticPages.TermsOfService, (ChangeFrequency.Yearly, 0.2m) } + }; + } + + /// + /// Determines if the given path represents a content page (from sitemap) + /// versus a static page (non-content). + /// + public static bool IsContentPage(string path) + { + return !NonContentRoutes.Contains(path); + } +} diff --git a/EssentialCSharp.Web/Controllers/HomeController.cs b/EssentialCSharp.Web/Controllers/HomeController.cs index de471129..0ef2e717 100644 --- a/EssentialCSharp.Web/Controllers/HomeController.cs +++ b/EssentialCSharp.Web/Controllers/HomeController.cs @@ -1,4 +1,5 @@ using DotnetSitemapGenerator; +using EssentialCSharp.Web.Constants; using EssentialCSharp.Web.Extensions; using EssentialCSharp.Web.Helpers; using EssentialCSharp.Web.Models; @@ -48,7 +49,7 @@ public IActionResult Index() } } - [Route("/TermsOfService", + [Route(RouteConstants.StaticPages.TermsOfService, Name = "TermsOfService")] public IActionResult TermsOfService() { @@ -56,27 +57,27 @@ public IActionResult TermsOfService() return View(); } - [Route("/Announcements", Name = "Announcements")] + [Route(RouteConstants.StaticPages.Announcements, Name = "Announcements")] public IActionResult Announcements() { ViewBag.PageTitle = "Announcements"; return View(); } - [Route("/about", Name = "about")] + [Route(RouteConstants.StaticPages.About, Name = "about")] public IActionResult About() { ViewBag.PageTitle = "About"; return View(); } - [Route("/home", Name = "home")] + [Route(RouteConstants.StaticPages.Home, Name = "home")] public IActionResult Home() { return View(); } - [Route("/guidelines", Name = "guidelines")] + [Route(RouteConstants.StaticPages.Guidelines, Name = "guidelines")] public IActionResult Guidelines() { ViewBag.PageTitle = "Coding Guidelines"; diff --git a/EssentialCSharp.Web/Helpers/SitemapXmlHelpers.cs b/EssentialCSharp.Web/Helpers/SitemapXmlHelpers.cs index 5db73953..001a163a 100644 --- a/EssentialCSharp.Web/Helpers/SitemapXmlHelpers.cs +++ b/EssentialCSharp.Web/Helpers/SitemapXmlHelpers.cs @@ -1,4 +1,5 @@ using DotnetSitemapGenerator; +using EssentialCSharp.Web.Constants; using EssentialCSharp.Web.Services; namespace EssentialCSharp.Web.Helpers; @@ -74,26 +75,26 @@ private static bool IsSitemapRoute(string route) => private static ChangeFrequency GetChangeFrequencyForRoute(string route) { - return route.ToLowerInvariant() switch + var normalizedRoute = route.TrimStart('/').ToLowerInvariant(); + + if (RouteConstants.SeoMetadata.RouteConfig.TryGetValue(normalizedRoute, out var config)) { - "/termsofservice" => ChangeFrequency.Yearly, - "/announcements" => ChangeFrequency.Monthly, - "/guidelines" => ChangeFrequency.Monthly, - _ => ChangeFrequency.Monthly - }; + return (ChangeFrequency)(int)config.Item1; + } + + return ChangeFrequency.Monthly; } private static decimal GetPriorityForRoute(string route) { - return route.ToLowerInvariant() switch + var normalizedRoute = route.TrimStart('/').ToLowerInvariant(); + + if (RouteConstants.SeoMetadata.RouteConfig.TryGetValue(normalizedRoute, out var config)) { - "/home" => 0.5M, - "/about" => 0.5M, - "/announcements" => 0.5M, - "/guidelines" => 0.9M, - "/termsofservice" => 0.2M, - _ => 0.5M - }; + return config.Item2; + } + + return 0.5M; } } diff --git a/EssentialCSharp.Web/src/components/SidebarPanel.vue b/EssentialCSharp.Web/src/components/SidebarPanel.vue index 22e5f173..7ea3e3db 100644 --- a/EssentialCSharp.Web/src/components/SidebarPanel.vue +++ b/EssentialCSharp.Web/src/components/SidebarPanel.vue @@ -1,35 +1,11 @@