Designing a Custom Provider
Before you fire up Visual Studio and start writing code for a custom provider, you should consider some of the important points regarding the design and implementation of providers in general. Your custom provider should follow the same design principles as the application blocks wherever practical. This includes:
- Adhering to object-oriented design principles
- Making use of appropriate design patterns
- Using resources efficiently
- Applying best practice principles for security, such as distrust of user input and the principle of least privilege
You must also avoid creating a provider that changes the fundamental design aims or the nature of the block, because doing so may affect stability and cause errors outside of the provider. For example, creating a non-symmetric provider for the Cryptography Application Block is likely to affect the way that the block works, because its design only fully supports symmetric algorithms.
In the case of the Caching Application Block, any provider you create must meet the aims described for the block in the Enterprise Library Documentation (available from the Enterprise Library section of your Start menu). For example, the Caching Application Block is designed to perform efficiently and be thread-safe. It also ensures that the backing store remains intact if an exception occurs while it is being accessed, and that the in-memory cache and the backing store remain synchronized at all times. To help meet these aims, your custom cache provider must raise exceptions that the block or the client code can handle if an error occurs that may affect the backing store content or synchronization between the in-memory and persistent caches.
Deciding Where to Start
All the application blocks define an interface for the providers they use, and many contain a base class from which you can inherit from when creating a custom provider. The Caching Application Block defines the IBackingStore interface, which contains the members shown in Table 1.
Table 1. IBackingStore Interface Members: The table shows the IBackingStore Interface members and a description of each.
||A read-only integer property that returns the number of objects in the backing store.
||This method takes as a parameter a new cache item and adds it to the backing store. This operation must succeed even if an item with the same key already exists. If any part of the process fails, it must remove both the existing and new item from the backing store.
||This method takes as a parameter the (String) key of an existing item, and removes that item from the backing store.
||This method takes two parameters: the (String) key of an item and a DateTime instance and updates the last accessed time property of that cached item.
||This method, which takes no parameters, flushes all stored items from the cache.
||This method, which takes no parameters, returns a HashTable containing all the items from the backing store.
|Figure 3. Component Interaction: The figure shows how the code components in the Caching Application Block interact.|
The Caching Application Block also contains a base class named BaseBackingStore, which automatically implements the rule on the Add
method of the IBackingStore interface by first calling the RemoveOldItem
method and then the AddNewItem
method in the class that inherits from it. If either of these methods fails, it calls the overridden RemoveItem
method to ensure cache consistency before throwing an exception to the routines within the application block. You can considerably reduce the amount of code you have to write by using this base class as the starting point for your custom provider.
shows how the methods and property exposed to the client application through the Cache Manager relate to the methods and property of the inter-component interfaces within the block.
As you can see from Figure 3
, the BaseBackingStore class exposes abstract methods that you must override in your provider. You must implement one property and six methods in your custom backing store provider when inheriting from BaseBackingStore. Here are the property and method definitions:
// return the number of objects in the backing store
public override int Count
// add a new item to persistence store
protected override void AddNewItem(
int storageKey, CacheItem newItem)
// flush all items from the backing store
public override void Flush()
// load all items from the underlying store
// without filtering expired items
protected override Hashtable LoadDataFromStore()
// remove an item with the specified storage key
// from the backing store--should throw an error
// if the item does not exist
protected override void Remove(int storageKey)
// remove an existing item with same key as a new item from
// the persistence store
// should not throw an error if the item does not exist
// called before a new item with the same key is added to the cache
protected override void RemoveOldItem(int storageKey);
// update the last accessed time for the specified cache item
protected override void UpdateLastAccessedTime(int storageKey, DateTime timestamp)
If you need to dispose of managed or un-managed resources, for example by closing or deleting files, you can override the Dispose
methods of the BaseBackingStore.
|Author's Note: You reference individual cache items using the integer storage key value, which is the hash code of the string key value the user provides for the cache item. The Caching Application Block converts between the string and integer values for you, and all operations within your provider use the integer storage key. The cache item includes a field containing the string key value, as you will see later in this article.