Friday, March 25, 2011

WPF: Configuring System.Runtime.Caching.MemoryCache for an MVVM based App

This is a continuation of our post here:

http://shujaatsiddiqi.blogspot.com/2011/03/wpf-performance-improvement-for-mvvm.html

In the above post we discussed how we can use the new caching feature available in .net framework 4.0 to improve the performance of a WPF application. As an example we used an application implementing MVVM pattern. In this example we would extend the example by discussing how we can use configuration option provided with this caching feature. We would also look at the underlying architecture provided by the framework to support this.

As we discussed in the previous example, the framework has provided MemoryCache. This is an implementation of ObjectCache providing an in-memory caching mechanism. Let us create a simple view showing the fields from this cache.

In order to use this you need to add a reference of System.Runtime.Caching assembly from the framework.

Yes I have Power Tools installed :).
<Window x:Class="WPF_MVVM_Caching.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WPF_MVVM_Caching"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>
    <Grid>
        <Label Content="PollingInterval" Height="26" HorizontalAlignment="Left" 
               Margin="10,21,0,0" Name="label1" VerticalAlignment="Top" Width="141" />
        <TextBlock Height="26" HorizontalAlignment="Left" Margin="157,21,0,0" Name="txtPollingInterval"
                   Background="LightGoldenrodYellow"
                   Text="{Binding PollingInterval, Mode=OneTime}" VerticalAlignment="Top" Width="302" />
        <Label Content="PhysicalMemoryLimit" Height="26" HorizontalAlignment="Left" 
               Margin="10,53,0,0" Name="label2" VerticalAlignment="Top" Width="141" />
        <TextBlock Background="LightGoldenrodYellow" Height="26" HorizontalAlignment="Left" 
                   Margin="157,53,0,0" Name="txtPhysicalMemoryLimit" Text="{Binding PhysicalMemoryLimit, Mode=OneTime}" 
                   VerticalAlignment="Top" Width="302" />
        <Label Content="Name" Height="26" HorizontalAlignment="Left" 
               Margin="11,85,0,0" Name="label3" VerticalAlignment="Top" Width="140" />
        <TextBlock Background="LightGoldenrodYellow" Height="26" HorizontalAlignment="Left" 
                   Margin="157,85,0,0" Name="txtName" Text="{Binding Name, Mode=OneTime}" 
                   VerticalAlignment="Top" Width="302" />
        <Label Content="CacheMemoryLimit" Height="26" HorizontalAlignment="Left" 
               Margin="10,116,0,0" Name="label4" VerticalAlignment="Top" Width="141" />
        <TextBlock Background="LightGoldenrodYellow" Height="26" HorizontalAlignment="Left" 
                   Margin="157,116,0,0" Name="txtCacheMemoryLimit" Text="{Binding CacheMemoryLimit, Mode=OneTime}" 
                   VerticalAlignment="Top" Width="302" />
    </Grid>
</Window>

The view is expecting certain properties from MainWindowViewModel which is used as its DataContext. They are bound with Text properties of TextBlocks. The definition of MainWindowViewModel is as follows:
namespace WPF_MVVM_Caching
{
    using System.ComponentModel;
    using System.Runtime.Caching;

    class MainWindowViewModel : INotifyPropertyChanged
    {
        public string CacheMemoryLimit
        {
            get { return string.Format("{0}", MemoryCache.Default.CacheMemoryLimit); }
        }

        public string PollingInterval
        {
            get { return string.Format("{0}", MemoryCache.Default.PollingInterval); }
        }

        public string PhysicalMemoryLimit
        {
            get { return string.Format("{0}", MemoryCache.Default.PhysicalMemoryLimit); }
        }

        public string Name
        {
            get { return MemoryCache.Default.Name; }
        }

        #region INotifyPropertyChanged Implementation
        
        public event PropertyChangedEventHandler PropertyChanged = delegate { };
        private void onPropertyChanged(string propertyName)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion INotifyPropertyChanged Implementation
    }
}


Loading Default Memory Cache with Configuration Options:
The configuration of default MemoryCache is easy. We just need to provide the following code in the app.config file and the framework automatically loads the MemoryCache with the specified values as in the configuration file.
<?xml version="1.0"?>
<configuration>
  <system.runtime.caching>
    <memoryCache>
      <namedCaches>
        <add name="Default"
             cacheMemoryLimitMegabytes="52"
             physicalMemoryLimitPercentage="40"
             pollingInterval="00:04:01" />
      </namedCaches>
    </memoryCache>
  </system.runtime.caching>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
  </startup>
