From a6146a71b00a82407e586add1d39b88758e32b79 Mon Sep 17 00:00:00 2001
From: Moritz <101179677+mxritzdev@users.noreply.github.com>
Date: Thu, 7 May 2026 07:28:05 +0200
Subject: [PATCH 1/2] updated to dotnet 10, refactored redirect, moved away
from mooncore
---
LinkRouter.sln | 16 -----
LinkRouter.slnx | 3 +
.../Http/Controllers/RedirectController.cs | 40 ++++++++++---
.../LoggingConsoleFormatter.cs | 60 +++++++++++++++++++
LinkRouter/App/Services/MetricsService.cs | 3 +-
LinkRouter/App/Services/RedirectionService.cs | 52 ++++++----------
LinkRouter/LinkRouter.csproj | 3 +-
LinkRouter/Program.cs | 13 ++--
global.json | 7 ---
9 files changed, 121 insertions(+), 76 deletions(-)
delete mode 100644 LinkRouter.sln
create mode 100644 LinkRouter.slnx
create mode 100644 LinkRouter/App/Implemlementations/LoggingConsoleFormatter.cs
delete mode 100644 global.json
diff --git a/LinkRouter.sln b/LinkRouter.sln
deleted file mode 100644
index 5659e00..0000000
--- a/LinkRouter.sln
+++ /dev/null
@@ -1,16 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LinkRouter", "LinkRouter\LinkRouter.csproj", "{0DEC18EE-615C-4E89-BA5C-B8B4AE83FE94}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {0DEC18EE-615C-4E89-BA5C-B8B4AE83FE94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {0DEC18EE-615C-4E89-BA5C-B8B4AE83FE94}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {0DEC18EE-615C-4E89-BA5C-B8B4AE83FE94}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {0DEC18EE-615C-4E89-BA5C-B8B4AE83FE94}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
-EndGlobal
diff --git a/LinkRouter.slnx b/LinkRouter.slnx
new file mode 100644
index 0000000..aaaca24
--- /dev/null
+++ b/LinkRouter.slnx
@@ -0,0 +1,3 @@
+
+
+
diff --git a/LinkRouter/App/Http/Controllers/RedirectController.cs b/LinkRouter/App/Http/Controllers/RedirectController.cs
index 6a940ad..cf90d5f 100644
--- a/LinkRouter/App/Http/Controllers/RedirectController.cs
+++ b/LinkRouter/App/Http/Controllers/RedirectController.cs
@@ -16,15 +16,39 @@ public RedirectController(Config config, RedirectionService redirectionService)
RedirectionService = redirectionService;
}
- [HttpGet("/{*path}")]
- public async Task RedirectToExternalUrl(string path)
+ [HttpGet("{*path}")]
+ public async Task RedirectToExternalUrl(string? path)
{
- return await RedirectionService.GetRedirect(path);
- }
- [HttpGet("/")]
- public async Task GetRootRoute()
- {
- return await RedirectionService.GetRedirect(string.Empty);
+ path = string.IsNullOrWhiteSpace(path)
+ ? "/"
+ : $"/{path.Trim('/')}/";
+
+
+ if (!RedirectionService.TryGetRedirect(path, out var rawRedirect) || rawRedirect == null)
+ {
+ // metrics for 404
+
+ if (string.IsNullOrEmpty(rawRedirect))
+ return NotFound();
+
+ if (RedirectionService.TryGetErrorCode(rawRedirect, out var notFoundStatusCode))
+ return StatusCode(notFoundStatusCode);
+
+
+ return RedirectPermanent(rawRedirect);
+ }
+
+ // metrics for everything else
+
+
+ if (RedirectionService.TryGetErrorCode(path, out var code))
+ return StatusCode(code);
+
+
+ // metrics for path
+
+
+ return RedirectPermanent(rawRedirect);
}
}
\ No newline at end of file
diff --git a/LinkRouter/App/Implemlementations/LoggingConsoleFormatter.cs b/LinkRouter/App/Implemlementations/LoggingConsoleFormatter.cs
new file mode 100644
index 0000000..e24f67a
--- /dev/null
+++ b/LinkRouter/App/Implemlementations/LoggingConsoleFormatter.cs
@@ -0,0 +1,60 @@
+using Microsoft.Extensions.Logging.Abstractions;
+
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Console;
+
+namespace LinkRouter.App.Implemlementations;
+
+public class LoggingConsoleFormatter : ConsoleFormatter
+{
+ public LoggingConsoleFormatter() : base(nameof(LoggingConsoleFormatter))
+ {
+ }
+
+ public override void Write(
+ in LogEntry logEntry,
+ IExternalScopeProvider? scopeProvider,
+ TextWriter textWriter)
+ {
+ var message = logEntry.Formatter?.Invoke(logEntry.State, logEntry.Exception)
+ ?? logEntry.State?.ToString();
+
+ // Timestamp
+ textWriter.Write(DateTime.Now.ToString("dd.MM.yy HH:mm:ss"));
+ textWriter.Write(' ');
+
+ // Log level
+ textWriter.Write(GetLevelText(logEntry.LogLevel));
+ textWriter.Write(' ');
+
+ // Category
+ textWriter.Write(logEntry.Category);
+ textWriter.Write(": ");
+
+ // Message
+ textWriter.Write(message);
+
+ // Exception (if any)
+ if (logEntry.Exception != null)
+ {
+ textWriter.Write(" | ");
+ textWriter.Write(logEntry.Exception);
+ }
+
+ textWriter.WriteLine();
+ }
+
+ private static string GetLevelText(LogLevel logLevel)
+ {
+ return logLevel switch
+ {
+ LogLevel.Critical => "CRIT",
+ LogLevel.Error => "ERRO",
+ LogLevel.Warning => "WARN",
+ LogLevel.Information => "INFO",
+ LogLevel.Debug => "DEBG",
+ LogLevel.Trace => "TRCE",
+ _ => "NONE"
+ };
+ }
+}
\ No newline at end of file
diff --git a/LinkRouter/App/Services/MetricsService.cs b/LinkRouter/App/Services/MetricsService.cs
index 56a182a..c56ae0c 100644
--- a/LinkRouter/App/Services/MetricsService.cs
+++ b/LinkRouter/App/Services/MetricsService.cs
@@ -1,9 +1,8 @@
-using MoonCore.Attributes;
using Prometheus;
namespace LinkRouter.App.Services;
-[Singleton]
+
public class MetricsService
{
private readonly Counter RouteCounter = Metrics.CreateCounter(
diff --git a/LinkRouter/App/Services/RedirectionService.cs b/LinkRouter/App/Services/RedirectionService.cs
index b6dfefc..58403d9 100644
--- a/LinkRouter/App/Services/RedirectionService.cs
+++ b/LinkRouter/App/Services/RedirectionService.cs
@@ -1,80 +1,62 @@
using LinkRouter.App.Configuration;
using Microsoft.AspNetCore.Mvc;
-using MoonCore.Attributes;
namespace LinkRouter.App.Services;
-[Singleton]
public class RedirectionService
{
private readonly Config Config;
- private readonly MetricsService MetricsService;
- public RedirectionService(Config config, MetricsService metricsService)
+ public RedirectionService(Config config)
{
Config = config;
- MetricsService = metricsService;
}
- public async Task GetRedirect(string path)
+ public bool TryGetRedirect(string path, out string? redirectPath)
{
- if (path == "")
+ redirectPath = null;
+
+ if (path == "/")
{
var url = Config.RootRoute;
- if (TryGetErrorCode(url, out var notFoundStatusCode))
- return new StatusCodeResult(notFoundStatusCode);
-
- await MetricsService.IncrementFound("/");
+ redirectPath = url;
- return new RedirectResult(url);
+ return true;
}
- if (!path.EndsWith("/"))
- path += "/";
-
- path = "/" + path;
-
-
var redirectRoute = Config.CompiledRoutes?.FirstOrDefault(x => x.CompiledPattern.IsMatch(path));
-
if (redirectRoute == null)
{
- await MetricsService.IncrementNotFound(path);
-
if (!Config.NotFoundBehavior.RedirectOn404)
- return new NotFoundResult();
-
+ {
+ return false;
+ }
- if (TryGetErrorCode(Config.NotFoundBehavior.RedirectUrl, out var notFoundStatusCode))
- return new StatusCodeResult(notFoundStatusCode);
+ redirectPath = Config.NotFoundBehavior.RedirectUrl;
- return new RedirectResult(Config.NotFoundBehavior.RedirectUrl);
+ return true;
}
var match = redirectRoute.CompiledPattern.Match(path);
- if (TryGetErrorCode(redirectRoute.RedirectUrl, out var statusCode))
- return new StatusCodeResult(statusCode);
-
-
foreach (var placeholder in redirectRoute.Placeholders)
{
var value = match.Groups[placeholder.Value].Value;
redirectRoute.RedirectUrl = redirectRoute.RedirectUrl.Replace("{" + placeholder.Key + "}", value);
}
- await MetricsService.IncrementFound(path);
+ redirectPath = redirectRoute.RedirectUrl;
- return new RedirectResult(redirectRoute.RedirectUrl);
+ return true;
}
- private bool TryGetErrorCode(string url, out int code)
+ public bool TryGetErrorCode(string path, out int code)
{
- if (Config.ErrorCodePattern.IsMatch(url))
+ if (Config.ErrorCodePattern.IsMatch(path))
{
- var errorCodeMatch = Config.ErrorCodePattern.Match(url);
+ var errorCodeMatch = Config.ErrorCodePattern.Match(path);
code = int.Parse(errorCodeMatch.Groups[1].Value);
return true;
}
diff --git a/LinkRouter/LinkRouter.csproj b/LinkRouter/LinkRouter.csproj
index 07143a2..ff702c5 100644
--- a/LinkRouter/LinkRouter.csproj
+++ b/LinkRouter/LinkRouter.csproj
@@ -1,7 +1,7 @@
- net9.0
+ net10.0
enable
enable
Linux
@@ -9,7 +9,6 @@
-
diff --git a/LinkRouter/Program.cs b/LinkRouter/Program.cs
index 846359f..ae5e530 100644
--- a/LinkRouter/Program.cs
+++ b/LinkRouter/Program.cs
@@ -1,9 +1,8 @@
using System.Text.Json;
using LinkRouter.App.Configuration;
+using LinkRouter.App.Implemlementations;
using LinkRouter.App.Services;
-using MoonCore.Extensions;
-using MoonCore.Helpers;
-using MoonCore.Logging;
+using Microsoft.Extensions.Logging.Console;
using Prometheus;
namespace LinkRouter;
@@ -19,8 +18,8 @@ public static void Main(string[] args)
builder.Services.AddControllers();
builder.Logging.ClearProviders();
-
- builder.Logging.AddAnsiConsole();
+ builder.Logging.AddConsole(options => { options.FormatterName = nameof(LoggingConsoleFormatter); });
+ builder.Logging.AddConsoleFormatter();
builder.Services.AddHostedService();
@@ -39,7 +38,9 @@ public static void Main(string[] args)
builder.Services.AddSingleton(config);
- builder.Services.AutoAddServices();
+ builder.Services.AddSingleton();
+
+ builder.Services.AddSingleton();
builder.Services.AddMetricServer(options => { options.Port = 5000; });
diff --git a/global.json b/global.json
deleted file mode 100644
index 93681ff..0000000
--- a/global.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "sdk": {
- "version": "9.0.0",
- "rollForward": "latestMinor",
- "allowPrerelease": false
- }
-}
\ No newline at end of file
From 6f4ccb20a6f4ffec618c5fc171dbdb2d86ee59ef Mon Sep 17 00:00:00 2001
From: Moritz <101179677+mxritzdev@users.noreply.github.com>
Date: Fri, 19 Jun 2026 19:06:05 +0200
Subject: [PATCH 2/2] Multiple changes
- Improved config readability
- Refactored redirection pipeline
- Moved regex patterns to class
- General Refactoring
- Updated Dependencies
---
.../Http/Controllers/RedirectController.cs | 28 +++++++---------
.../LoggingConsoleFormatter.cs | 2 --
.../App/{Configuration => Models}/Config.cs | 33 ++++++++++++-------
LinkRouter/App/Models/Patterns.cs | 10 ++++++
LinkRouter/App/Services/ConfigWatcher.cs | 4 +--
LinkRouter/App/Services/RedirectionService.cs | 17 ++++++----
LinkRouter/Program.cs | 2 +-
7 files changed, 56 insertions(+), 40 deletions(-)
rename LinkRouter/App/{Configuration => Models}/Config.cs (81%)
create mode 100644 LinkRouter/App/Models/Patterns.cs
diff --git a/LinkRouter/App/Http/Controllers/RedirectController.cs b/LinkRouter/App/Http/Controllers/RedirectController.cs
index cf90d5f..0a62b0f 100644
--- a/LinkRouter/App/Http/Controllers/RedirectController.cs
+++ b/LinkRouter/App/Http/Controllers/RedirectController.cs
@@ -1,4 +1,4 @@
-using LinkRouter.App.Configuration;
+using LinkRouter.App.Models;
using LinkRouter.App.Services;
using Microsoft.AspNetCore.Mvc;
@@ -9,46 +9,42 @@ public class RedirectController : Controller
{
private readonly Config Config;
private readonly RedirectionService RedirectionService;
+ private readonly MetricsService MetricsService;
- public RedirectController(Config config, RedirectionService redirectionService)
+ public RedirectController(Config config, RedirectionService redirectionService, MetricsService metricsService)
{
Config = config;
RedirectionService = redirectionService;
+ MetricsService = metricsService;
}
[HttpGet("{*path}")]
- public async Task RedirectToExternalUrl(string? path)
+ public async Task RedirectTo(string? path)
{
+ Console.WriteLine(path);
path = string.IsNullOrWhiteSpace(path)
? "/"
: $"/{path.Trim('/')}/";
-
if (!RedirectionService.TryGetRedirect(path, out var rawRedirect) || rawRedirect == null)
{
- // metrics for 404
-
if (string.IsNullOrEmpty(rawRedirect))
return NotFound();
- if (RedirectionService.TryGetErrorCode(rawRedirect, out var notFoundStatusCode))
+ if (RedirectionService.TryGetStatusCode(rawRedirect, out var notFoundStatusCode))
return StatusCode(notFoundStatusCode);
+ await MetricsService.IncrementNotFound(path);
- return RedirectPermanent(rawRedirect);
+ return Redirect(rawRedirect);
}
- // metrics for everything else
-
-
- if (RedirectionService.TryGetErrorCode(path, out var code))
+ if (RedirectionService.TryGetStatusCode(path.Trim('/'), out var code))
return StatusCode(code);
+ await MetricsService.IncrementFound(path);
- // metrics for path
-
-
- return RedirectPermanent(rawRedirect);
+ return Redirect(rawRedirect);
}
}
\ No newline at end of file
diff --git a/LinkRouter/App/Implemlementations/LoggingConsoleFormatter.cs b/LinkRouter/App/Implemlementations/LoggingConsoleFormatter.cs
index e24f67a..bff7cfc 100644
--- a/LinkRouter/App/Implemlementations/LoggingConsoleFormatter.cs
+++ b/LinkRouter/App/Implemlementations/LoggingConsoleFormatter.cs
@@ -1,6 +1,4 @@
using Microsoft.Extensions.Logging.Abstractions;
-
-using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Console;
namespace LinkRouter.App.Implemlementations;
diff --git a/LinkRouter/App/Configuration/Config.cs b/LinkRouter/App/Models/Config.cs
similarity index 81%
rename from LinkRouter/App/Configuration/Config.cs
rename to LinkRouter/App/Models/Config.cs
index 5166264..c9410b1 100644
--- a/LinkRouter/App/Configuration/Config.cs
+++ b/LinkRouter/App/Models/Config.cs
@@ -1,13 +1,28 @@
using System.Text;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
-using LinkRouter.App.Models;
-namespace LinkRouter.App.Configuration;
+namespace LinkRouter.App.Models;
public class Config
{
- public string RootRoute { get; set; } = "https://example.com";
+ [JsonPropertyName("RootRedirect")] public string? RootRedirect { get; set; } = "https://example.com";
+
+ // Legacy property, only used during deserialization
+ [Obsolete]
+ [JsonPropertyName("RootRoute")]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
+ public string? LegacyRootRoute
+ {
+ get => null; // never serialize
+ set
+ {
+ if (!string.IsNullOrEmpty(value) && string.IsNullOrEmpty(RootRedirect))
+ {
+ RootRedirect = value;
+ }
+ }
+ }
public NotFoundBehaviorConfig NotFoundBehavior { get; set; } = new();
@@ -39,11 +54,7 @@ public void CompileRoutes()
foreach (var route in Routes)
{
- if (!route.Route.StartsWith("/"))
- route.Route = "/" + route.Route;
-
- if (!route.Route.EndsWith("/"))
- route.Route += "/";
+ route.Route = "/" + route.Route.Trim('/') + "/";
var compiled = new CompiledRoute
{
@@ -55,9 +66,9 @@ public void CompileRoutes()
var escaped = Regex.Escape(route.Route);
- var pattern = new Regex(@"\\\{(\d|\w+)\}", RegexOptions.CultureInvariant);
- var matches = pattern.Matches(escaped);
+
+ var matches = Patterns.PlaceholderPattern.Matches(escaped);
foreach (var match in matches.Select(x => x))
{
@@ -102,7 +113,5 @@ public void CompileRoutes()
CompiledRoutes = compiledRoutes
.ToArray();
}
-
- [JsonIgnore] public static Regex ErrorCodePattern = new(@"\s*\-\>\s*(\d+)\s*$", RegexOptions.Compiled | RegexOptions.CultureInvariant);
}
\ No newline at end of file
diff --git a/LinkRouter/App/Models/Patterns.cs b/LinkRouter/App/Models/Patterns.cs
new file mode 100644
index 0000000..859e109
--- /dev/null
+++ b/LinkRouter/App/Models/Patterns.cs
@@ -0,0 +1,10 @@
+using System.Text.RegularExpressions;
+
+namespace LinkRouter.App.Models;
+
+public static class Patterns
+{
+ public static Regex ErrorCodePattern = new(@"\s*\-\>\s*(\d+)\s*$", RegexOptions.Compiled | RegexOptions.CultureInvariant);
+
+ public static Regex PlaceholderPattern = new (@"\\\{(\d|\w+)\}", RegexOptions.Compiled | RegexOptions.CultureInvariant);
+}
\ No newline at end of file
diff --git a/LinkRouter/App/Services/ConfigWatcher.cs b/LinkRouter/App/Services/ConfigWatcher.cs
index a78570d..cf7079e 100644
--- a/LinkRouter/App/Services/ConfigWatcher.cs
+++ b/LinkRouter/App/Services/ConfigWatcher.cs
@@ -1,5 +1,5 @@
using System.Text.Json;
-using LinkRouter.App.Configuration;
+using LinkRouter.App.Models;
namespace LinkRouter.App.Services;
@@ -47,7 +47,7 @@ private void OnChanged(object sender, FileSystemEventArgs e)
var config = JsonSerializer.Deserialize(content);
Config.Routes = config?.Routes ?? [];
- Config.RootRoute = config?.RootRoute ?? "https://example.com";
+ Config.RootRedirect = config?.RootRedirect ?? "https://example.com";
Logger.LogInformation("Config file changed.");
diff --git a/LinkRouter/App/Services/RedirectionService.cs b/LinkRouter/App/Services/RedirectionService.cs
index 58403d9..336911c 100644
--- a/LinkRouter/App/Services/RedirectionService.cs
+++ b/LinkRouter/App/Services/RedirectionService.cs
@@ -1,5 +1,4 @@
-using LinkRouter.App.Configuration;
-using Microsoft.AspNetCore.Mvc;
+using LinkRouter.App.Models;
namespace LinkRouter.App.Services;
@@ -18,7 +17,10 @@ public bool TryGetRedirect(string path, out string? redirectPath)
if (path == "/")
{
- var url = Config.RootRoute;
+ var url = Config.RootRedirect;
+
+ if (string.IsNullOrEmpty(url))
+ return false;
redirectPath = url;
@@ -52,12 +54,13 @@ public bool TryGetRedirect(string path, out string? redirectPath)
return true;
}
- public bool TryGetErrorCode(string path, out int code)
+ public bool TryGetStatusCode(string path, out int code)
{
- if (Config.ErrorCodePattern.IsMatch(path))
+ var match = Patterns.ErrorCodePattern.Match(path);
+
+ if (match.Success)
{
- var errorCodeMatch = Config.ErrorCodePattern.Match(path);
- code = int.Parse(errorCodeMatch.Groups[1].Value);
+ code = int.Parse(match.Groups[1].Value);
return true;
}
diff --git a/LinkRouter/Program.cs b/LinkRouter/Program.cs
index ae5e530..47033a6 100644
--- a/LinkRouter/Program.cs
+++ b/LinkRouter/Program.cs
@@ -1,6 +1,6 @@
using System.Text.Json;
-using LinkRouter.App.Configuration;
using LinkRouter.App.Implemlementations;
+using LinkRouter.App.Models;
using LinkRouter.App.Services;
using Microsoft.Extensions.Logging.Console;
using Prometheus;