Browse DevX
Sign up for e-mail newsletters from DevX


Creating Custom Providers for Enterprise Library : Page 4

When the providers installed with Enterprise Library don't meet your needs, take advantage of the library's pluggable architecture and roll your own.




Building the Right Environment to Support AI, Machine Learning and Deep Learning

Creating the Provider Class
For your custom provider to appear in the Configuration Console as a custom cache backing store, and be installable in the Caching Application Block, it must implement the IBackingStore interface and carry a ConfigurationElementType attribute indicating that it implements the class CustomCacheStorageData. The class BaseBackingStore implements IBackingStore, so inheriting from this satisfies the first condition. The following code example shows how the class carries the required attribute as well.

namespace Microsoft.Practices.EnterpriseLibrary.Caching. BackingStoreImplementations { [ConfigurationElementType(typeof(CustomCacheStorageData))] public class MyCustomBackingStore : BaseBackingStore { // name of the name/value pair declared in the application // configuration file <backingStores> section private const String filePathConfigurationName = "path"; // file extensions for the cached object and cache information files private const String dataExtension = ".cachedata"; private const String infoExtension = ".cacheinfo"; // internal variable to hold path for the cache files private String filePath = String.Empty; ...

If you are creating your custom provider within the Enterprise Library Visual Studio solution, within the BackingStores folder of the Caching Application Block section, you can use the existing namespace for your class as shown above.

The remainder of the code in the listing above declares the name for the one configuration value required in the configuration file for this provider—the path="..." attribute. It also declares the file extensions for each cached item's two files, and a variable to hold the configured file path value.

Creating the Class Constructor
You must provide a suitable constructor for your provider class with a signature that matches the way ASP.NET passes values from the application's configuration file to a provider. If your provider does not include custom design-time configuration support (as in this article), values from the application configuration file appear in a NameValueCollection passed to the constructor when the underlying ObjectBuilder utility instantiates the provider class.

Author's Note: If you implement a specific configuration class for your provider, so that it behaves like the standard providers in the Caching Application Block, values from the configuration file appear as individual parameters to the constructor. I'll show you how the Enterprise Library Configuration Console supports configuration of custom caching providers in a future article.

The custom caching provider described here takes a NameValueCollection containing a single configuration value that defines the full path to the folder where the cache files will be created. Here's the required constructor signature:

public MyCustomBackingStore(NameValueCollection configAttributes) { // get path to disk file passed in NameValueCollection String pathAttribute = configAttributes[filePathConfigurationName]; if (pathAttribute != String.Empty) { // save the file path filePath = pathAttribute; } else { throw new Exception("Error in application configuration, '" + filePathConfigurationName + "' attribute not found"); } }

Inside the preceding constructor, the code ensures that the configuration file contains the path attribute with a non-empty value, and saves it in the local variable named filePath. By default, the Configuration Console is not aware of the parameter requirements of a custom provider as so cannot validate them. Therefore, your code must check that all required attributes/parameters are present.

Implementing the Count Property
Most of the rest of the operations in the custom provider just consist of file access operations to manipulate the two files that store the details and data for each cached item. The Count property obtains an array of file names for files in the cache file folder that have the file extension specified for data files, and returns the length of the array:

public override int Count { get { String searchString = String.Concat("*", dataExtension); String[] cacheFiles = Directory.GetFiles(filePath, searchString, SearchOption.TopDirectoryOnly); return cacheFiles.Length; } }

Implementing the AddNewItem Method
Adding a new item to the cache involves creating the two new files required to store it. The Cache Manager passes the hashed storage key value (an integer) and the new CacheItem instance to your method override. The "info" file contains the (String) value of the key, the last access date and time, and the duration of the first "expiration" class in the array of expirations in the CacheItem.

Author's Note: For simplicity in this implementation the sample provider requires the CacheItem to use a SlidingTime instance for the first expiration in the array, and persists only this first expiration.

After creating an array containing the "info" values to store, the code creates the information file and writes all the lines in the array to it using the static WriteAllLines method of the File class. If a file with this name already exists, the code deletes it first. Because the BaseBackingStore class calls the RemoveOldItem method before calling AddNewItem, there should never be a preexisting file. However, any attempt to delete a locked or read-only file will raise an error:

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(); SlidingTime slidingDuration = (SlidingTime)newItem.GetExpirations().GetValue(0); infoData[2] = slidingDuration.ItemSlidingExpiration.ToString(); // create information file String infoFile = Path.Combine(filePath, String.Concat(storageKey.ToString(), infoExtension)); try { if (File.Exists(infoFile)) { File.Delete(infoFile); } File.WriteAllLines(infoFile, infoData); } catch { throw new FileNotFoundException( "Cannot create cache info file", infoFile); } ...

After creating the information file, the provider must serialize the data to cache, and write it to the data file using the same file name (the integer hash of the cache key converted to a String). Again, the code attempts to delete any existing file with this name to ensure that a problem with the file will raise an exception to the Cache Manager, which helps to maintain cache synchronization.

Enterprise Library contains many useful features that you can use in your own code, and that reduce the amount of code you have to write. In this case, the Caching Application Block in Enterprise Library already exposes a class named SerializationUtility that can convert an Object into a Byte array, and back again. The sample provider uses the static ToBytes method of this class to serialize the object to be cached, then writes it to the binary disk file using the static WriteAllBytes method of the File class:

... // serialize object and write to data file Byte[] itemBytes = SerializationUtility.ToBytes(newItem.Value); String dataFile = Path.Combine(filePath, String.Concat(storageKey.ToString(), dataExtension)); try { if (File.Exists(dataFile)) { File.Delete(dataFile); } File.WriteAllBytes(dataFile, itemBytes); } catch { throw new FileNotFoundException( "Cannot create cache data file", dataFile); } }

Author's Note: If you need to support multiple expiration types and different cache priorities, you must adapt the AddNewItem method implementation to store details of the expiration types and the cache priority, and their values, in the "information" file.

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