Very good benefit, for very little work – IIS Compression for IIS6

I’ve seen quite frequently that many applications have not implemented compression. I personally, think it’s great, simply because processor power is fairly cheap and the faster I can deliver content to my users the better. To me, the negligible difference in processing power is definately worth the increase in user experience when pages load sometimes 2 to 3 times faster.

Before we get started, I think it would be beneficial to take a trip to a compression reporting service to see if your server is currently using compression and if not, what potential benefit you would most likely receive. These compression reports are usually free and only require a URL to do the analysis. I have used PipeBoost in the past to display compression reports.

Ok, now that you can quantify the potential benefit, let’s get started by showing you my personal settings for IIS compression. Many thanks to Frendy who originally turned me on to the benefits of IIS compression and allowing Microsoft’s IIS compression engine to do all the work rather than writing my own HTTP modules for it.

Keep in mind, for your own application you may want to have different settings than what I’ve chosen. I will not go into the detail of each setting, however, this should give you a jumping point to dive a little deeper for your particular application.

Changing IIS to Enable Compression

The first change needs to be made by going to:

IIS Manager > Right click on Web Sites > Click Properties > Click the Service Tab

Change the screen to look similar to the following (Checkmark Compress application files and Compress static files):

Website Properties in IIS

Changing the Metabase.xml

Next, we want to change the metabase.xml file to direct IIS on the compression scheme we want to use. I set up schemes for both gzip and deflate (though gzip is most often used I believe).

You will want to add the following section to your metabase.xml file directly below the <IIsFilter></IIsFilter> section.

Note: This may already be there, if so, simply review the values and match them up appropriately.

<IIsCompressionScheme	Location ="/LM/W3SVC/Filters/Compression/deflate"
		HcCompressionDll="%windir%\system32\inetsrv\gzip.dll"
		HcCreateFlags="0"
		HcDoDynamicCompression="TRUE"
		HcDoOnDemandCompression="TRUE"
		HcDoStaticCompression="FALSE"
		HcDynamicCompressionLevel="9"
		HcFileExtensions="htm
			html
			txt
			js
			css"
		HcOnDemandCompLevel="10"
		HcPriority="1"
		HcScriptFileExtensions="asp
			dll
			exe
			aspx
			asmx
			ashx
			asbx
			axd"
	>
</IIsCompressionScheme>
<IIsCompressionScheme	Location ="/LM/W3SVC/Filters/Compression/gzip"
		HcCompressionDll="%windir%\system32\inetsrv\gzip.dll"
		HcCreateFlags="1"
		HcDoDynamicCompression="TRUE"
		HcDoOnDemandCompression="TRUE"
		HcDoStaticCompression="TRUE"
		HcDynamicCompressionLevel="9"
		HcFileExtensions="htm
			html
			txt
			js
			css"
		HcOnDemandCompLevel="10"
		HcPriority="1"
		HcScriptFileExtensions="asp
			dll
			exe
			aspx
			asmx
			ashx
			asbx
			axd"
	>
</IIsCompressionScheme>       
    

That’s all there is to it. You should get a pretty strong benefit from compression depending on your particular setup; hope this helps!

Creating Greater Application Error Transparency

Application Error Tracking

Often times, when we develop applications for the real world we are put in a position where we can’t just test our applications in a normal debug fashion. Perhaps the error is only caused on a production server while the local or test servers work fine. Also, applications in a production environment tend to work with several other external applications, services, resources, etc. that are not available or apparent on a local machine.

By adding a little code to the global.asax file which is triggered anytime an application breaks, we can peer into problems we experience; and even become proactive in addressing issues before our customers complain.

This advanced warning, can really open your eyes to just how often a web application is crashing without you even knowing it, as well as provide a wealth of information for hard to replicate issues such as bugs from specific browsers, sessions, cookies, or even individual users!

Global.asax VB Code

