Setting up ASP .NET Core Web Application for Serving Content and Better PageSpeed Insights Scores

Always Use Https

As a general rule of thumb, even if you are creating a web application strictly for intranet use, always use https. Use a self signed server certificate if you have to, no data should ever flow over network unencrypted. You will never know who is eavesdropping.

ASP .NET Core Web applications are created with https enabled by default. But if you have to turn it on later, all you have to do is attach the required middlewares to the pipeline in Configure method of Startup class. Also enable hsts while you are at it. Hsts is a mechanism to signal requesting clients that this site only accepts https connections. You can find more info on HSTS in this link.


        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");

                //ADD app.UseHsts() LINE TO ENABLE HSTS
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            //ADD app.UseHttpsRedirection() LINE TO ENABLE HTTPS REDIRECTION
            app.UseHttpsRedirection();

            app.UseStaticFiles();

            //more code...
        

If you want to configure https and hsts middlewares beyond their default values, you can do so in ConfigureServices method of Startup class by calling AddHsts and AddHttpsRedirection methods.


        public void ConfigureServices(IServiceCollection services)
        {
            //some code...

            services.AddHsts(options => options.IncludeSubDomains = true);

            if (!_env.IsDevelopment())
            {
                //Production environment uses load balancer so local service's https port is not what is seen from outside
                services.AddHttpsRedirection(options =>
                {
                    options.RedirectStatusCode = StatusCodes.Status301MovedPermanently;
                    options.HttpsPort = 443;
                });
            }

            //more code...


To be able to use environment information in ConfigureServices method, you can use dependency injection.


    public class Startup
    {
        private readonly Microsoft.AspNetCore.Hosting.IWebHostEnvironment _env;

        public Startup(Microsoft.AspNetCore.Hosting.IWebHostEnvironment env)
        {
            _env = env;
        }

    //more code...


Serve Static Assets with an Efficient Cache Policy

You will be better off by inlining local css and js, using web fonts and serving other static assets over cdn. However, if you have any static files you serve locally, it's good to make clients aware that they can cache the file and don't need to download it everytime. You can use "asp-append-version" tag helper attribute in html markup for cache busting whenever static file changes. "asp-append-version" attribute works for source values in img, link, script html tags.

To setup cache-control response header, change default app.UseStaticFiles() line in Configure method of Startup class.


        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {

            //some code...

            app.UseStaticFiles(new StaticFileOptions
            {
                OnPrepareResponse = ctx =>
                {
                    // Requires the following import:
                    // using Microsoft.AspNetCore.Http;
                    var cachePeriodInSeconds = env.IsDevelopment() ? "600" : "31536000";
                    ctx.Context.Response.Headers.Append("Cache-Control", $"public, max-age={cachePeriodInSeconds}");
                }
            });

            //more code...


Enable Response Compression

Enabling response compression reduces client download sizes and saves bandwidth. By default, ASP .NET Core Web applications do not use response compression. Multiple types of compression can be enabled, one will be chosen according to "Accept-Encoding" value of request headers and the order providers are added in application code.

Response compression is enabled in Configure method of Startup class. Add it right after https redirection, if it's added before static file settings, static files will be compressed too.


        public void Configure(IApplicationBuilder app, Microsoft.AspNetCore.Hosting.IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();

            //THIS LINE ENABLES RESPONSE COMPRESSION
            app.UseResponseCompression();

            app.UseStaticFiles(new StaticFileOptions
            {
                OnPrepareResponse = ctx =>
                {
                    // Requires the following import:
                    // using Microsoft.AspNetCore.Http;
                    var cachePeriodInSeconds = env.IsDevelopment() ? "600" : "31536000";
                    ctx.Context.Response.Headers.Append("Cache-Control", $"public, max-age={cachePeriodInSeconds}");
                }
            });


Response compression is further configured in ConfigureServices method of Startup class. Compression providers will be preferred in their added order. For example; if request accept encoding header contains both gzip and brotli compression, brotli will be preferred because it's added first in below code.


        public void ConfigureServices(IServiceCollection services)
        {

            //Some code...

            services.Configure<GzipCompressionProviderOptions>(options => options.Level = System.IO.Compression.CompressionLevel.Fastest);
            services.Configure<BrotliCompressionProviderOptions>(options => options.Level = System.IO.Compression.CompressionLevel.Fastest);

            services.AddResponseCompression(options =>
            {
                options.EnableForHttps = true;
                options.Providers.Clear();
                options.Providers.Add<BrotliCompressionProvider>();
                options.Providers.Add<GzipCompressionProvider>();
            });

            //more code...


Set Correct Vary Header Value

"Vary" response header tells clients, on which conditions does response content change. For example; if response compression is enabled, "Vary" response header will be set to "Accept-Encoding", which will tell the client that server response will change depending on "Accept-Encoding" value in request headers. This is important to web crawlers more than users.

If response content varies depending on other conditions, it should be added to "Vary" response header value. For example; if mobile and desktop users are served different content (responsive web sites serve same content to both types of clients, only layout changes on client side), "Vary" response header value can be modified like shown below.


        public void Configure(IApplicationBuilder app, Microsoft.AspNetCore.Hosting.IWebHostEnvironment env)
        {

            //some code...

            //Set vary headers so client is hinted on response may have different content based on these request header values
            app.Use(async (context, next) =>
            {
                context.Response.Headers[Microsoft.Net.Http.Headers.HeaderNames.Vary] =
                    new string[] { "Accept-Encoding, User-Agent" };

                await next();
            });

            //more code...