diff --git a/.gitignore b/.gitignore
index 8a30d25..1b75002 100644
--- a/.gitignore
+++ b/.gitignore
@@ -378,7 +378,6 @@ FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
-!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
diff --git a/src/BitBadger.AspNetCore.CanonicalDomains/BitBadger.AspNetCore.CanonicalDomains.csproj b/src/BitBadger.AspNetCore.CanonicalDomains/BitBadger.AspNetCore.CanonicalDomains.csproj
new file mode 100644
index 0000000..fd80d9d
--- /dev/null
+++ b/src/BitBadger.AspNetCore.CanonicalDomains/BitBadger.AspNetCore.CanonicalDomains.csproj
@@ -0,0 +1,14 @@
+
+
+
+ net7.0
+ enable
+ enable
+
+
+
+
+
+
+
+
diff --git a/src/BitBadger.AspNetCore.CanonicalDomains/CanonicalDomainMiddleware.cs b/src/BitBadger.AspNetCore.CanonicalDomains/CanonicalDomainMiddleware.cs
new file mode 100644
index 0000000..ca875be
--- /dev/null
+++ b/src/BitBadger.AspNetCore.CanonicalDomains/CanonicalDomainMiddleware.cs
@@ -0,0 +1,41 @@
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Extensions;
+
+namespace BitBadger.AspNetCore.CanonicalDomains;
+
+public class CanonicalDomainMiddleware
+{
+ ///
+ /// The domains which should be redirected
+ ///
+ internal static readonly IDictionary CanonicalDomains = new Dictionary();
+
+ ///
+ /// The next middleware in the pipeline to be executed
+ ///
+ private readonly RequestDelegate _next;
+
+ ///
+ /// Constructor
+ ///
+ /// The next middleware in the pipeline to be exectued
+ public CanonicalDomainMiddleware(RequestDelegate next)
+ {
+ _next = next;
+ }
+
+ public async Task InvokeAsync(HttpContext ctx)
+ {
+ var host = ctx.Request.Host.Host;
+ if (CanonicalDomains.ContainsKey(host))
+ {
+ UriBuilder uri = new(ctx.Request.GetDisplayUrl());
+ uri.Host = CanonicalDomains[host];
+ ctx.Response.Redirect(uri.Uri.ToString ());
+ }
+ else
+ {
+ await _next.Invoke(ctx);
+ }
+ }
+}
diff --git a/src/BitBadger.AspNetCore.CanonicalDomains/IApplicationBuilderExtensions.cs b/src/BitBadger.AspNetCore.CanonicalDomains/IApplicationBuilderExtensions.cs
new file mode 100644
index 0000000..7610e9d
--- /dev/null
+++ b/src/BitBadger.AspNetCore.CanonicalDomains/IApplicationBuilderExtensions.cs
@@ -0,0 +1,57 @@
+using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+
+namespace BitBadger.AspNetCore.CanonicalDomains;
+
+///
+/// Extensions on the interface
+///
+public static class IApplicationBuilderExtensions
+{
+ ///
+ /// Initialize and use the canonical domain middleware
+ ///
+ public static IApplicationBuilder UseCanonicalDomains(this IApplicationBuilder app)
+ {
+ void warnForMissingConfig() {
+ var logger = (ILogger?)app.ApplicationServices
+ .GetService(typeof(ILogger));
+ if (logger is not null)
+ {
+ logger.LogWarning("No canonical domain configuration was found; no domains will be redirected");
+ }
+ }
+
+ var config = (IConfiguration)app.ApplicationServices.GetService(typeof(IConfiguration))!;
+
+ var section = config.GetSection("CanonicalDomains");
+ if (section is not null)
+ {
+ foreach (var item in section.GetChildren())
+ {
+ var nonCanonical = item["From"];
+ var canonical = item["To"];
+ if (nonCanonical is not null && canonical is not null)
+ {
+ CanonicalDomainMiddleware.CanonicalDomains.Add(nonCanonical, canonical);
+ }
+ }
+
+ if (CanonicalDomainMiddleware.CanonicalDomains.Count > 0)
+ {
+ app.UseMiddleware ();
+ }
+ else
+ {
+ warnForMissingConfig();
+ }
+ }
+ else
+ {
+ warnForMissingConfig();
+ }
+
+ return app;
+ }
+}
\ No newline at end of file