' Code that runs when an unhandled error occurs
Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs)
    'Get reference to context
    Dim Context = HttpContext.Current
    
    'Create new exception
    Dim Exception As New Exception
    
    'If non-local, then send error (while debugging/developing, etc. doesn't constantly throw errors)
    If Context.Request.Url.AbsoluteUri.IndexOf("localhost") < 0 Then
        'NOTE: If you have session values that are pertinent to identifying user, session, interaction, etc.
        'this is a good place to reference those for display as well.
        
        Try
            'Get reference to thrown error
            Exception = Context.Server.GetLastError()
            
            Dim sb As New StringBuilder()
            
            sb.Append("An error has occurred on " & ConfigurationManager.AppSettings("baseHref") & ". A user requested " & Request.Path.ToString & chr(13) & chr(13))
            sb.Append("OS: " & Request.Browser.Platform & chr(13))
            sb.Append("Machine Name: " & Environment.MachineName.ToString & chr(13))
            sb.Append("Browser: " & Request.Browser.Browser.ToString & " - Ver. " & Request.Browser.MajorVersion.ToString & chr(13))
            sb.Append("Method Name: " & Session("METHOD_NAME") & chr(13))
            sb.Append("Source: " & Exception.Source & chr(13))
            sb.Append("Message: " & Exception.Message & chr(13))
            sb.Append("Message Details: " & Exception.InnerException.ToString() & chr(13))
            sb.Append("Target Site: " & Exception.TargetSite.Name & chr(13) & chr(13))
            sb.Append("Stack Trace: " & Exception.StackTrace & chr(13))
                        
            'NOTE: Add delivery mechanism (email, write to log file, write to db, write to event viewer, etc.) for sb.toString()
            
        Catch ex As Exception
            'Custom handling here... perhaps no logging done, retry, or try tiered logging if one log mechanism fails...
            
            Exit Sub
        End Try
    End If
End Sub

Special thanks to Budi Awan, who first brought this up to me.

Someday I Need to Write a JS Library

Unfortunately, I’ve never sat down and written a full blown out JS library; at least not in a really clean and reusable way. However, I do have a few custom Javascript functions that I tend to use in several projects.

The majority of these can be found by doing a quick search on google or some equivalent; but I figured I could keep them here for an easy find and if anyone else happened upon them they might be useful as a small collection.

Javascript Cookie Management

//create cookie
function createCookie(name,value,mins) {
	if (mins) {
		var date = new Date();
		date.setTime(date.getTime()+(mins*60*1000));
		var expires = "; expires="+date.toGMTString();
	}
	else var expires = "";
	document.cookie = name+"="+value+expires+"; path=/";
}

//read cookie
function readCookie(name) {
	var nameEQ = name + "=";
	var ca = document.cookie.split(';');
	for(var i=0;i < ca.length;i++) {
		var c = ca[i];
		while (c.charAt(0)==' ') c = c.substring(1,c.length);
		if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
	}
	return null;
}

//erase cookie
function eraseCookie(name) {
	createCookie(name,"",-1);
}

String Manipulation

function inStr(strSearch, charSearchFor) {
	for (i=0; i < len(strSearch); i++)
	{
	    if (charSearchFor == mid(strSearch, i, len(charSearchFor)))
	    {
			return i;
	    }
	}
	return -1;
}

function len(str) {
    return String(str).length;
}

function mid(str, start, len) {
    if (start < 0 || len  iLen) {
        iEnd = iLen;
    }
    else {
        iEnd = start + len;
    }
    
    return String(str).substring(start,iEnd);
}

function left(str, n) {
    if (n  String(str).length) {  // Invalid bound, return
        return str;                // entire string
    }
    else { // Valid bound, return appropriate substring
        return String(str).substring(0,n);
    }
}

function right(str, n) {
    if (n  String(str).length) {  // Invalid bound, return
        return str;                     // entire string
    }    
    else { // Valid bound, return appropriate substring
        var iLen = String(str).length;
        return String(str).substring(iLen, iLen - n);
    }
}

Page Navigation

This is really just shorthand for 99% of the popup pages that I would use when I called window.open().

function openPopup(page, width, height) 
{
    window.open(page,'PopupWindow','width='+width+',height='+height+',resizable=no,scrollbars=no');
}

Page/Form Input Helpers

//**************************************************************//
//Makes input control (form control) a highlighted color.  Works
//specifically for textboxes and textareas.
//
//obj = "this" if being triggered by event or if null is passed 
//      or accepts an overload of any document control object.
//
//className = name of css class for active input box.
//--------------------------------------------------------------//

function doActivateInput(obj) {
    if(obj == "[object Event]" || obj == null) {
        obj = this;
    }
    
    if(obj != null) {
        obj.className="input_active";
    }
}

function doDeactivateInput(obj) {
    if(obj == "[object Event]" || obj == null) {
        obj = this;
    }
    
    if(obj != null) {
        obj.className="";
    }
}
//**************************************************************//


