Minify HTML output of your pages

Minification of HTML output using ASP.NET IHttpModule

Minification of resource can speed up your page load and decrease network traffic making you pages load faster, especially on a poor Internet connections. Keep in mind that not all of your website visitors have high speed Internet connection.

Especially if you are targeting countries with mostly poor Internet connection, HTML minification can really boost the page load and improve user experience. Minifying the HTML is one of the recommendations you will also get from Google Page Speed insights.

Note

Compacting HTML code, including any inline JavaScript and CSS contained in it, can save many bytes of data and speed up download and parse times.

Since there are so many benefits, there is no reason not to do this.

The fastest string matching is easy to be done with Regular Expressions, so we have to do a string replacement in the output HTML. This means, after the page has been generated, before serving it back to the user, we need to minify the page, which bascally means stripping down spaces between tags and removing new lines.

Applying minification on the website does not come for free however, it will put additional pressure on the CPU to minify every request, but this can be resolved with additonal caching. However you will gain more on the page speed comparing to a CPU utilization you use for minification.

This means that in the end page output will go down to a single line. Since we apply this after the page output is already generated, using IHttpModule implemented class is a logical choice. However we cannot apply this to all HTML tags, such as PRE HTML tags. 

Note

The HTML <pre> element represents preformatted text which is to be presented exactly as written in the HTML file. Whitespace inside this element is displayed as written. More about PRE tags can be found at MDN docs https://developer.mozilla.org/en-US/docs/Web/HTML/Element/pre or W3Schools https://www.w3schools.com/tags/tag_pre.asp

So our regular expression needs to preserve PRE tags and not to apply the minification to them. Since System.Web.IHttpModule applies processing of a Response with a Filter property which is a System.IO.Stream implementation, we first need to create a Stream which will minify the output HTML

using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;

namespace Web.Html.Minify
{
    class Filter: Stream
    {
        #region fields
        private HttpContext httpcontext;
        protected Stream stream { get; set; }
        #endregion

        #region properties
        public override bool CanRead
        {
            get { return true; }
        }

        public override bool CanSeek
        {
            get { return true; }
        }

        public override bool CanWrite
        {
            get { return true; }
        }

        public override void Flush()
        {
            this.stream.Flush();
        }

        public override long Length
        {
            get { return 0; }
        }

        public override long Position
        {
            get;
            set;
        }
        #endregion

        #region constructors 
        public Filter(HttpContext context)
        {
            this.httpcontext = context;

            this.stream = context.Response.Filter;
        }
        #endregion


        public override int Read(byte[] buffer, int offset, int count)
        {
            return this.stream.Read(buffer, offset, count);
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            return this.stream.Seek(offset, origin);
        }

        public override void SetLength(long value)
        {
            this.stream.SetLength(value);
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            String html = Encoding.UTF8.GetString(buffer, offset, count);

            // Replace all spaces between tags skipping PRE tags
            html = Regex.Replace(html, @"(?<=\s)\s+(?![^<>]*</pre>)", String.Empty);

            // Replace all new lines between tags skipping PRE tags
            html = Regex.Replace(html, "\n(?![^<]*</pre>)", String.Empty);

            byte[] outdata = System.Text.Encoding.UTF8.GetBytes(html);
            this.stream.Write(outdata, 0, outdata.GetLength(0));

        }

        public override void Close()
        {
            this.stream.Close();
        }

    }
}

    

Now to engage the filtering through System.Web.IHttpModule implemented class which will reference our custom System.IO.Stream class.

In case you are still using Umbraco 6 as I am for my website, you need to switch off HTML minification for your back-office access. Since Umbraco 6 is based on WebForms, it is enough to filter out all .aspx or .axd requests. This will keep minification only on the nicely formed URL of your website since they are based on the MVC routing and Razor rendering rather than WebForma as the Umbraco 6 back-end.

using System;
using System.Web;

namespace Web.Html.Minify
{
    public class Module : IHttpModule
    {
        private bool disposing = false;

        public void Init(HttpApplication context)
        {
            context.PostRequestHandlerExecute  = context_PostRequestHandlerExecute;
        }

        void context_PostRequestHandlerExecute(object sender, EventArgs e)
        {
            HttpApplication app = sender as HttpApplication;
            HttpContext context = app.Context;
            string contentType = app.Context.Response.ContentType;

            if (context.Request.HttpMethod == "GET" &&
                contentType.Equals("text/html") &&
                context.Response.StatusCode == 200 &&
                context.CurrentHandler != null &&
                !app.Context.Request.CurrentExecutionFilePathExtension.Equals(".aspx", StringComparison.InvariantCultureIgnoreCase) &&
                !app.Context.Request.CurrentExecutionFilePathExtension.Equals(".axd", StringComparison.InvariantCultureIgnoreCase))
            {
                context.Response.Filter = new Filter(context);

            }
        }

        public void Dispose()
        {
            if (!disposing)
            {
                //TODO: Dispose any resource used
                disposing = true;
            }
        }
    }
}
    

Now when we have the module and filter done we need to switch on the module in the Web.config file. The class library project I built is named as the namespace in these code snipets which is Web.Html.Minify so we will reference it as following

<configuration>
  <system.webServer>
    <modules>

      <remove name="MinifyModule" />
      <add name="MinifyModule" type="Web.Html.Minify.Module, Web.Html.Minify" />
    
    </modules>
  </system.webServer>
<configuration>
    

 

 

References

Disclaimer

Purpose of the code contained in snippets or available for download in this article is solely for learning and demo purposes. Author will not be held responsible for any failure or damages caused due to any other usage.


About the author

DEJAN STOJANOVIC

Dejan is a passionate Software Architect/Developer. He is highly experienced in .NET programming platform including ASP.NET MVC and WebApi. He likes working on new technologies and exciting challenging projects

CONNECT WITH DEJAN  Loginlinkedin Logintwitter Logingoogleplus Logingoogleplus

.NET

read more

JavaScript

read more

SQL/T-SQL

read more

PowerShell

read more

Comments for this article