Tuesday, 3 March 2009

Compress ASP.NET response streams

When sending large amounts of data from IIS to the browser, it is sometimes worth compressing certain types of data such as text documents. Although compression is not supported by IIS 6.0, most browsers support basic gzip compression and they notify the server of this ability by sending a header in each request. The following piece of code shows how to use the System.IO.Compression namespace to add a filter to the output stream that compresses the output whilst checking and setting the correct headers. In this example, a .xls document containing in a string is being sent to the client:

    'Send response with content type to display as MS Excel
    context.Response.Clear()
    context.Response.Buffer = True

    context.Response.AddHeader("content-disposition", String.Format( "attachment;filename={0}", fileName))
    context.Response.ContentEncoding = Encoding.UTF8

    context.Response.Cache.SetCacheability(HttpCacheability.Private)

    'Compress the output as it may be very large
    'When flushing or closing+ending the stream, the compression filter does not have a chance to write the compression footer
    'Therefore, make sure the compression filter stream is closed before flushing
    AddCompression(context)

    context.Response.ContentType = "application/vnd.ms-excel"

    'Write to response
    context.Response.Write(_reportXmlss)

    'context.Response.Flush() 'Do not flush if using compression
    'context.Response.Close()
    context.Response.End()

The AddCompression method checks the appropriate headers and adds a compression filter stream to the output:
'Add compression to the response stream
    Public Sub AddCompression(ByVal context As HttpContext)

        Dim acceptEncoding As String = context.Request.Headers("Accept-Encoding")
        If acceptEncoding Is Nothing OrElse acceptEncoding.Length = 0 Then Return

        'Convert to lower to check
        acceptEncoding = acceptEncoding.ToLower

        'Gzip or Compress compression
        'Compress compression is quicker and performs better compression so try that first
        If (acceptEncoding.Contains("deflate")) Then

            context.Response.Filter = New DeflateStream(context.Response.Filter, CompressionMode.Compress)
            context.Response.AppendHeader("Content-Encoding", "deflate")

        ElseIf acceptEncoding.Contains("gzip") Then

            context.Response.Filter = New GZipStream(context.Response.Filter, CompressionMode.Compress)
            context.Response.AppendHeader("Content-Encoding", "gzip")

        End If

    End Sub
To check if compression is being used, I use the awesome HttpWatch, which shows useful information such as headers, amount of compression and bytes sent/received.

2 comments:

Joe Hopkins said...

Hi,

Nice article, client respond time would also greatly improve if ASP.NET team could make its caching feature in version 4.0 to cater for distributed environment.

They are planning to ship "Velocity" with the new version but i think there are much more mature solution available in the market like NCache, they also have a free distributed caching solution called NCache Express. Check it out

vtortola said...

Hi,

What will happen when I tried to transfer a 4G file? will it compress it chunk by chunk at the same time that is transfering it or will it buffer it complete for compression?

Cheers.