//**************************************************************//
//Preloads images for a page to correct wait time when displaying 
//hidden/new images
//
//imgPathArray = string array of img paths to preload 
//--------------------------------------------------------------//
function preloadImages(imgPathArray) {
    for(i=0;i<imgPathArray.length;i++) {
        var img = new Image();
        img.src = imgPathArray[i];
    }
}
//**************************************************************//


//**************************************************************//
//Helper function for adding textbox activate/inactivate event listeners
//
//obj = the object (textbox/input box) to perform action on
//--------------------------------------------------------------//
function doHelperAddTextboxEventListeners(obj) {
    if (obj.addEventListener) {
       obj.addEventListener("focus", function () {doActivateInput(this);}, false);
       obj.addEventListener("blur", function () {doDeactivateInput(this);}, false); 
    } 
    else if (obj.attachEvent) {
       obj.onfocus = function () {doActivateInput(obj);};
       obj.onblur = function () {doDeactivateInput(obj);};
    }
}
//**************************************************************//


//**************************************************************//
//Helper function for adding buttons hover state event listeners
//
//obj = the object (button) to perform action on
//--------------------------------------------------------------//
function doHelperAddButtonEventListeners(obj, activeImgPath, inactiveImgPath) {
    if (obj.addEventListener) {
       obj.addEventListener("mouseover", function () {this.src=activeImgPath;}, false);
       obj.addEventListener("focus", function () {this.src=activeImgPath;}, false);
       obj.addEventListener("mouseout", function () {this.src=inactiveImgPath;}, false);
       obj.addEventListener("blur", function () {this.src=inactiveImgPath;}, false);   
    } 
    else if (obj.attachEvent) {
       obj.onmouseover = function () {this.src=activeImgPath;};
       obj.onfocus = function () {this.src=activeImgPath;};
       obj.onmouseout = function () {this.src=inactiveImgPath;};
       obj.onblur = function () {this.src=inactiveImgPath;};  
    }
}
//**************************************************************//

Check out Script.acul.us!

For a really fun (and useful) Javascript library try out: Script.aculo.us, which does a wide range of client-side effects to really provide a richer UI and better web-applications.

Linq to SQL Basic Operations

The following is a very brief demonstration of Linq to SQL to retrieve, insert, update, and delete a row of data. I make no claims that this is the most efficient or best practice of Linq, but simply a starting point.

There are various resources all over the internet for extended information on Linq; these would be better suited to discuss why to use Linq as well as advanced how-to information as well.

For demonstration purposes, we’ll use a fictious table schema based around a User.

Retrieve Record

public static User GetUser(int UserID)
{
    MasterDataContext dc = new MasterDataContext();
    var query = from a in dc.Users
                where a.UserID == UserID
                select a;

    return query.Single<User>();
}

Create Record

public static void CreateUser(string EmailAddress, string Password, string Alias, string FirstName, string LastName, string City, string State, string Country, string Zip, string Description)
{
    MasterDataContext dc = new MasterDataContext();
    var usr = new User();

    usr.Email = EmailAddress;
    usr.Password = Password;
    usr.Alias = Alias;
    usr.FirstName = FirstName;
    usr.LastName = LastName;
    usr.City = City;
    usr.State = State;
    usr.Country = Country;
    usr.Zip = Zip;
    usr.Description = Description;

    usr.Created = DateTime.Now;
    usr.LastUpdated = DateTime.Now;

    dc.Users.InsertOnSubmit(usr);
    dc.SubmitChanges();
}

Update Record

public static void UpdateUser(int UserID, string EmailAddress, string Password, string Alias, string FirstName, string LastName, string City, string State, string Country, string Zip, string Description)
{
    MasterDataContext dc = new MasterDataContext();
    var query = from a in dc.Users
                where a.UserID == UserID
                select a;

    var usr = query.Single();

    usr.Email = EmailAddress;
    usr.Password = Password;
    usr.Alias = Alias;
    usr.FirstName = FirstName;
    usr.LastName = LastName;
    usr.City = City;
    usr.State = State;
    usr.Country = Country;
    usr.Zip = Zip;
    usr.Description = Description;

    usr.LastUpdated = DateTime.Now;

    dc.SubmitChanges();
}

Delete Record

public static void DeleteUser(int UserID)
{
    MasterDataContext dc = new MasterDataContext();
    var query = from a in dc.Users
                where a.UserID == UserID
                select a;

    var usr = query.Single<User>();

    dc.Users.DeleteOnSubmit(usr);
    dc.SubmitChanges();
}

Triple DES Encryption Class

This is a useful encyption class written by Paul Hayman to get started with TripleDES.

Class written in C#

