(Print this page)

Caching in a Windows Forms .Net application
Published date: Sunday, February 24, 2013
On: Moer and Éric Moreau's web site

Caching is very well known method of improving the speed of applications. Data that are never or rarely changing over a given period of time (like the list of countries, provinces, employees, tax rates) are good candidates to be cached especially if this data is coming from a slow source like a web site, a web service, a slow database server or the result of a long calculation.

Caching has always been very popular in web applications because they can rely on the HttpRuntime Cache object from the System.Web dll. But who said we can’t use it on other platforms? This article will show you how to use this exact same components in your Windows Forms application (but could also be used in other project types).

This month’s demo application

This month demo code is available in both VB and C#. The solution was created using Visual Studio 2012 but all the objects are available since version 1.1 of the .Net Framework (meaning that you can use it since Visual Studio 2003).

Creating the solution

The first thing you need to use is to add a reference to the System.Web.Dll component so you can get access to the Caching namespace.

Figure 1: Adding the required reference

The demo UI

To be able to test the caching feature, I have created a simple UI like the one shown in figure 2.

Figure 2: The demo application UI

The top group box offers features to add, update and delete strings in the cache. In my demo application, I store strings but you can store anything from a Boolean to a class instance.

The second group box is only used to try to read values from the cache.

The gray zone at the bottom is a data grid view displaying the content of the cache. This is only used for debugging purposes.

Adding a value to the cache

Let’s start with the Set button. This button is used to add values to the cache.

The code of this button first starts by validating that both textboxes are filled and that the key is not already existing in the cache. When the key and the value are valid, the combination is added to the cache and the data grid view is refreshed.

The full code behind this button is:

'Validate the textboxes have values 
If (String.IsNullOrWhiteSpace(txtPutKey.Text)) Then
    MessageBox.Show("You need to fill the Key")
    Return
End If

If (String.IsNullOrWhiteSpace(txtPutValue.Text)) Then
    MessageBox.Show("You need to fill the Value")
    Return
End If

'Validate that the key is not already existing
If (HttpRuntime.Cache(txtPutKey.Text) IsNot Nothing) Then
    MessageBox.Show("This Key already exist")
    Return
End If

'Add the key-value to the cache
HttpRuntime.Cache.Add(txtPutKey.Text,
    txtPutValue.Text,
    Nothing,
    Cache.NoAbsoluteExpiration,
    TimeSpan.FromSeconds(60),
    CacheItemPriority.Default,
    AddressOf OnCacheItemRemoved)

'Refresh the debugging datagrid
RefreshOutputCache()

I think it is worth spending a few lines to talk about the Cache.Add method. Full details can be found at http://msdn.microsoft.com/en-us/library/system.web.caching.cache.add.aspx/html.

The first 2 parameters are the key and the value. The key must be a string and it is case-sensitive while the value is an object letting you store just about anything.

The third parameter is a dependency which the Cache will monitor to detect changes. If the dependency changes, the attached cache object is removed from the cache. I won’t use it in this article.

The fourth and fifth parameters are related to the expiration of the cached item. You use the fourth when you want to specify the absolute date and time of expiration of your item. You use the fifth parameter when you want to set an expiration relative to the current time. You cannot set both parameters at the same time. In my demo, I use the fifth parameter to have the item expiring 60 seconds after it was added to the cache. I doubt your application will use such a small expiration delay.

The sixth parameter is related to the priority and should be set to Default unless you need to put some constraints on memory usage.

The last parameter is the method to callback when and item from the cache expires and is getting removed. You don’t need to handle it. It can be useful to release some resources attached/linked to the object. In my demo, I will just refresh the data grid so that you can see that the items are effectively removed from the cache sometime after 60 seconds (since the cache expiration runs on a lower priority thread, the callback won’t be called exactly after 60 seconds but rather sometime after the expiration depending on many factors like CPU and memory usage).

The method to call back has a strict signature which looks like this:

Private Sub OnCacheItemRemoved(key As String, value As Object, reason As CacheItemRemovedReason)
    If dataGridView1.InvokeRequired Then
        Dim d As New RefreshOutputCacheDelegate(AddressOf RefreshOutputCache)
        Invoke(d)
    Else
        RefreshOutputCache()
    End If
End Sub

Note that because this event is called from a different thread, we need to use the Invoke method to move to the UI thread which in turns use a delegate declared like this:

Private Delegate Sub RefreshOutputCacheDelegate()

The RefreshOutputCache method is simply casting the cache as a dictionary object (because in my case all the values are stored as strings) to be able to easily show the cached items into a data grid:

Private Sub RefreshOutputCache()
    dataGridView1.DataSource = HttpRuntime.Cache.Cast(Of DictionaryEntry)().ToList()
