Client Channel Output Caching

Update 8/2/2010: Added the section describing how to customize the output caching module.

A new feature that arrived at WcfContrib v2.1 Feb10 Beta.
You can use the output caching to cache the operation output with the setting of your desire.
This is extremely useful and may save you quite some time from building client caching stores.

The built-in mechanism implemented here is In-Proc in-memory cache using the synchronized dictionary that comes with WcfContrib as well.
You can fully customize it and change the underlying provider or store. (E.g. EntLib/ASP.NET Caching/AppFabric caching/Database/File system and whatnot)

If the result is present in the cache and is still active according to its policy and interval, this result is returned without going through the WCF pipeline whatsoever which enables very good performance.

Usage Guideline
If you're using the output caching feature, you should dispose the controller instance at your process exit point:
using (OutputCacheController.Current) { }

Features

  • Output Cache on several elements - The behavior can be attached to a client channel for either a specific client channel instance or contract or specific operations.
  • Different cache policies - Absolute / Sliding / Indefinite.
  • Ignore Default Value - There may be services which return null when a certain entity isn't found for example, in this case you can use this mode to avoid caching it so next time it will still call the service to try and fetch the entity. (defaults to false)
  • Compare Enumerable Items - When set to true (the default), A custom equality comparer will be used with IEnumerable parameters since in this case you usually don't want to check equality by reference.
  • Equality Comparers - You can provide your own set of equality comparers to be used, this is index-based.
  • Provider Factory Model - The model is fully customizable

What is NOT supported

The behavior doesn't support operations with out/ref parameter definitions.
This is due to the fact that the 'IClientOperationInvokerBehavior' (the core implementation for this behavior) wasn't designed while taking this into account in the first place.
This will be addressed in a future release.

Usage Scenarios

Contract-level Definition
[ServiceContract]
[OutputCacheBehavior(OutputCachePolicy.Absolute, 3000)] //--> This will affect only 'Do' in this case since the other operations have the behavior set explicitly on them
interface IService
{
        [OperationContract]
        void Do(Foo foo);

        [OperationContract]
        [OutputCacheBehavior(OutputCachePolicy.Sliding, 2000)]
        Foo GetFoo1();

        [OperationContract(AsyncPattern = true)]
        [OutputCacheBehavior(OutputCachePolicy.Sliding, 2000)]                   //--> Both the synchronous and asynchronous need to be tagged with the behavior,
        IAsyncResult BeginGetFoo1(AsyncCallback callback, object asyncState);    //  It will use the same underlying source for both of them, no worries.
        Foo EndGetFoo1(IAsyncResult result);                                                     
}

Client-channel based definition:
ClientChannel<IService> myChannel = new ClientChannel<IService>(...);

//Add output cache to a specific operation
//You can add it to the entire description or contract behaviors which will affect all operations that were not set with the behavior explicitly on them
//You can also change the description of the global instance (ClientChannel<IService>.Instance)
myChannel.Description.Contract.Operations[0].Behaviors.Add(new OutputCacheBehaviorAttribute(...));

Derived ClientChannel definition:
You can attach the behaviors in the InitializeRuntime or through attributes on your own derived client channel.
For more information see ClientChannel documentation file on the Client Channel Extensions model.

Customizing the output cache provider

The output cache module uses the factory pattern to create the output cache provider that implements the required behavior.
You can attach your own custom factory through the configuration section - Read about it here.

I will demonstrate the parts that are included with WCF Contrib to show you how you can implement something similar and perhaps keep using some of it:

Output Cache Provider Factory
This is the factory class that is responsible for creating the actual output cache provider.
This is where you extensibility point kicks in, you can replace the built-in factory with your own.

The factory needs to have a public parameter-less constructor and implement 'IOutputCacheProviderFactory'
public interface IOutputCacheProviderFactory
{
    IOutputCacheProvider CreateProvider();
}

As you can see, this is a simple factory interface for providing the actual provider implementation.

Output Cache Provider
Output cache provider is where the magic really happens.
This is where you should implement your custom output caching solution if you like.
public interface IOutputCacheProvider
{
    void AddItem(OutputCacheItemKey key, OutputCacheInterval interval, object value);

    bool TryGetItem(OutputCacheItemKey key, out object value);
}
  • AddItem - this method is supposed to add a new item to the output cache solution.
  • TryGetItem - this method should look up the output cache solution for the item that was stored.
  • OutputCacheInterval - you're getting the desired interval as a parameter.
    • Consider setting the fields "LastVisitedAt" and "AddedAt" to the proper values at a given time so you can reference it later.
    • You can use "IsActive()" method if you like to check whether the interval is active for the current time according to its policy and time properties.
Note: If the provider implements IDisposable, it will be disposed once the OutputCacheController is disposed.

The built-in provider is called "IntervalBasedCacheProvider", it is important to know this since this too is written in a reusable way where you can plug-in your custom store.
The provider gets two parameters in its constructor:
  1. purgeTimerIntervalInMilliseconds - The interval in milliseconds for the timer that is in charge of iterating the items and purging those which are inactive.
  2. store - An implementation for IOutputCacheStore
    1. AddItem - The provider basically saves the key and interval in its local state, sets the time properties of the interval to the current time and adds the item to the store.
    2. TryGetItem - When an item is requested from the cache, the provider checks if there's an active key in its local state, if so, sets the last visited time to the current time and gets the item from the store.
    3. Removing Items - In the timer callback, it iterates the keys in its local state and removes the inactive ones from the store

Output Cache Store
The store is responsible for storing the items.
public interface IOutputCacheStore
{
    void SetItem(OutputCacheItemKey key, object value);

    void RemoveItem(OutputCacheItemKey key);

    bool TryGetItem(OutputCacheItemKey key, out object value);
}

The store that is provided with the built-in solution is using the SynchronizedDictionary that is a part of WCF Contrib as well.

Implementing your own custom output caching solution
You can go about it in two ways:
  1. Plug-in your own factory and provider and implement a different scenario - you can use this approach to write a provider that uses an interval-based caching solution such as HttpRuntime Caching, Enterprise Library, etc
  2. Plug-in your own factory and still use the built-in IntervalBasedCacheProvider only with your own custom store, such as a AppFabric caching, file-system, database, etc

Output Cache Item Key
It's important to recognize the description of the item keys if you like to implement you own solution since you will need a way a way to correlate between the item and key.
  • The key contains the operation action and parameters and custom equality comparers if any were provided.
  • It can be used as a dictionary key since it overrides Equals and GetHashCode by calculating it over the action and parameters using the appropriate comparer.
  • ToString() is also overriden to return the aggregation of the action and parameters.

Last edited Feb 8, 2010 at 7:12 PM by zuker, version 11

Comments

No comments yet.