using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;
using System.IO;

namespace Security
{
    /// <summary>
    /// Wrapper class for Triple Des encryption
    /// </summary>
    /// <remarks>
    /// Author : Paul Hayman
    /// Date : Feb 2006
    /// info@PaulHayman.com
    /// </remarks>
    public class Encryption
    {

        private TripleDESCryptoServiceProvider des = new TripleDESCryptoServiceProvider();
        private UTF8Encoding utf8 = new UTF8Encoding();

        private byte[] keyValue;
        private byte[] iVValue;

        /// <summary>
        /// Key to use during encryption and decryption
        /// </summary>
        /// <remarks>
        /// <example>
        /// byte[] key = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24 };
        /// </example>
        /// </remarks>
        public byte[] Key
        {
            get { return keyValue; }
            set { keyValue = value; }
        }

        /// <summary>
        /// Initialization vetor to use during encryption and decryption
        /// </summary>
        /// <remarks>
        /// <example>
        /// byte[] iv = { 8, 7, 6, 5, 4, 3, 2, 1 };
        /// </example>
        /// </remarks>
        public byte[] iV
        {
            get { return iVValue; }
            set { iVValue = value; }
        }

        /// <summary>
        /// Constructor, allows the key and initialization vetor to be provided
        /// </summary>
        /// <param name="key"><see cref="Key"/></param>
        /// <param name="iV"><see cref="iV"/></param>
        public Encryption(byte[] key, byte[] iV)
        {
            this.keyValue = key;
            this.iVValue = iV;
        }

        /// <summary>
        /// Decrypt bytes
        /// </summary>
        /// <param name="bytes"></param>
        /// <returns>Decrypted data as bytes</returns>
        public byte[] Decrypt(byte[] bytes)
        {
            return Transform(bytes, des.CreateDecryptor(this.keyValue, this.iVValue));
        }

        /// <summary>
        /// Encrypt bytes
        /// </summary>
        /// <param name="bytes"></param>
        /// <returns>Encrypted data as bytes</returns>
        public byte[] Encrypt(byte[] bytes)
        {
            return Transform(bytes, des.CreateEncryptor(this.keyValue, this.iVValue));
        }

        /// <summary>
        /// Decrypt a string
        /// </summary>
        /// <param name="text"></param>
        /// <returns>Decrypted data as string</returns>
        public string Decrypt(string text)
        {
            byte[] input = Convert.FromBase64String(text);
            byte[] output = Transform(input, des.CreateDecryptor(this.keyValue, this.iVValue));
            return utf8.GetString(output);
        }

        /// <summary>
        /// Encrypt a string
        /// </summary>
        /// <param name="text"></param>
        /// <returns>Encrypted data as string</returns>
        public string Encrypt(string text)
        {
            byte[] input = utf8.GetBytes(text);
            byte[] output = Transform(input, des.CreateEncryptor(this.keyValue, this.iVValue));
            return Convert.ToBase64String(output);
        }

        /// <summary>
        /// Encrypt or Decrypt bytes.
        /// </summary>
        /// <remarks>
        /// This is used by the public methods
        /// </remarks>
        /// <param name="input">Data to be encrypted/decrypted</param>
        /// <param name="cryptoTransform">
        /// <example>des.CreateEncryptor(this.keyValue, this.iVValue)</example>
        /// </param>
        /// <returns>Byte data containing result of opperation</returns>
        private byte[] Transform(byte[] input, ICryptoTransform cryptoTransform)
        {
            // Create the necessary streams
            MemoryStream memory = new MemoryStream();
            CryptoStream stream = new CryptoStream(memory, cryptoTransform, CryptoStreamMode.Write);

            // Transform the bytes as requesed
            stream.Write(input, 0, input.Length);
            stream.FlushFinalBlock();

            // Read the memory stream and convert it back into byte array
            memory.Position = 0;
            byte[] result = new byte[memory.Length];
            memory.Read(result, 0, result.Length);

            // Clean up
            memory.Close();
            stream.Close();

            // Return result
            return result;
        }

    }
}

Class written in VB

Imports System 
Imports System.Collections.Generic 
Imports System.Text 
Imports System.Security.Cryptography 
Imports System.IO 

