Cache App Block
Cache App Block
4
The Caching Application Block
177
5409_FEN_CH04_p177-240 5/15/06 3:35 PM Page 178
reality does not always match ideology. It is often the case that systems and
applications must be designed without specific information about how the
application needs to perform or scale. This, however, is not an excuse to
design a system that does not perform or scale well. Architects must strive
for a design that overcomes these challenges. It is important to remember
that caching isn’t something that can typically be added to an application at
any point in the development cycle; the application should be designed
with caching in mind.
The Microsoft patterns & practices team has published a lot of excellent
information on caching best practices, most notably the Caching Architecture
Guide for .NET Framework Applications.1 The section in the chapter that cov-
ers the design of the Caching Application Block details how the recom-
mendations in this guide are core to the design of Enterprise Library’s
Caching Application Block. The chapter describes how the application
block has been designed for extensibility and provides examples for how to
extend it. It also shows how to configure and develop an application so that
it can benefit from the features of the Caching Application Block.
Note that much of the information in this chapter is not new; rather, it
is a combination of parts of the Caching Architecture Guide for .NET Frame-
work Applications document and the Enterprise Library documentation for
the Caching Application Block. Most of the new information in this chapter
is where I show how to extend the Caching Application Block by way of a
custom Cache Storage Provider, expiration policy, and callback. I don’t
repeat all the information found in these guides, but I focus on the parts
that are specific to Enterprise Library’s implementation for caching.
1. Found at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/
html/CachingArch.asp.
5409_FEN_CH04_p177-240 5/15/06 3:35 PM Page 179
Performance
By storing data as close as possible to the consumer of the data, repetitive
data creation, processing of the data, and data retrieval can be avoided. Ref-
erence data like countries and states are excellent candidates for informa-
tion that should be cached, because this type of information rarely changes.
Therefore, it can be retrieved from the backend data source less frequently
and cached on an application server or Web server. This reduces or elimi-
nates the need to make multiple roundtrips to a database to retrieve this
type of data as well the need to recreate the same data for each request.
Eliminating these types of activities can dramatically improve an applica-
tion’s performance.
Scalability
Often the same data, business functionality, and user interface fragments
are required by many users and processes in an application. For example,
a combo box that lets users select a specific country in a form could be used
by all the users of a Web application regardless of who that user is or even
where they are in the Web application. If this information is processed for
5409_FEN_CH04_p177-240 5/15/06 3:35 PM Page 180
each request, valuable resources are wasted recreating the same output.
Instead, the page fragment can be stored in the ASP.NET output cache and
reused for each request. This improves the scalability of the application
because as the user base increases, the demand for server resources for
these tasks remains constant and the resources that would be used to ren-
der these results can now be used for other purposes. Furthermore, this
helps scale the resources of the backend database server. By storing fre-
quently used data in a cache, fewer database requests are made, meaning
that more users can be served.
Availability
Sometimes the services that provide information to an application may be
unavailable. This is very common, for example, in occasionally connected
smart client systems. By storing that data in another place, an application
may be able to survive system failures such as Web service problems or
hardware failures. Of course, this depends a lot on the type and amount of
the actual data that is cached and if the application has been designed to
cache information specifically to handle availability issues.
It is atypical and often inadvisable to cache all the information for an
application, especially if that data is not relatively static or is transactional
in nature. One exception to this rule, however, is if the application must be
designed to be available even when a backend data store is not available.
For example, it may be reasonable for the application to cache all of its
information because the data store has scheduled periods where it may be
offline or connectivity to the data source is unreliable. Each time a user
requests information from the data store while it is online, the information
can be returned and cached, updating the cache on each request. When the
data store becomes unavailable, requests can still be serviced using the
cached data until the data store comes back online.
2. This is true for the ASP.NET cache in .NET Framework 1.1. Microsoft has tested and does
support using the ASP.NET cache for non-Web scenarios for .NET Framework 2.0.
5409_FEN_CH04_p177-240 5/15/06 3:35 PM Page 182
ers can configure each of these named caches differently using the
Enterprise Library Configuration Tool.
• When cache items require a combination of expiration settings for
absolute time, sliding time, extended time format (e.g., every
evening at midnight), file dependency, or never expired. The
ASP.NET cache supports absolute time and sliding time expirations;
however, it does not support setting both expirations at one time.
Only one type of expiration can be set for a particular cache item.
The CacheManager supports setting multiple types of cache item
expirations at the same time.
Except for the last bullet point, the design goals for the Caching Appli-
cation Block are the same. Additionally, the design goals for the Caching
Application Block include providing a caching API that is easy to use, easy
to maintain, and easy to configure. Furthermore, the caching solution needs
to perform efficiently and must be reliable by ensuring that the BackingStore
remains intact if an exception occurs while the cache is being accessed.
One of the most important design goals with this version of the Caching
Application Block was to ensure that the cache is thread safe, which helps to
ensure that the states of the in-memory cache and the BackingStore remain
synchronized. The following sections define the primary classes in the Caching
Application Block and explain how they are used to accomplish these design
goals. Figure 4.1 provides a high-level overview of many of these classes.
(e.g., customer data, countries, zip codes, etc.) are being stored, it is best to
store them in different caches—one cache for each type of item. This
increases the efficiency of searching when retrieving an item because it
reduces the number of items in each cache. In the Caching Application
Block, all caching operations occur through the CacheManager class. The
CacheManager class provides all the methods needed to add, retrieve, and
remove items from the cache.
All of the Enterprise Library application blocks are designed using the
factory design pattern. Factories are objects that exist solely to create other
objects. In the Caching Application Block, the CacheManagerFactory is
used to create an instance of a CacheManager. The CacheManagerFactory
uses the features provided by the Configuration Application Block to
retrieve configuration information and determine which CacheManager
should be created. The CacheManagerFactory class has two overloads for
its GetCacheManager method that are used to create a CacheManager: one
overload takes the name of a CacheManager and the other one doesn’t take
any arguments. The overload that requires an argument will initialize the
CacheManager with the name that is supplied. The overload that takes no
arguments initializes the CacheManager that is configured as the Default-
CacheManager in the configuration data for this application block. In both
cases, the private CreateCacheManager method is called to ultimately cre-
ate the CacheManager.
In its CreateCacheManager method, the CacheManagerFactory first
creates a ScavengingPolicy and BackingStore and uses the instances of
these objects to construct the actual Cache object that the CacheManager
will encapsulate. It then creates instances of the ExpirationTask and
ScavengingTask and uses these instances to create a new Background-
Scheduler. The BackgroundScheduler is used to initialize the underly-
ing cache.
An ExpirationPollTimer is also created, and both the Background-
Scheduler and the ExpirationPollTimer are started. The instances of
the cache, BackgroundScheduler, and ExpirationPollTimer are passed
into the CacheManager’s constructor to create the new instance. This new
instance is then added to the HashTable of CacheManagers that the
CacheManagerFactory manages. Therefore, the CacheManager is held in
5409_FEN_CH04_p177-240 5/15/06 3:35 PM Page 186
the scope of the application, and as such, its cache can be accessed from any
class or thread concurrently without the need to recreate the CacheMan-
ager class multiple times.
It is important to note that the CacheManager does not hold any state
and is simply a front-end interface to the cache. This design allows the
CacheManager to provide the quickest possible response times to the cache
client by performing any operations on the cache metadata after returning
the control to the cache client. Because it is such a critical method, I have
included the code for the CreateCacheManager in Listing 4.1.
CachingConfigurationView view =
new CachingConfigurationView(ConfigurationContext);
CacheManagerData cacheManagerData =
view.GetCacheManagerData(cacheManagerName);
CacheCapacityScavengingPolicy scavengingPolicy =
new CacheCapacityScavengingPolicy(cacheManagerName, view);
IBackingStore backingStore =
backingStoreFactory.CreateBackingStore(cacheManagerName);
Cache cache = new Cache(backingStore, scavengingPolicy);
scheduler.Start();
timer.StartPolling(new
TimerCallback(scheduler.ExpirationTimeoutExpired),
cacheManagerData.ExpirationPollFrequencyInSeconds * 1000);
5409_FEN_CH04_p177-240 5/15/06 3:35 PM Page 187
[Visual Basic]
Private Function CreateCacheManager(ByVal cacheManagerName As String) _
As CacheManager
scheduler.Start()
timer.StartPolling(New TimerCallback( _
AddressOf scheduler.ExpirationTimeoutExpired), _
cacheManagerData.ExpirationPollFrequencyInSeconds * 1000)
When the internal Cache object is constructed, all data in the Backing-
Store is loaded into an in-memory representation that is contained in the
Cache object. This is the only time that the BackingStore is ever read—when
an application makes changes to the cache, the changes are written to both
the internal cache and the BackingStore. An application can make requests
to the CacheManager object to retrieve cached data, add data to the cache,
and remove data from the cache, and it should always be synchronized
with the BackingStore. Table 4.1 describes the methods that the CacheMan-
ager class exposes for performing these functions.
Another class, CacheFactory, refines the CacheManagerFactory class
with static methods that simply pass through to an instance of the Cache-
ManagerFactory class. This provides a simpler interface for developers
because it allows a CacheManager to be created without directly having to
instantiate a factory class, and it just contains a single method: GetCache-
Manager. GetCacheManager contains two overloads: one overload accepts
Method/Property Description
Flush Removes all items and metadata from the cache. If an error
occurs during the removal, the cache is left unchanged.
Remove Removes the given item and its metadata from the cache. If no
item exists with that key, this method does nothing.
5409_FEN_CH04_p177-240 5/15/06 3:35 PM Page 189
Cache Objects
A Cache object receives requests from a CacheManager and implements all
operations between the BackingStore and the in-memory representation of
the cached data. A cache is simply a copy of the master data stored in mem-
ory or on disk. Therefore, a Cache object simply contains a hash table that
holds the in-memory representation of the data; however, that item of data
must first be packaged as a CacheItem object. A CacheItem includes the
data itself, together with other information such as the item’s key, its pri-
ority, a RefreshAction, and an array of expiration policies. (All of these
classes are explained in detail in the following sections.) The Cache object
uses a hash table as a lock to control access to the items in the cache, both
from the application and from the BackgroundScheduler. It also provides
thread safety for the entire Caching Application Block.
When an application adds an item to the cache by calling CacheMan-
ager’s Add method, the CacheManager simply forwards the request to the
Cache object. If there isn’t an item in the in-memory hash table that matches
the key for the item being added, the Cache object will first create a dummy
cache item and add it to an in-memory hash table. Then, whether the item
exists or not, it will use the item found for this key as a snapshot of the item
before performing the insert. It then locks the cache item in the in-memory
hash table, adds the item to BackingStore, and finally replaces the existing
cache item in the in-memory hash table with the new cache item. (In the
case where the item was not already in the in-memory hash table, it
replaces the dummy item.)
If there is an exception while writing to the BackingStore, it removes the
dummy item added to the in-memory hash table and does not continue.
The Caching Application Block enforces a strong exception safety guaran-
tee. This means that if an Add operation fails, the state of the cache rolls back
5409_FEN_CH04_p177-240 5/15/06 3:35 PM Page 190
to what it was before it tried to add the item. In other words, either an oper-
ation is completed successfully or the state of the cache remains unchanged.
(This is also true for the Remove and Flush methods.)
If the number of cached items exceeds a predetermined limit when the
item is added, the BackgroundScheduler object begins scavenging. When
adding an item, the application can use an overload of the Add method to
specify an array of expiration policies, the scavenging priority, and an
object that implements the ICacheItemRefreshAction interface. As
explained later in this chapter, a RefreshAction receives a notification
when an item is removed from the cache.
When an application calls the CacheManager’s GetData method to
retrieve an item, the CacheManager object forwards the request to the
Cache object. If the item is in the cache, it is returned from the Cache’s in-
memory representation. If it isn’t in the cache, the request returns the value
null (or Nothing in VB.NET). If the item is expired, the item also returns the
value null (or Nothing in VB.NET).
CacheService Objects
As described in the section “Custom Cache Detailed Design” of the Caching
Architecture Guide for .NET Framework Applications, a CacheManager
object has references to both a CacheStorage and a CacheService object.
The CacheStorage object is used for inserting, getting, and removing items
from the cache storage. The Caching Application Block implements this
design by way of the BaseBackingStore class (and classes that inherit
from it). The CacheService object is designed to manage metadata that
may be associated with CacheItems. This metadata may include items like
expiration policies, priorities, and callbacks. While a single CacheService
class does not exist in the Caching Application Block, the functionality that
such a service is designed to implement does exist by way of the Back-
groundScheduler and ExpirationPollTimer classes.
Expiration Policies
An important aspect of caching state is the way in which it is kept consis-
tent with the master data and other application resources. Expiration poli-
cies can be used to define the contents of a cache that are invalid based on
the amount of time that the data has been in the cache or on notification
from another resource. The first type of expiration policy is known as a
time-based expiration and the second is known as a notification-based
expiration.
The Caching Application Block’s expiration process is performed by the
BackgroundScheduler that periodically examines the CacheItems to see
if any items have expired. The ExpirationPollFrequencyInSeconds set-
ting for a CacheManager controls how frequently the expiration cycle
occurs for that instance of the CacheManager. Expiration is a two-part
process. The first part is known as marking and the second part is known
as sweeping. The process is divided into separate tasks to avoid any con-
5409_FEN_CH04_p177-240 5/15/06 3:35 PM Page 192
flicts that can occur if the application is using a cache item that the Back-
groundScheduler is trying to expire.
The Caching Application Block ships with four expiration policies; three
are time-based expirations and one is a notification-based expiration. The
time-based expirations are AbsoluteTime, SlidingTime, and Extended-
FormatTime. The notification-based expiration is FileDependency. Fur-
thermore, the Caching Application Block provides the capability for adding
a custom extension policy to the ones that already exist by creating a new
class that implements the ICacheItemExpiration interface. This inter-
face, as well as the expiration policies that ship with Enterprise Library, are
shown in Figure 4.2.
Time-Based Expirations. Time-based expirations invalidate data based
on either relative or absolute time periods. Use time-based expiration when
volatile cache items, such as those that have regular data refreshes or those
that are valid for only a set amount of time, are stored in a cache. Time-
based expiration enables policies to be set that keep items in the cache only
as long as their data remains current. For example, if an application dis-
plays product information that gets updated in the product catalog once a
day at most, the product information can be cached for the time that those
products remain constant in the catalog, that is, for a 24-hour period.
5409_FEN_CH04_p177-240 5/15/06 3:35 PM Page 193
[Visual Basic]
Dim expiryTime As DateTime = New DateTime(2007, 7, 26, 0, 0, 0)
Dim absExpiryTime As AbsoluteTime = New AbsoluteTime(expiryTime)
5409_FEN_CH04_p177-240 5/15/06 3:35 PM Page 195
[Visual Basic]
Dim expireTime As ExtendedFormatTime = _
New ExtendedFormatTime("0 0 * * 6")
[Visual Basic]
Dim expiryTime As TimeSpan = New TimeSpan(0, 5, 0)
Dim slideExpireTime As SlidingTime = New SlidingTime(expiryTime)
[Visual Basic]
Dim expireNotice As FileDependency = New FileDependency(“Products.XML”)
5409_FEN_CH04_p177-240 5/15/06 3:35 PM Page 196
productsCache.Add(myProduct.ProductID, myProduct, _
CacheItemPriority.Normal, Nothing, expireNotice)
{
DateTime currentLastWriteTime = DateTime.MinValue;
Database db =
DatabaseFactory.CreateDatabase
(dependencyDatabaseInstance);
IDataReader dataReader =
db.ExecuteReader
("GetLastNotificationDate", dependencyTableName);
if( dataReader.Read())
currentLastWriteTime = dataReader.IsDBNull(0) ?
DateTime.MinValue :
dataReader.GetDateTime( 0 );
dataReader.Close();
if (lastModifiedTime.Equals(DateTime.MinValue))
{
lastModifiedTime = currentLastWriteTime;
}
if (lastModifiedTime.Equals(currentLastWriteTime) == false)
{
lastModifiedTime = currentLastWriteTime;
bRetVal = true;
}
}
catch (Exception e)
{
throw new ApplicationException(String.Format("{0}: {1}",
SR.ExceptionInvalidDatabaseNotificationInfo
(dependencyTableName),e.Message), e);
}
return bRetVal;
[Visual Basic]
Public Function HasExpired() As Boolean
Dim bRetVal As Boolean = False
Try
Dim currentLastWriteTime As DateTime = DateTime.MinValue
Dim db As Database = _
DatabaseFactory.CreateDatabase _
(dependencyDatabaseInstance)
If dataReader.Read() Then
5409_FEN_CH04_p177-240 5/15/06 3:35 PM Page 198
currentLastWriteTime = IIf(dataReader.IsDBNull(0), _
DateTime.MinValue, _
dataReader.GetDateTime(0))
End If
dataReader.Close()
If lastModifiedTime.Equals(DateTime.MinValue) Then
lastModifiedTime = currentLastWriteTime
End If
[Visual Basic]
'Monitor the Products table in the Northwind DB instance.
Dim expireNotice As DatabaseDependency = _
New DatabaseDependency (“Northwind”, “Products”);
productsCache.Add(myProduct.ProductID, myProduct, _
CacheItemPriority.Normal, Nothing, expireNotice)
work Applications; however, it seems to deviate on this one item. The dele-
gate that is described as the CacheItemRemovedCallback in the Caching
Architecture Guide for .NET Framework Applications is known as the
ICacheItemRefreshAction interface in the Caching Application Block.
During the development of the Caching Application Block, the respon-
sibility for this delegate changed. Originally, callbacks were only designed
for expirations, and the purpose of the callback was solely to allow the
owner of that item to refresh it in the cache. However, as development pro-
gressed, the requirement surfaced that callbacks were needed for removals
and scavengings too, but the name was never changed. So, even though the
name implies that an implementation of this interface should refresh a
cached item, it is not necessary to do so. Rather, the ICacheItemRefre-
shAction interface just defines the contract that must be implemented so
that an object will be notified when an item is removed from cache. It is then
up to that implementation to determine what action should occur.
It is important to note that the implementing class of an ICacheItem-
RefreshAction must be serializable. Take care when implementing this
interface not to create an object that maintains too much state about its envi-
ronment, because all portions of its environment will be serialized as well,
possibly creating a huge object graph. Figure 4.3 illustrates the ICacheIt-
emRefreshAction interface as well as the enumeration that is passed to the
Refresh method, which lists the possible values for why an item may have
[Visual Basic]
Public Class LoggingRefreshAction : Inherits ICacheItemRefreshAction
Public Sub Refresh(ByVal key As String, _
ByVal expiredValue As Object, _
ByVal removalReason As CacheItemRemovedReason)
CacheStorage
The Caching Architecture Guide for .NET Framework Applications defines the
third major component of a custom cache triad to be CacheStorage. The
CacheStorage implementation separates the cache functionality from the
cache data store. The Caching Application Block implements this design
and provides an extension point to the block with the IBackingStore
interface and the BaseBackingStore abstract base class. This interface
defines the contract that must be implemented by all BackingStores.
Implementers of this method are responsible for interacting with their
underlying persistence mechanisms to store and retrieve CacheItems. All
methods must guarantee Weak Exception Safety—that operations must
complete entirely, or they must completely clean up from the failure and
leave the cache in a consistent state. The mandatory cleanup process will
remove all traces of the item that caused the failure, causing that item to be
expunged from the cache entirely.
The abstract BaseBackingStore class, which implements the IBack-
ingStore interface, is provided to facilitate the creation of BackingStores.
This class contains implementations of common policies and utilities that
can be used by all BackingStores. Table 4.2 lists the BaseBackingStore’s
methods and properties. All methods other than the Add, CurrentCache-
Manager, and Load methods are abstract and must therefore be overridden
by a concrete BackingStore.
The concrete cache storage classes that are included with the Caching
Application Block are the NullBackingStore, the IsolatedStorage-
BackingStore, and the DataBackingStore.
Method/Property Description
Disk-Resident Cache
A disk-resident cache contains technologies that use disk-based data stor-
ages, such as files or databases. Disk-based caching is useful when large
amounts of data need to be handled, the data in the application services
may not always be available for reacquisition, or the cached data must sur-
vive process recycles and computer reboots. Both the overhead associated
with data processing and interprocess communications can be reduced by
storing data that has already been transformed or rendered nearer to the
data consumer.
If a CacheManager has been configured to use a persistent BackingStore,
the Caching Application Block will load the cache contents from the Back-
ingStore when the cache is first created. After the initial load, the Backing-
Store is updated after each operation on the in-memory cache. However,
the BackingStore is never read from again (unless the cache is disposed and
recreated, for example, on application restart). It is also important to note
that while an application can use more than one CacheManager, the
Caching Application Block does not support the use of the same persistent
BackingStore location and partition name by multiple CacheManagers in
an application. For example, configuring an application with two Cache-
Managers that both leverage isolated storage and have a partition name of
ProductCache will most likely cause data corruption.
In its original state, the Caching Application Block supports two types
of persistent BackingStores, each of which is suited to particular situations:
isolated storage and data cache storage. Additionally, you can also extend
5409_FEN_CH04_p177-240 5/15/06 3:35 PM Page 205
instance name where both leverage the same Data Access Application
Block configuration. For example, two distinct applications that are both
configured to use the DataBackingStore, both named Northwind, and
both have CacheManagers named ProductCache will be seen as sharing a
CacheManager across application domains and is not supported by the
Caching Application Block. Rather, every application that leverages the
data cache store should have its own instance and partition.
It is possible, however, to have the same application run in multiple
processes (for example, the application is deployed on multiple computers
in a Web farm). There are three possible ways to configure the Caching
Application Block for this circumstance.
CachingConfigurationView cachingConfigurationView =
(CachingConfigurationView) configurationView;
CustomCacheStorageData customConfiguration =
(CustomCacheStorageData)
cachingConfigurationView.GetCacheStorageDataForCacheManager
(CurrentCacheManager);
partitionName = customConfiguration.Extensions["PartitionName"];
xmlFileName = String.Format("{0}.{1}",
customConfiguration.Extensions["XmlFileName"],partitionName);
if (customConfiguration.StorageEncryption != null)
{
StorageEncryptionFactory encryptionFactory = new
StorageEncryptionFactory
(cachingConfigurationView.ConfigurationContext);
encryptionProvider =
encryptionFactory.CreateSymmetricProvider
(CurrentCacheManager);
}
}
[Visual Basic]
Public Overrides Sub Initialize _
(ByVal configurationView As ConfigurationView)
ArgumentValidation.CheckForNullReference _
(configurationView, "configurationView")
ArgumentValidation.CheckExpectedType _
(configurationView, GetType(CachingConfigurationView))
partitionName = customConfiguration.Extensions("PartitionName")
xmlFileName = String.Format("{0}.{1}", _
customConfiguration.Extensions("XmlFileName"),partitionName)
New StorageEncryptionFactory _
(cachingConfigurationView.ConfigurationContext)
encryptionProvider = _
encryptionFactory.CreateSymmetricProvider _
(CurrentCacheManager)
End If
End Sub
uses an XML file, the custom BackingStore will need to retain information
about the name of the XML file and the partition name. Therefore, a data
transfer object must be created that encapsulates this information. This
object will be used by design-time classes for setting the configuration
information, and it will be used by the custom BackingStore to initialize
itself in its Initialize method. Listing 4.10 shows two properties of an
xmlFileCacheStorageData class that let the file name and partition name
be set and retrieved.
[XmlAttribute("partitionName")]
public string PartitionName
{
get { return partitionName; }
set { partitionName = value; }
}
[Visual Basic]
<XmlAttribute("xmlFileName")> _
Public Property XmlFileName() As String
Get
Return xmlFileName
End Get
Set
xmlFileName = Value
End Set
End Property
<XmlAttribute("partitionName")> _
Public Property PartitionName() As String
Get
Return partitionName
End Get
Set
partitionName = Value
End Set
End Property
5409_FEN_CH04_p177-240 5/15/06 3:35 PM Page 213
The second task that must be completed is the same as the task that
needed to be completed for the previous type of custom BackingStore; that
is, to create a class that inherits from the abstract BaseBackingStore class
and override the abstract methods. The only difference between this class
and the one created for the previous type is that in this class’ Initialize
method, the data transfer object that was just created will be used instead
of the CustomCacheStorageData class. Listing 4.11 shows what the
revised Initialize method would look like.
CachingConfigurationView cachingConfigurationView =
(CachingConfigurationView) configurationView;
xmlFileCacheStorageData xmlFileConfiguration =
(xmlFileCacheStorageData)
cachingConfigurationView.GetCacheStorageDataForCacheManager
(CurrentCacheManager);
partitionName = xmlFileConfiguration.PartitionName;
xmlFileName = String.Format("{0}.{1}",
xmlFileConfiguration.XmlFileName,
xmlFileConfiguration.PartitionName);
if (xmlFileConfiguration.StorageEncryption != null)
{
StorageEncryptionFactory encryptionFactory = new
StorageEncryptionFactory
(cachingConfigurationView.ConfigurationContext);
encryptionProvider =
encryptionFactory.CreateSymmetricProvider
(CurrentCacheManager);
}
}
[Visual Basic]
Public Overrides Sub Initialize _
5409_FEN_CH04_p177-240 5/15/06 3:35 PM Page 214
ArgumentValidation.CheckForNullReference _
(configurationView, "configurationView")
ArgumentValidation.CheckExpectedType _
(configurationView, GetType(CachingConfigurationView))
encryptionProvider = _
encryptionFactory.CreateSymmetricProvider _
(CurrentCacheManager)
End If
End Sub
The last step is to create the design-time classes that allow the Enterprise
Library Configuration Tool to present the user-friendly interface that makes
configuring a new BackingStore easier and less error-prone. This step is not
absolutely necessary; a new BackingStore can still be used even if design-
time classes for it do not exist. In that case, however, the configuration infor-
mation for it needs to be entered and modified manually, and the benefits
with respect to validating configuration information will not be realized.
Three tasks must be performed to create the design-time classes needed
to configure a new BackingStore.
Chapter 2 provides much more detail about how and why these classes
need to be created. The next few paragraphs document the specific steps
needed to create the design-time interface for the XmlFileBackingStore.
The first task is to create a new ConfigurationNode that provides a user
with the ability to add and modify the configuration properties of the Xml-
FileBackingStore. The specific properties that need to be exposed are the
FileName and the PartitionName. The Caching Application Block’s
design-time assembly provides an abstract base class named CacheStor-
ageNode that makes it easier to create a ConfigurationNode for a Backing-
Store. Listing 4.12 shows the XmlFileCacheStorageNode class that is
derived from the CacheStorageNode base class.
public XmlFileCacheStorageNode():
this(new xmlFileCacheStorageData(SR.XmlFileCacheStorage))
{
}
[Browsable(false)]
public override string Type
{
get { return xmlFileCacheStorageData.TypeName; }
}
[Required]
[SRDescription(SR.Keys.FileNameDescription)]
[SRCategory(SR.Keys.CategoryGeneral)]
public string FileName
{
get { return xmlFileCacheStorageData.XmlFileName; }
set { xmlFileCacheStorageData.XmlFileName = value; }
}
5409_FEN_CH04_p177-240 5/15/06 3:35 PM Page 216
[Required]
[SRDescription(SR.Keys.FilePartitionNameDesciption)]
[SRCategory(SR.Keys.CategoryGeneral)]
public string PartitionName
{
get { return xmlFileCacheStorageData.PartitionName; }
set { xmlFileCacheStorageData.PartitionName = value; }
}
}
[Visual Basic]
Public Class XmlFileCacheStorageNode : Inherits CacheStorageNode
Private xmlFileCacheStorageData As xmlFileCacheStorageData
<Browsable(False)> _
Public Overrides ReadOnly Property Type() As String
Get
Return xmlFileCacheStorageData.TypeName
End Get
End Property
<Required, _
SRDescription(SR.Keys.FileNameDescription), _
SRCategory(SR.Keys.CategoryGeneral)> _
Public Property FileName() As String
Get
Return xmlFileCacheStorageData.XmlFileName
End Get
Set
xmlFileCacheStorageData.XmlFileName = Value
End Set
End Property
<Required, _
SRDescription(SR.Keys.FilePartitionNameDesciption), _
SRCategory(SR.Keys.CategoryGeneral)> _
Public Property PartitionName() As String
Get
Return xmlFileCacheStorageData.PartitionName
End Get
Set
xmlFileCacheStorageData.PartitionName = Value
End Set
End Property
End Class
5409_FEN_CH04_p177-240 5/15/06 3:35 PM Page 217
xmlIncludeTypeService.AddXmlIncludeType
(CacheManagerSettings.SectionName,
typeof(xmlFileCacheStorageData));
}
NodeCreationEntry entry =
NodeCreationEntry.CreateNodeCreationEntryNoMultiples
(new AddChildNodeCommand(serviceProvider, nodeType), nodeType,
typeof(xmlFileCacheStorageData), SR.XmlFileCacheStorage);
nodeCreationService.AddNodeCreationEntry(entry);
}
[Visual Basic]
Private Shared Sub RegisterXmlIncludeTypes _
(ByVal serviceProvider As IServiceProvider)
(GetType(IXmlIncludeTypeService)) Is IXmlIncludeTypeService, _
CType(serviceProvider.GetService _
(GetType(IXmlIncludeTypeService)), IXmlIncludeTypeService), _
CType(Nothing, IXmlIncludeTypeService))
xmlIncludeTypeService.AddXmlIncludeType _
(CacheManagerSettings.SectionName, GetType(xmlFileCacheStorageData))
End Sub
nodeCreationService.AddNodeCreationEntry(entry)
End Sub
[Visual Basic]
<assembly : _
ConfigurationDesignManager( _
GetType(xmlFileBackingStoreConfigurationDesignManager))
>
5409_FEN_CH04_p177-240 5/15/06 3:35 PM Page 219
Once this assembly has been compiled and deployed so the Enterprise
Library Configuration Tool can access it, you can add and configure the
XmlFileBackingStore just as easily as any of the BackingStores that
ship with Enterprise Library’s Caching Application Block. (You’ll see how
to do this for all of the BackingStores a little later in the chapter.) Figure 4.5
shows the list of options for adding a BackingStore in the Enterprise
Library Configuration Tool once this new assembly has been deployed.
220