A Configurable AppFabric Cache Attribute For Your WCF Services
Doing a search online for WCF and Cache will eventually lead you to a number of links* demonstrating the use of an IOperationInvoker implementation to manage caching. With AppFabric Cache rapidly becoming a mainstream enterprise technology I felt this to be an ideal time to revisit this concept.
I chose to offer the following features in my implementation
- Using the default or a named cache
- Using a cache region
- Action keyed caching
- Choice of using one or more parameters to the operation as a key
- Choice of XML serialized keys or a more compact form
- Choice of XmlSerializer or DataContractSerializer when using the XML serialization option
Using an IOperationInvoker implementation allows for the following pattern
Using the custom invoker in your code is straightforward and natural as the following snippet from the test service provided with the code attached to this blog demonstrates
/// <summary>
/// This method is for testing cache retrieval by key.
///
/// </summary>
/// <param name="catGuy"></param>
/// <returns></returns>
[OperationContract]
[AppFabricCacheBehavior]
CatMember GetCat(long catGuy);
/// <summary>
/// This method is for testing demonstrating cache by Action.
/// </summary>
/// <returns></returns>
[OperationContract]
[AppFabricCacheBehavior(CacheByAction=true)]
List<long> GetIds();
As you can see it’s only a matter of supplying an additional attribute along with your OperationContract..
The behavior code itself is very simple and contains descriptions of what the attribute parameters do
[AttributeUsage(AttributeTargets.Method)]
public class AppFabricCacheBehavior : Attribute, IOperationBehavior
{
/// <summary>
/// If this property is true the only key considered is Action.
/// A common use case scenario is a method that returns a slowly changing drop down list.
/// Setting this will cause all other non AppFabric Cache specific options to be ignored.
/// </summary>
public bool CacheByAction { get; set; }
/// <summary>
/// If this property is true then either DataContractSerializer or XmlSerializer will be used on keys and then concatenated.
/// By default this is false and the key will be a simple string concatenation
/// You should set this to true when using complex type(s) as a key.
/// Avoid setting this to true when you are using primitive type(s) as a key.
/// </summary>
public bool SerializeKeysToXml { get; set; }
/// <summary>
/// Substitutes XmlSerializer for DataContractSerializer when SerializeKeysToXml is true.
/// </summary>
public bool UseXmlSerializer { get; set; }
/// <summary>
/// Retrieves items from the named cache if not null. If CacheName is not specified DefaultCache will be used.
/// </summary>
public string CacheName { get; set; }
/// <summary>
/// Retrieves items from the named region.
/// </summary>
public string CacheRegion { get; set; }
/// <summary>
/// Indexes of the objects to generate key from. If not specified it will default to KeyIndexes = new int[]{0}.
/// </summary>
public int[] KeyIndexes { get; set; }
public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
{
}
public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{
dispatchOperation.Invoker = new AppFabricCachingInvoker(this, dispatchOperation.Invoker,dispatchOperation.Action);
}
public void Validate(OperationDescription operationDescription)
{
}
}
The only real work being done in this class is forwarding the original Invoker and passing in the Action.
Attribute Parameter Use Cases and Observations
While I was researching this article I noticed in some implementations they would unconditionally serialize all the parameters to XML and then concatenate them into a giant key. I still allow you to do this in my implementation if you choose to but I also give you a choice to use a simpler more compact format that will greatly reduce the memory usage when using primitive types. In simple test cases this cuts memory usage in 1/2 and I suspect in cases with very large payloads the effect is even more dramatic.
I also noticed some of the implementations did not take into account methods using zero parameters in their key calculations. It is a common occurrence to see Web Services built with zero parameter methods in order to provide for retrieving lists and such. By setting CacheByAction=true in the supplied code you will bypass key calculation logic and immediately use the Action parameter passed in as a key instead.
Test runs on my system ran approximately 3 times faster for cached responses versus having to access the database. As with any performance testing however, you will need to test in your own environment to see the effect a solution like the one presented might have.
Try It Out
Please give the code a test run and drop a comment if you would like a new feature or find what you think is a bug.
*The following links all demonstrate a form of caching using an IOperationInvoker implementation
Windows Communication Foundation Extensibility
Extending WCF with Custom Behaviors by Aaron Skonnard
Developing Custom OperationInvoker by Sankarsan
Use AppFabric Cache to cache your WCF Service response by Mikael Håkansson
Thanks to Paolo Salvatori, Valery Mizonov, and Quoc Bui for their review
Built for Quoc