Caching in WCF?
If you are using .NET 4, the recommended way is to use MemoryCache
Any caching solution should address two basic problems
1) Storage of cache items and retrieval
2) Cache invalidation
Since Http caching is a well known one I am not going to explain it in detail. You can use asp compatibility attribute alone with some web configuration, where you will get caching by charm.
[AspNetCacheProfile("MyProfile")]
public Customer GetName(string id)
{
// ...
}
And the web config is like
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
</system.serviceModel>
<system.web>
<caching>
<outputCacheSettings>
<outputCacheProfiles>
<add name=" MyProfile" duration="600" varyByParam="none" sqlDependency="MyTestDatabase:MyTable"/>
</outputCacheProfiles>
</outputCacheSettings>
</caching>
</system.web>
But this is not suitable for most scenarios especially when you have large complex object to cache. For example I had a situation where I wanted to cache a system generated image (the output of the operation contract is a system generated image that depends on the input). In such a case, you have to implement your own cache. I have used Microsoft enterprise library caching blocks that addressed all my caching storage requirements. However, you still need to do the plumbing to integrate Microsoft enterprise library caching block with your WCF service. First you have to intercept the WCF communication channel to implement the cache. A detail discussion of how to intercept the WCF communication channel can be found at http://msdn.microsoft.com/en-us/magazine/cc163302.aspx. This is how you do the plumbing for WCF caching
Step 0 Let’s say you have an operation contract as follows and you want to cache the item return by that method.
[OperationContract]
MyCompositeClass Rotate(int angle)
Step 1 First you have to register your custom cacher in the WCF pipeline. To do that I am going to use an attribute so that I can nicely decorate my WCF call according to aspect orient programming principles.
using System;
using System.ServiceModel.Description;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.Reflection;
[AttributeUsage(AttributeTargets.Method)]
public class MyCacheRegister : Attribute, IOperationBehavior
{
ConstructorInfo _chacherImplementation;
public ImageCache(Type provider)
{
if (provider == null)
{
throw new ArgumentNullException("Provider can't be null");
}
else if (provider.IsAssignableFrom(typeof(IOperationInvoker)))
{
throw new ArgumentException("The type " + provider.AssemblyQualifiedName + " does not implements the interface " + typeof(IOperationInvoker).AssemblyQualifiedName);
}
else
{
try
{
Type[] constructorSignatureTypes = new Type[1];
constructorSignatureTypes[0] = typeof(IOperationInvoker);
_chacherImplementation = provider.GetConstructor(constructorSignatureTypes);
}
catch
{
throw new ArgumentException("There is no constructor in " + provider.AssemblyQualifiedName + " that accept " + typeof(IOperationInvoker).AssemblyQualifiedName + " as a parameter");
}
}
}
public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
{
return;
}
public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
{
return;
}
/// <summary>
/// Decorate the method call with the cacher
/// </summary>
public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{
//decorator pattern, decorate with a cacher
object[] constructorParam = new object[1];
constructorParam[0] = dispatchOperation.Invoker;
dispatchOperation.Invoker = (IOperationInvoker)_chacherImplementation.Invoke(constructorParam);
}
public void Validate(OperationDescription operationDescription)
{
return;
}
}
Step 2
Then you have to implement the point where cache object will be retrieved.
using System;
using System.ServiceModel.Dispatcher;
using Microsoft.Practices.EnterpriseLibrary.Caching;
using Microsoft.Practices.EnterpriseLibrary.Common;
using System.IO;
class RotateCacher : IOperationInvoker
{
private IOperationInvoker _innerOperationInvoker;
public RotateImageCacher(IOperationInvoker innerInvoker)
{
_innerOperationInvoker = innerInvoker;
}
public object[] AllocateInputs()
{
Object[] result = _innerOperationInvoker.AllocateInputs();
return result;
}
public object Invoke(object instance, object[] inputs, out object[] outputs)
{
object result=null;
///TODO: You will have more object in the input if you have more ///parameters in your method
string angle = inputs[1].ToString();
///TODO: create a unique key from the inputs
string key = angle;
string provider = System.Configuration.ConfigurationManager.AppSettings["CacheProviderName"];
///Important Provider will be DiskCache or MemoryCache for the moment
provider =”DiskCache”;
///TODO: call enterprise library cache manager, You can have your own
/// custom cache like Hashtable
ICacheManager manager = CacheFactory.GetCacheManager(provider);
if (manager.Contains(key))
{
result =(MyCompositeClass) manager[key];
}
else
{
result =(MyCompositeClass) _innerOperationInvoker.Invoke(instance, inputs, out outputs);
manager.Add(key, result);
}
return result;
}
public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
{
IAsyncResult result = _innerOperationInvoker.InvokeBegin(instance, inputs, callback, state);
return result;
}
public object InvokeEnd(object instance, out object[] outputs, IAsyncResult asyncResult)
{
object result = _innerOperationInvoker.InvokeEnd(instance, out outputs, asyncResult);
return result;
}
public bool IsSynchronous
{
get { return _innerOperationInvoker.IsSynchronous; }
}
}
Step 3
Finally add your attribute above your service call
[OperationContract]
[MyCacheRegister(typeof(RotateCacher)]
MyCompositeClass Rotate(int angle)
The configuration of enterprise library caching block is beyond the scope of this answer. You can use following link to learn it. The good thing about enterprise library is that you get ready made ways to extend your caching policy. It has built in ways for cache expiry and storage. You also can write your own cache expiration and storage policies. http://www.codeproject.com/KB/web-cache/CachingApplicationBlock.aspx
One final thing, for you to get your enterprise library caching working you need to add following configuration details. You also need to add relevant dlls to your project reference.
<configSections>
<section name="cachingConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Caching.Configuration.CacheManagerSettings, Microsoft.Practices.EnterpriseLibrary.Caching, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="true" />
</configSections>
<cachingConfiguration defaultCacheManager="Cache Manager">
<cacheManagers>
<add name="MemoryCache" type="Microsoft.Practices.EnterpriseLibrary.Caching.CacheManager, Microsoft.Practices.EnterpriseLibrary.Caching, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
expirationPollFrequencyInSeconds="60" maximumElementsInCacheBeforeScavenging="1000"
numberToRemoveWhenScavenging="10" backingStoreName="NullBackingStore" />
<add name="DiskCache" type="Microsoft.Practices.EnterpriseLibrary.Caching.CacheManager, Microsoft.Practices.EnterpriseLibrary.Caching, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
expirationPollFrequencyInSeconds="60" maximumElementsInCacheBeforeScavenging="1000"
numberToRemoveWhenScavenging="10" backingStoreName="IsolatedStorageCacheStore" />
</cacheManagers>
<backingStores>
<add type="Microsoft.Practices.EnterpriseLibrary.Caching.BackingStoreImplementations.NullBackingStore, Microsoft.Practices.EnterpriseLibrary.Caching, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
name="NullBackingStore" />
<add name="IsolatedStorageCacheStore" type="Microsoft.Practices.EnterpriseLibrary.Caching.BackingStoreImplementations.IsolatedStorageBackingStore, Microsoft.Practices.EnterpriseLibrary.Caching, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
encryptionProviderName="" partitionName="MyCachePartition" />
</backingStores>
</cachingConfiguration>
You could take a look at Velocity. This is Microsoft's distributed in-memory caching framework. But this may be a little bit too beta...