</configuration>
Now run the application. We can see that the MemoryCache.Default is constructed with the values that we specified in the configuration file. The view would appear as follows:


So in order to provide the definition of the Default MemoryCache configuration, we just need to make sure that we use name Default and provide the values for the properties that we want to change. The properties not specified in configuration would be kept with default values.

Configuring Non-Default Caches:
In the above example we have seen how we can configure the Default MemoryCache. We can use the same technique to configure a non-default cache. We might be needing it when we want to define a local cache. In order to instantiate a non-default cache, whose properties expected to be loaded from a configuration file, MemoryCache provides a constructor. We need to provide the name of cache configuration to use to construct the cache instance as follows:
<?xml version="1.0"?>
<configuration>
  <system.runtime.caching>
    <memoryCache>
      <namedCaches>
        <add name="TestCache"
             cacheMemoryLimitMegabytes="52"
             physicalMemoryLimitPercentage="40"
             pollingInterval="00:04:01" />
      </namedCaches>
    </memoryCache>
  </system.runtime.caching>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
  </startup>
</configuration>
We can use this cache as follows:
class MainWindowViewModel : INotifyPropertyChanged
{
    MemoryCache cache;

    public MainWindowViewModel()
    {
        cache = new MemoryCache("TestCache");
        //string s = "";
    }

    public string CacheMemoryLimit
    {
        get { return string.Format("{0}", cache.CacheMemoryLimit); }
    }

    public string PollingInterval
    {
        get { return string.Format("{0}", cache.PollingInterval); }
    }

    public string PhysicalMemoryLimit
    {
        get { return string.Format("{0}", cache.PhysicalMemoryLimit); }
    }

    public string Name
    {
        get { return cache.Name; }
    }

    #region INotifyPropertyChanged Implementation
    
    public event PropertyChangedEventHandler PropertyChanged = delegate { };
    private void onPropertyChanged(string propertyName)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    #endregion INotifyPropertyChanged Implementation
}
When we run the application we can see that the properties are successfully loaded from the configuration and the view is shown as follows:


System.Runtime.Caching.Configuration in Details
Now let us look at the underlying architecture used by the above logic. The configuration classes for run-time caching is provided in System.Runtime.Caching.Configuration namespace in System.Runtime.Caching assembly. They are based on the same logic we implement when we want configuration option for some classes i.e. We have to define classes inheriting from the following classes:

- ConfigurationElement
- ConfigurationElementCollection
- ConfigurationSection
- ConfigurationSectionGroup

For System.Runtime.Caching, these classes are as follows:
- MemoryCacheElement
- MemoryCacheSettingsCollection
- MemoryCacheSection
- CachingSectionGroup

They can be shown in the class diagram as follows:



Now in order to go further with this example we need to add a reference of System.Configuration assembly as follows:


Let us update the configuration file as follows:
<?xml version="1.0"?>
<configuration>
  <configSections>
    <section name="MemoryCacheConfiguration"
                  type="System.Runtime.Caching.Configuration.MemoryCacheSection, System.Runtime.Caching, Version= 4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
  </configSections>
  <MemoryCacheConfiguration>
    <namedCaches>
      <add name="MyMemoryCache" physicalMemoryLimitPercentage="23"
            pollingInterval="00:04:43" cacheMemoryLimitMegabytes="123" />
    </namedCaches>
  </MemoryCacheConfiguration>
</configuration>
Here we have defined a section named MemoryCacheConfiguration for System.Runtime.Caching.MemoryCacheSection type. Later we have provided the details about MemoryCacheConfiguration with details about runtime cache.

Now we instantiate a memory cache based on the information provided here in the configuration file. We can update the view model's constructor as follows:
public MainWindowViewModel()
{
    //Get Configuration section info from Configuration
    MemoryCacheSection section = (MemoryCacheSection)ConfigurationManager.GetSection("MemoryCacheConfiguration");

    //Create a name / value pair for properties
    NameValueCollection configurationNameValueCollection = new NameValueCollection();
    configurationNameValueCollection.Add("pollingInterval", 
        string.Format("{0}", section.NamedCaches[0].PollingInterval));
    configurationNameValueCollection.Add("physicalMemoryLimitPercentage", 
        string.Format("{0}", section.NamedCaches[0].PhysicalMemoryLimitPercentage));
    configurationNameValueCollection.Add("cacheMemoryLimitMegabytes", 
        string.Format("{0}", section.NamedCaches[0].CacheMemoryLimitMegabytes));
    
    //instantiate cache
    cache = new MemoryCache(section.NamedCaches[0].Name, configurationNameValueCollection);
}
Now let us run the application. The view should appear as follows:


Download Code:

No comments: