Redirect to https and www or non-www in ASP.NET Core

In this post I describe how you can redirect requests to https and www or non-www with a rewrite rule in ASP.NET Core. The reasons to do a 301 redirect is improved SEO and higher website security.

It is better to have only one version of your website in search engines, if you have several versions of your website (http://, http://www., https://, https://www.) you might be penalized for duplicate content.

Hyper Text Transfer Protocol Secure (HTTPS) is a secure version of the Hyper Text Transfer Protocol (HTTP). Https means that all communication between a browser and your server is encrypted, this makes it very difficult for a man in the middle to get information from data that is sent between a browser and a server.

Why a 301 redirect? A 301 redirect is a permanent redirect from one url to another url, this means that search engines knows that the url that you redirect to is the correct url and that all search ranking score should apply to this url.

Redirect rule

I have created a rewrite rule that is responsible for redirects. I have used a Middleware for redirections before, but it stopped to work when I was upgrading from ASP.NET Core 2.2 to ASP.NET Core 3.1. This rule will only make one redirection, I think that it is better to only do one redirect compared to do multiple redirects if we use multiple rules.

using System;
using Microsoft.Net.Http.Headers;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Rewrite;

namespace Annytab.Rules
{
    /// <summary>
    /// This class handles redirects to https, www, non-www
    /// </summary>
    public class RedirectHttpsWwwNonWwwRule : IRule
    {
        #region Variables

        public int status_code { get; set; }
        public bool redirect_to_www { get; set; }
        public bool redirect_to_non_www { get; set; }
        public bool redirect_to_https { get; set; }
        public string[] hosts_to_ignore { get; set; }

        #endregion

        #region Constructors

        /// <summary>
        /// Create a new rule
        /// </summary>
        public RedirectHttpsWwwNonWwwRule()
        {
            // Set values for instance variables
            this.status_code = 301;
            this.redirect_to_www = false;
            this.redirect_to_non_www = false;
            this.redirect_to_https = false;
            this.hosts_to_ignore = new string[] { "localhost" };

        } // End of the constructor

        #endregion

        #region methods

        /// <summary>
        /// Apply the rule
        /// </summary>
        public virtual void ApplyRule(RewriteContext context)
        {
            // Get the request
            HttpRequest req = context.HttpContext.Request;

            // Get the host
            string host = req.Host.Host;

            // Create an uri builder with the current request
            UriBuilder uriBuilder = new UriBuilder(host + req.Path + req.QueryString);
            uriBuilder.Scheme = req.Scheme;
            uriBuilder.Port = req.IsHttps == true ? 443 : 80;

            // Check hosts to ignore
            for (int i = 0; i < hosts_to_ignore.Length; i++)
            {
                if (host.Equals(hosts_to_ignore[i], StringComparison.OrdinalIgnoreCase))
                {
                    context.Result = RuleResult.ContinueRules;
                    return;
                }
            }

            // Check if we should do a https redirect
            if (this.redirect_to_https == true && req.IsHttps == false)
            {
                // Add https scheme and port
                uriBuilder.Scheme = "https";
                uriBuilder.Port = 443;
                
                // Check if we should do a www redirect
                if(this.redirect_to_www == true && this.redirect_to_non_www == false && host.StartsWith("www") == false)
                {
                    uriBuilder.Host = "www." + uriBuilder.Host;
                }
                else if (this.redirect_to_non_www == true && this.redirect_to_www == false && host.StartsWith("www") == true)
                {
                    uriBuilder.Host = uriBuilder.Host.Replace("www.", "");
                }
     
                // Do a redirect
                HttpResponse response = context.HttpContext.Response;
                response.StatusCode = this.status_code;
                response.Headers[HeaderNames.Location] = uriBuilder.Uri.AbsoluteUri;
                context.Result = RuleResult.EndResponse;
                return;
            }
            else if (this.redirect_to_www == true && this.redirect_to_non_www == false && host.StartsWith("www.") == false)
            {
                // Modify the host
                uriBuilder.Host = "www." + uriBuilder.Host;

                // Do a redirect
                HttpResponse response = context.HttpContext.Response;
                response.StatusCode = this.status_code;
                response.Headers[HeaderNames.Location] = uriBuilder.Uri.AbsoluteUri;
                context.Result = RuleResult.EndResponse;
                return;
            }
            else if (this.redirect_to_non_www == true && this.redirect_to_www == false && host.StartsWith("www.") == true)
            {
                // Modify the url
                uriBuilder.Host = uriBuilder.Host.Replace("www.", "");

                // Do a redirect
                HttpResponse response = context.HttpContext.Response;
                response.StatusCode = this.status_code;
                response.Headers[HeaderNames.Location] = uriBuilder.Uri.AbsoluteUri;
                context.Result = RuleResult.EndResponse;
                return;
            }
            else
            {
                context.Result = RuleResult.ContinueRules;
                return;
            }

        } // End of the ApplyRule method

        #endregion

    } // End of the class

} // End of the namespace

Use the redirect rule

Our rewrite rule is added to the Configure method in the StartUp class and this rule will be used to redirect to https and to www or non-www. I use IWebsiteSettingRepository website_settings_repository to store information about if I should do a www redirect, a non-www redirect and/or a https redirect. KeyStringList is a wrapper for a dictionary with strings, this class makes it easier to get values from the dictionary.

// Add redirection and use a rewriter
RedirectHttpsWwwNonWwwRule rule = new RedirectHttpsWwwNonWwwRule
{
    status_code = 301,
    redirect_to_https = redirect_https,
    redirect_to_www = redirect_www,
    redirect_to_non_www = redirect_non_www,
    hosts_to_ignore = new string[] {"localhost", "fotbollstabeller001.azurewebsites.net" }
};
RewriteOptions options = new RewriteOptions();
options.Rules.Add(rule);
app.UseRewriter(options);
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IWebsiteSettingRepository website_settings_repository)
{
    // Use error handling
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseStatusCodePagesWithReExecute("/home/error/{0}");
    }

    // Get website settings
    KeyStringList settings = website_settings_repository.GetAllFromCache();
    bool redirect_https = settings.Get("REDIRECT-HTTPS") == "true" ? true : false;
    bool redirect_www = settings.Get("REDIRECT-WWW") == "true" ? true : false;
    bool redirect_non_www = settings.Get("REDIRECT-NON-WWW") == "true" ? true : false;

    // Add redirection and use a rewriter
    RedirectHttpsWwwNonWwwRule rule = new RedirectHttpsWwwNonWwwRule
    {
        status_code = 301,
        redirect_to_https = redirect_https,
        redirect_to_www = redirect_www,
        redirect_to_non_www = redirect_non_www,
        hosts_to_ignore = new string[] {"localhost", "fotbollstabeller001.azurewebsites.net" }
    };
    RewriteOptions options = new RewriteOptions();
    options.Rules.Add(rule);
    app.UseRewriter(options);

    // Use static files
    app.UseStaticFiles(new StaticFileOptions
    {
        OnPrepareResponse = ctx =>
        {
            // Cache static files for 30 days
            ctx.Context.Response.Headers.Append("Cache-Control", "public,max-age=25920000");
            ctx.Context.Response.Headers.Append("Expires", DateTime.UtcNow.AddDays(300).ToString("R", CultureInfo.InvariantCulture));
        }
    });

    // Use sessions
    app.UseSession();

    // For most apps, calls to UseAuthentication, UseAuthorization, and UseCors must 
    // appear between the calls to UseRouting and UseEndpoints to be effective.
    app.UseRouting();

    // Use authentication and authorization middlewares
    app.UseAuthentication();
    app.UseAuthorization();

    // Routing endpoints
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            "default",
            "{controller=home}/{action=index}/{id?}");
    });

} // End of the Configure method

1 thought on “Redirect to https and www or non-www in ASP.NET Core”

Leave a Reply

Your email address will not be published. Required fields are marked *