For so many years I’ve written code that needed to know whether or not a collection of objects has been changed or not. Now I’ve finally written the code that tells me just that.

The scenario is typical. You have a class called Order with a property called OrderLines which is a generic List containing OrderLine objects. You retrieve the Order class from the database including all the OrderLines, change some properties and then you save the Order object back to the database. Now the question is if you need to save the OrderLines as well. To know that, you need to know if the OrderLines collection has been changed. It would be a waste of resource to persist all the order lines each time you make a modification to the Orders object. To know if it has changed, we need a list that is aware of its own state.

The StateList<T>

This class is a subclass of the List<T> generic class but with a few twists. It does exactly what a normal List<T> does, but has two new members – a MarkUnChanged method and an IsChanged property. It also overrides the GetHashCode method. That’s all it does.

The MarkUnChanged method tells the StateList what the original list contains. You call this method after you’ve retrieved all the OrderLines and populated the list. That let’s the StateList know what the comparison is for reference. Behind the scenes, the MarkUnChanged method stores the hash code of the items in the list in a private integer variable.

The IsChanged property returns a Boolean value that indicates whether or not the items in the list produce the same hash code as the one stored in the private variable. If it doesn’t, then the list is changed. This is the class.

[System.Serializable]

public class StateList<T> : System.Collections.Generic.List<T>

{

  public override int GetHashCode()

  {

    long hash = 0;

    foreach (T item in this)

    {

      hash += item.GetHashCode();

    }

 

    return hash.GetHashCode();

  }

 

  private int _HashCode = 0;

 

  /// <summary>

  /// Gets if this list's data has been changed.

  /// </summary>

  public virtual bool IsChanged

  {

    get

    {

      return this.GetHashCode() != _HashCode;

    }

  }

 

  /// <summary>

  /// Marks the object as being clean,

  /// which means not changed.

  /// </summary>

  public virtual void MarkUnChanged()

  {

    _HashCode = this.GetHashCode();

    base.TrimExcess();

  }

}

Example

Here is a little taste of how it works:

StateList<int> list = new StateList<int>();

list.Add(3);

list.Add(4);

list.Add(5);

// list.IsChanged = true

 

list.MarkUnChanged();

// list.IsChanged = false

 

list.Add(6);

// list.IsChanged = true

 

list.Clear();

list.Add(3);

list.Add(4);

list.Add(5);

// list.IsChanged = false

The order of the items

The code above produces the same hash code no matter in what order the individual items are located in the list. So if the same items are located in the list but at different locations, the IsChanged property is false. To support location awareness, you can just switch the overridden GetHashCode method with this one:

public override int GetHashCode()

{

  long hash = 0;

  for (int i = 0; i < this.Count; i++)

  {

    hash += this[i].GetHashCode() ^ i;

  }

 

  return hash.GetHashCode();

}

A month ago I challenged Simone to a friendly duel about who could get the best YSlow score. I did all sorts of things to improve the score but the one thing I never figured out was how to gzip compress the WebResource.axd handler. Then yesterday I was playing around with Fiddler and noticed that IE doesn’t cache WebResource.axd but loads it at every request to a page. Then I knew I had to do something about it and I might as well give the gzip compression another try.

I already have a HTTP compression module that works like a charm, so I just needed it to compress WebResource.axd and implement the right client cache mechanisms as well. Sounds simple right?

The compression

I knew this was my Achilles heel since I’ve failed at it before. Lucky for me, Miron Abramson just did it a week ago so I took a look at his solution. It was exactly what I needed, so I picked out the parts I needed and stuck them into my own compression module.

What Mirons code does is to create an HTTP request to the WebResource.axd, copy the response to a memory stream, compress it and serve it back to the client. That is a very clever approach but had a big problem. Every time the browser requested the WebResource.axd handler his module created an HTTP request. That was too big an overhead for me. I fixed it easily by caching the response at the first request and then served the compressed bytes from the cache from then on. Apart from that, his code rocks.

The caching

First of all I set the expires, last-modified and ETag HTTP headers that let’s any browser cache the response. Browsers are a little different when it comes to caching web resources so by specifying both an ETag and last-modified header takes care of them all.

Further more I added support for the If-None-Match request header. What it does is that when a page is refreshed by hitting F5 or similar, the browser sends the If-None-Match header to the server with the ETag as the value. Then I can simply check if the incoming ETag value is the same as the server ETag and terminate the response with a 304 Not modified response header.

That saves bandwidth and tells the browser to use the version from its cache instead of loading the content from the server again. The thing is that files served from WebResource.axd are always static so no harm can be done by using all means to let the browser cache them.

Limitations

The module expects that all files served by the WebResource.axd are JavaScript. That means that you probably run into trouble if you serve images or other file types from the WebResource.axd handler. Most websites don’t and I don’t like doing that in my projects, so that’s why I didn’t spend extra time on supporting it.

Download

Get the CompressionModule.cs file below to compress both your pages and WebResource.axd handler.

CompressionModule.zip (2,27 kb)

PS. I won the YSlow challenge.