ASP.NET Core WWW Subdomain Redirect

Recently I was working on a .NET Core project for a client who wanted the application hosted on SSL. Further, for SEO purposes, they wanted the application to automatically redirect requests for https://somedomain.com to the www subdomain - i.e. https://www.somedomain.com.

I was thrilled with how easy it was to satisfy the first requirement in .NET Core - Microsoft has made the process simple with the RequireHttpsAttribute class, so the application returns an HTTP 301 redirect permanent response when non-https requests are received:

services.AddMvc(options => {
options.Filters.Add(new RequireHttpsAttribute{
Permanent = true
});
});

I was quite surprised when I could not find the equivalent functionality for a www subdomain redirect. Web searches only turned up the IIS URL rewriting approach, which I have used in the past on other projects, but I was really disappointed that I couldn't solve it the same way as the SSL requirement. Or could I? (The astute reader will reckon the answer is yes, based on the existence of this post.)

The fact that Microsoft has made a lot of the .NET source code available helped by showing how the https attribute does its work, and this gave me a blueprint for what needed to be done (reference the RequireHttpsAttribute source). We just do the same thing except with gophers. (10 extra points for you if you know the source of that quote.)

A few minutes later and I had exactly what was needed - one additional property was required since Visual Studio did not like a www subdomain request during debugging sessions! So now I can require both https and www in the requests, with HTTP 301 responses being sent out when either SSL or www are missing from the request:

services.AddMvc(options => {
options.Filters.Add(new RequireHttpsAttribute{
Permanent = true
});
options.Filters.Add(new RequireWwwAttribute{
IgnoreLocalhost = true,
Permanent = true
});
});

So (finally) what you came here for, right? The attribute code:

[AttributeUsage(AttributeTargets.Class
| AttributeTargets.Method,
Inherited = true,
AllowMultiple = false)]
public class RequireWwwAttribute : Attribute, IAuthorizationFilter, IOrderedFilter {

private bool? permanent;
public bool Permanent {
get => permanent ?? true;
set => permanent = value;
}

private bool? ignoreLocalhost;
public bool IgnoreLocalhost {
get => ignoreLocalhost ?? true;
set => ignoreLocalhost = value;
}

public int Order { get; set; }

public void OnAuthorization(
AuthorizationFilterContext context) {
if (context == null)
throw new ArgumentNullException(nameof(context));

var req = context.HttpContext.Request;
var host = req.Host;

var isLocalHost = string.Equals(host.Host,
"localhost", StringComparison.OrdinalIgnoreCase);
if (IgnoreLocalhost && isLocalHost) {
return;
}

if (host.Host.StartsWith("www",
StringComparison.OrdinalIgnoreCase)) {
return;
}

var optionsAccessor = context.HttpContext
.RequestServices
.GetRequiredService<IOptions<MvcOptions>>();
var permanentValue = permanent
?? optionsAccessor.Value.RequireHttpsPermanent;

//carriage return below added for legibility
//it is not actually present in the working code
var newPath = $"{req.Scheme}://www.{host.Value}
{req.PathBase}{req.Path}{req.QueryString}";
context.Result = new RedirectResult(
newPath, permanentValue);

}

}

Good luck using this attribute, and may all your code be groovy!

Author Avatar
Share this: