RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX


Caching Data with a Web Service in Enterprise Library : Page 5

If you've ever wondered whether you might be able to use a web service to cache data—or whether it would be fast enough to be useful—wonder no more.

Converting Data Types for the Web Service
The data types chosen for the sample provider proxy and web service interface are different from those exposed by the Caching Application Block when it calls the backing store provider. Therefore, some of the methods in the provider must perform type conversion. For example, the AddNewItem method must extract the individual values from the CacheItem instance passed to it and match these to the types specified in the proxy interface.

The next listing shows how the AddNewItem method builds a String Array named infoData containing the item key, the "last accessed" date/time, and the sliding time cache duration in seconds:

   protected override void AddNewItem(
      int storageKey, CacheItem newItem)
      // set up information array values
      String[] infoData = new String[3];
      infoData[0] = newItem.Key;
      infoData[1] = newItem.LastAccessedTime.ToString();
      // see if there is a cache sliding expiration 
      // duration specified
      ICacheItemExpiration[] cie = newItem.GetExpirations();
      if ((cie.Length > 0) && (cie[0] is SlidingTime))
         // get sliding time value and convert to a string 
         // for the info array
         SlidingTime slidingDuration = (SlidingTime)
         infoData[2] = 
         // no duration specified, so use the default value
         infoData[2] = wsDefaultDuration.ToString();
Author's Note: This simple implementation supports only sliding time durations, though you could extend it to support the other types defined within the Caching Application Block if required.

The code then converts the cached item to a Base64-encoded string. The core Enterprise Library contains a SerializationUtility class that you can use to serialize and de-serialize objects:

      // serialize object and convert to Base64 encoding
      Byte[] itemBytes = SerializationUtility.ToBytes(
      String itemString = Convert.ToBase64String(itemBytes);
Finally, the method gets a reference to the proxy class and calls its AddNewItem method. The code handles any exceptions and generates error messages in the same way as you saw earlier for the Flush method:

      // get proxy instance, call method, and check for errors 
      ICustomCacheWebService proxy = GetWebServiceProxy();
      String errMessage = String.Empty;
         errMessage = proxy.AddNewItem(
            wsPartition, storageKey.ToString(),
            infoData, itemString);
      catch (Exception ex)
         throw new Exception(
            "Failed to execute AddNewItem method", ex);
      if (errMessage != String.Empty)
         throw new Exception(
            "Cannot add new item to cache partition '" 
            + wsPartition + "'. Web Service error: " + 
Loading Cached Items
When the Caching Application Block initializes, it loads any persisted cache items from the backing store by calling the provider's LoadDataFromStore method. This is perhaps the most complex method, because it must convert the String Array returned from the web service proxy that contains all the cached items into a Hashtable of CacheItem instances.

The first stage of the method, shown in the next listing, creates a Hashtable, gets a reference to the proxy, and calls its LoadDataFromStore method; specifying as usual the partition name defined for this provider instance. It checks whether any cached items exist, and if not, returns an empty Hashtable.

Note that the LoadFromDataStore method of the web service has to return a String Array of size 1 (instead of a simple String), when an error occurs. The code in the LoadDataFromStore method of the provider checks to see if an error occurred, and returns the details to the user:

   protected override System.Collections.Hashtable LoadDataFromStore()
      Hashtable cacheItems = new Hashtable();
      ICustomCacheWebService proxy = GetWebServiceProxy();
      String[] serviceCachedItems = 
      // see if cache partition is empty
      if ((serviceCachedItems == null) || 
         (serviceCachedItems.Length == 0))
         return cacheItems;
      // see if the Web Service returned an error
      if (serviceCachedItems.Length == 1)
         throw new Exception(
            "Cannot load items from cache partition '" + 
            wsPartition + "'. Web Service error: " + 
Provide that no error occurs, the next section of the method code iterates through the String Array, extracting the values for the cached item. Each cached item is stored as four consecutive values in the array, so the code indexes into the array to get each value for a cached item, and then skips four places to the next cached item.

For each cached item, the code extracts the key, the "last accessed" date/time, the sliding duration (in seconds), and the Base64-encoded string containing the cached object or value. The code converts these values to the appropriate types and then generates a new CacheItem instance, adds it to the Hashtable, and continues with the next cached item in the array:

      // read the cached item data into the Hashtable
      Int32 itemIndex = 0;
      while (itemIndex < (serviceCachedItems.Length - 1))
         String itemKey = serviceCachedItems[itemIndex];
         DateTime lastAccessed = DateTime.Parse(
            serviceCachedItems[itemIndex + 1]);
         TimeSpan slidingDuration = TimeSpan.Parse(
            serviceCachedItems[itemIndex + 2]);
         // deserialize object from .cachefile file
         Byte[] itemBytes = Convert.FromBase64String(
            serviceCachedItems[itemIndex + 3]);
         Object itemValue = SerializationUtility.ToObject(
         // create CacheItem and add to Hashtable
         CacheItem item = new CacheItem(lastAccessed, itemKey, 
            itemValue, CacheItemPriority.Normal, null, 
            new SlidingTime(slidingDuration));
         cacheItems.Add(itemKey, item);
         itemIndex += 4;
      return cacheItems;
The remaining methods in the provider class—Count (which calls the CachedItemCount method of the proxy), Remove, RemoveOldItem, and UpdateLastAccessedTime—work in much the same way as those you have just seen. Each method passes the partition name, the integer hash of the cache key (called the storage key), and any other required parameters to the proxy class methods. In addition, except for CachedItemCount, the web service returns any error message as a String that the method can use to generate an appropriate error message. Because the CachedItemCount method returns an integer value, the error indicator in this case is -1. The file CustomWebServiceBackingStore.cs in the downloadable code contains all the methods.

Adding Configuration and Design Support for the Provider
The custom Web Service Cache provider requires a configuration data class that exposes the values in the application configuration to the provider. This class, defined in the file CustomWebServiceCacheStorageData.cs (in the Caching\Configuration subfolder), looks and works much like the equivalent for the custom caching provider discussed in previous articles. The only difference is that it exposes the three custom properties for this provider, as well as the provider type and name.

To implement design support for the Enterprise Library configuration tools, you must also:

  • Create a "storage node" class that the configuration tools use to store and display the node for the custom provider.
  • Modify the registrar classes that add nodes and commands to the configuration tools.
  • Add the relevant entries to the resources file for the configuration classes.
The sample project includes a folder named EntLibSourceFiles that contains the files and code required to implement design support, including:

  • CustomWebServiceCacheStorageData.cs. This class stores the run-time settings and data for the provider.
  • CustomWebServiceCacheStorageNode.cs. This class implements the configuration node for the configuration tools and stores the configuration data.
  • CachingNodeMapRegistrar.cs.additions. This file contains the C# code you must add to the CachingNodeMapRegistrar.cs class that registers configuration nodes with the configuration tools.
  • CachingCommandRegistrar.cs.additions. This file contains the C# code you must add to the CachingCommandRegistrar.cs class that registers commands with the configuration tools.
  • Resources.resx.additions. This file contains the entries you must add to the Resources.resx file in the Caching\Configuration\Design\Properties folder of Enterprise Library.
To understand how these classes work, and exactly how you add or update them within Enterprise Library, see the articles in the Related Resources section of this article.

With the custom handler, proxy class, and design-support classes complete, it's time to compile the Caching Application Block and copy the assemblies to the required location. The easy way to do that is to run the BuildLibraryAndCopyAssemblies.bat file located in the EntLibSrc\App Blocks subfolder of the Enterprise Library source files. All the final assemblies, and the Configuration Console, reside in the EntLibSrc\App Blocks\bin subfolder.

Recall that, when using the unsigned version of the Enterprise Library assemblies in the EntLibSrc\App Blocks\bin folder, you must use the version of the Configuration Console (EntLibConfig.exe) located in this folder to configure your applications. See the sidebar "Editing Enterprise Library 3.x Configuration Files" for more information.

Building the Target Web Service
After updating Enterprise Library as discussed in the preceding sections of this article, the only remaining task is to build the web service that will communicate with the Caching Application Block and persist the cached items. How it does this depends on your own requirements.

The main constraints and requirements for the web service are:

  • It must implement the ICustomCacheWebService interface, and functionally perform in the correct manner by returning an error when a method fails
  • It must reside within the namespace you specified in the proxy class you created within Enterprise Library
  • It must be available to anonymous requests that use the SOAP protocol, unless you implement credential handling and presentation in your custom proxy and backing store provider
The sample web service, implemented by the class SampleCachingWebService in the SampleCacheWebService\App_Code subfolder of the examples, fulfills these requirements. To reference the ICustomCacheWebService interface, you must add a reference to the Caching Application Block assembly (or the namespace where you defined the interface) to your project and class:

   // in C#:
   using Microsoft.Practices.EnterpriseLibrary.Caching;
   ' in Visual Basic.NET:
   Imports Microsoft.Practices.EnterpriseLibrary.Caching
You define the namespace of the web service in the WebService attribute that decorates the class, and you should include the WebServiceBinding attribute that specifies conformance with the Basic Profile 1.1 as shown here:

   // in C#:
      ConformsTo = WsiProfiles.BasicProfile1_1)]
   ' in Visual Basic.NET:
      Namespace:="your-proxy-class-namespace")> _
      ConformsTo = WsiProfiles.BasicProfile1_1)> _
The class itself will inherit from System.Web.Services.WebService; you add the interface declaration for ICustomCacheWebService here as well:

   // in C#:
   public class SampleCachingWebService : 
      System.Web.Services.WebService, ICustomCacheWebService
   ' in Visual Basic.NET:
   Public Class SampleCachingWebService
      Inherits System.Web.Services.WebService
      Implements ICustomCacheWebService

Close Icon
Thanks for your registration, follow us on our social networks to keep up-to-date