Namespace Security 
    ''' <summary>
    ''' Wrapper class for Triple Des encryption 
    ''' </summary>
    ''' <remarks> 
    ''' Author : Paul Hayman 
    ''' Date : Feb 2006
    ''' info@PaulHayman.com 
    ''' </remarks>
    Public Class Encryption 
        
        Private des As New TripleDESCryptoServiceProvider() 
        Private utf8 As New UTF8Encoding() 
        
        Private keyValue As Byte() 
        Private iVValue As Byte() 
        
        ''' <summary>
        ''' Key to use during encryption and decryption 
        ''' </summary> 
        ''' <remarks> 
        ''' <example> 
        ''' byte[] key = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24 }; 
        ''' </example>
        ''' </remarks> 
        Public Property Key() As Byte() 
            Get 
                Return keyValue 
            End Get 
            Set 
                keyValue = value 
            End Set 
        End Property 
        
        ''' <summary> 
        ''' Initialization vetor to use during encryption and decryption 
        ''' </summary>
        ''' <remarks> 
        ''' <example> 
        ''' byte[] iv = { 8, 7, 6, 5, 4, 3, 2, 1 }; 
        ''' </example>
        ''' </remarks> 
        Public Property iV() As Byte() 
            Get 
                Return iVValue 
            End Get 
            Set 
                iVValue = value 
            End Set 
        End Property 
        
        ''' <summary> 
        ''' Constructor, allows the key and initialization vetor to be provided 
        ''' </summary>
        ''' <param name="key"><see cref="Key"/></param> 
        ''' <param name="iV"><see cref="iV"/></param>
        Public Sub New(ByVal key As Byte(), ByVal iV As Byte()) 
            Me.keyValue = key 
            Me.iVValue = iV 
        End Sub 
        
        ''' <summary> 
        ''' Decrypt bytes 
        ''' </summary>
        ''' <param name="bytes"></param>
        ''' <returns>Decrypted data as bytes</returns>
        Public Function Decrypt(ByVal bytes As Byte()) As Byte() 
            Return Transform(bytes, des.CreateDecryptor(Me.keyValue, Me.iVValue)) 
        End Function 
        
        ''' <summary> 
        ''' Encrypt bytes 
        ''' </summary> 
        ''' <param name="bytes"></param>
        ''' <returns>Encrypted data as bytes</returns> 
        Public Function Encrypt(ByVal bytes As Byte()) As Byte() 
            Return Transform(bytes, des.CreateEncryptor(Me.keyValue, Me.iVValue)) 
        End Function 
        
        ''' <summary> 
        ''' Decrypt a string 
        ''' </summary> 
        ''' <param name="text"></param>
        ''' <returns>Decrypted data as string</returns>
        Public Function Decrypt(ByVal text As String) As String 
            Dim input As Byte() = Convert.FromBase64String(text) 
            Dim output As Byte() = Transform(input, des.CreateDecryptor(Me.keyValue, Me.iVValue)) 
            Return utf8.GetString(output) 
        End Function 
        
        ''' <summary> 
        ''' Encrypt a string 
        ''' </summary> 
        ''' <param name="text"></param>
        ''' <returns>Encrypted data as string</returns> 
        Public Function Encrypt(ByVal text As String) As String 
            Dim input As Byte() = utf8.GetBytes(text) 
            Dim output As Byte() = Transform(input, des.CreateEncryptor(Me.keyValue, Me.iVValue)) 
            Return Convert.ToBase64String(output) 
        End Function 
        
        ''' <summary> 
        ''' Encrypt or Decrypt bytes. 
        ''' </summary> 
        ''' <remarks> 
        ''' This is used by the public methods 
        ''' </remarks> 
        ''' <param name="input">Data to be encrypted/decrypted</param>
        ''' <param name="cryptoTransform"> 
        ''' <example>des.CreateEncryptor(this.keyValue, this.iVValue)</example> 
        ''' </param> 
        ''' <returns>Byte data containing result of opperation</returns> 
        Private Function Transform(ByVal input As Byte(), ByVal cryptoTransform As ICryptoTransform) As Byte() 
            ' Create the necessary streams 
            Dim memory As New MemoryStream() 
            Dim stream As New CryptoStream(memory, cryptoTransform, CryptoStreamMode.Write) 
            
            ' Transform the bytes as requesed 
            stream.Write(input, 0, input.Length) 
            stream.FlushFinalBlock() 
            
            ' Read the memory stream and convert it back into byte array 
            memory.Position = 0 
            Dim result As Byte() = New Byte(memory.Length - 1) {} 
            memory.Read(result, 0, result.Length) 
            
            ' Clean up 
            memory.Close() 
            stream.Close() 
            
            ' Return result 
            Return result 
        End Function 
        
    End Class 
End Namespace