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