End Sub

Getting a value from the cache

Getting the value is a simple operation. 

I just start by validating that the key is specified and also that the key exist in the cache.

I can then query the cache with the key to retrieve its value. In my case, because all the values are strings, I can just cast it back using the ToString method before displaying it in a textbox.

Here is the full code:

'Validate that the key is filled
If (String.IsNullOrWhiteSpace(txtGetKey.Text)) Then
    MessageBox.Show("You need to fill the Key")
    Return
End If

'Validate that the key exist
If (HttpRuntime.Cache(txtGetKey.Text) Is Nothing) Then
    MessageBox.Show("This Key does not exist")
    Return
End If

'Shows the value
txtGetValue.Text = HttpRuntime.Cache(txtGetKey.Text).ToString()

Removing a value from the cache

Removing an item from the cache is much like getting a value from it. 

After validating that the key has a value and the key exist, we can just called the Remove method.

Here is the full code:

'Validate that the key is filled
If (String.IsNullOrWhiteSpace(txtPutKey.Text)) Then
    MessageBox.Show("You need to fill the Key")
    Return
End If

'Validate that the key exist
If (HttpRuntime.Cache(txtPutKey.Text) Is Nothing) Then
    MessageBox.Show("This Key does not exist")
    Return
End If

'Remove the item
HttpRuntime.Cache.Remove(txtPutKey.Text)

You might wonder why I am not calling the RefreshOutputCache method to refresh the data grid. The reason is simple. When the item was added to the cache, we have set a callback for item removal. This is not only called when the item expires but also when the code calls the Remove method.

Updating a value in the cache

You might think that updating a value from the cache is done calling a method much like we did when using the Add or Remove methods. But the API designers decided to do it differently. Instead, you need to affect the item of the cache collection.

Here is the code:

'Validate the textboxes have values 
If (String.IsNullOrWhiteSpace(txtPutKey.Text)) Then
    MessageBox.Show("You need to fill the Key")
    Return
End If

If (String.IsNullOrWhiteSpace(txtPutValue.Text)) Then
    MessageBox.Show("You need to fill the Value")
    Return
End If

'Validate that the key is not already existing
If (HttpRuntime.Cache(txtPutKey.Text) Is Nothing) Then
    MessageBox.Show("This Key does not exist")
    Return
End If

'update the value from the cache
HttpRuntime.Cache(txtPutKey.Text) = txtPutValue.Text

'Refresh the debugging datagrid
RefreshOutputCache()

Notice that this time we need to explicitly call the method to refresh the data grid view because the item is not removed.

If you run the application to this point, you add an item and then update it (before 60 seconds elapsed), you will find that the data grid correctly shows the new value. But if you wait a bit longer, you will find that the updated item will never disappear from the data grid. It happens that updating the value like we do here also nullifies the expiration of the object. Figure 3 is showing the values of the expiration both before and after the update of the value.

Figure 3: The updated item will now never expires (at least not in this era!)

For me the behavior is not acceptable. I was ready to call the Remove method before adding the modified item again to the collection. That was before I found the Insert method. In fact this method would better be named called Upsert (contraction of Update and Insert). The Insert method will add the item if the key is not existing in the cache and it will update the item if the key already exist.

Here is the new snippet:
'update the value from the cache
Dim x = GetCacheUtcExpiryDateTime(txtPutKey.Text)
'HttpRuntime.Cache(txtPutKey.Text) = txtPutValue.Text
HttpRuntime.Cache.Insert(txtPutKey.Text,
    txtPutValue.Text,
    Nothing,
    Cache.NoAbsoluteExpiration,
    TimeSpan.FromSeconds(60),
    CacheItemPriority.Default,
    AddressOf OnCacheItemRemoved)
Dim y = GetCacheUtcExpiryDateTime(txtPutKey.Text)

Be careful if you use other overloads of the Insert method (like the one where you only specify the key and the new value). The result would be a never-expiring item like we first got using the previous syntax. You can find all the overloads of the Insert method at http://msdn.microsoft.com/en-us/library/05kd8d77.aspx.

In fact, your final code would be better to use only the Insert method. You might ask why the two methods exist. The answer is easy, the Insert method only exists since .Net Framework 2.0 (while the Add method exist since version 1.1).

You should also consider

Do you remember the Microsoft Enterprise Library? This library is still around and still continue to exist. The latest official version is 5.0 and they offer a caching namespace.

If you are expecting more than what we have explored here, you should really consider digging the Enterprise Library.

Conclusion

Sometimes the dll containing a feature of the .Net Framework is not limiting the platform to which the feature should be limited too. The Cache feature is a good example.


(Print this page)