Sunday, September 19, 2004

I've recently discovered Context in .Net while reading the Programming .Net Components book (Chapter 11). This is an amazing piece of technology that I definitely needed to try. A first try was to make a Caching context:

Caching context

Image that you have methods that perform long computation, such as querying a database etc..., and return a result. Usually, the result does not change much over time, so typically you would like to cache the results in order to improve the efficiency of the application. Now imagine that there exists a Caching context that would take care of caching method calls. For example, we would like to write something like this:

[Caching]
public class CachedClass : ContextBoundObject
{
    [Cached]
    public string ReturnBigObject()
    { 
        Console.WriteLine("miss");
        Thread.Sleep(1000);
        return DateTime.Now.ToString(); 
    }
}

In that sample, we would like the output of ReturnBigObject to be cached. For example, the sample method below shows the desired behavior. The first call to ReturnBigObject is a cache miss and then, the remaining calls are cached.

miss
c.ReturnBigObject(): 5/10/2004 19:31:31
c.ReturnBigObject(): 5/10/2004 19:31:31
c.ReturnBigObject(): 5/10/2004 19:31:31

Let's see the steps to take to create the caching context:

Building the context (1): CachingAttribute

The CachingAttribute has to inherit from ContextAttribute and override two methods. The main task of the attribute is to add a IContextProperty implementation to the context properties (GetPropertiesForNewContext method). 

[AttributeUsage(AttributeTargets.Class,AllowMultiple =false,Inherited =true)]
public class CachingAttribute : ContextAttribute
{
    public CachingAttribute():base("Chaching")
    {}
    public override void GetPropertiesForNewContext(IConstructionCallMessage ctorMsg)
    {
        IContextProperty property =
            new CachingContextProperty(ctorMsg.ActivationType);
        ctorMsg.ContextProperties.Add(property);
    }
    public override bool IsContextOK(Context ctx, IConstructionCallMessage ctorMsg)
    {
        return false;
    }
}

Building the context (1.1): CachedAttribute

The CachedAttribute is used to tag method to be cached. It contains several parameters to set up the HttpRuntime.Cache object.

Building the context (2): CachingContextProperty

The task of this class is to install a server IMessageSink in the message flow that will cache the message calls. Therefore, this class implements IContextProperty, IContributeServerContextSink.

public class CachingContextProperty : 
    IContextProperty, IContributeServerContextSink
{
    private Type activationType;
    ...
    public IMessageSink GetServerContextSink(IMessageSink nextSink)
    {
        CachingSink cachingSink = new CachingSink(nextSink, activationType);
        return cachingSink;
    }
}

Building the context (3): CachingSink

This is where the real work occurs. This class filters the call to the method (IMethodMessage) and looks in a Cache (HttpRuntime.Cache) if it is stored, if stored it returns the value, otherwize it calls the next sink and store the value in the Cache. (This part of the code is a little bit more technical because a part of the IMethodReturnMessage has to be "copied").

The heart of this class is as follows:

public IMethodReturnMessage SyncProcessMessage(IMethodMessage msg)
{
    // creating unique hash value out of method, instance and paramteres
    string hash = Hash(msg);
    // looking in the cache
    IMethodReturnMessage returnMessage = HttpRuntime.Cache[hash] as IMethodReturnMessage;
    if (returnMessage != null) // cache hit
        return returnMessage;
    
    returnMessage = this.Parent.NextSink.SyncProcessMessage(msg) 
        as IMethodReturnMessage;

    // caching returned information
    CachedMethodReturnMessage cachedMessage= new CachedMethodReturnMessage(returnMessage);

    // storing in cache
    HttpRuntime.Cache.Add(hash, cachedMessage,
        this.cachedAttribute.Dependencies,
        this.cachedAttribute.AbsoluteExpiration,
        this.cachedAttribute.SlidingExpiration,
        this.cachedAttribute.Priority,
        null);
    IMethodReturnMessage r = HttpRuntime.Cache[hash] as IMethodReturnMessage;
    return returnMessage;
}

Downloads

The full source is available on www.dotnetwiki.org

 

posted on Monday, September 20, 2004 6:50:00 AM UTC  #    Comments [12]
Monday, June 06, 2005 5:34:56 PM UTC
Paul's Imaginary Friend
Monday, June 06, 2005 5:34:56 PM UTC
This is fantastic. It makes it so easy to add caching to an existing class. Thank you highlighting this.
Andrew Coats
Monday, June 06, 2005 5:34:57 PM UTC
Great application. See also this article on 'Aspect Oriented Programming' in the March, 2002 issue of MSDN magazine:
<br>
<br><a target="_new" href="http://msdn.microsoft.com/msdnmag/issues/02/03/AOP/">http://msdn.microsoft.com/msdnmag/issues/02/03/AOP/</a>
<br>
<br>There are some performance issues: the context is set up on calls to every method in the class, so you would more likely use it on something like a specialized data access class. The overhead for business objects may be significant.
Jeffrey Sax
Monday, June 06, 2005 5:34:57 PM UTC
Although it's pretty simple to use ContextBoundObject. It also has some performance hit. Peli might get another new toy when he checks out <a target="_new" href="http://rail.dei.uc.pt/index.htm">http://rail.dei.uc.pt/index.htm</a>
Nat
Monday, June 06, 2005 5:34:57 PM UTC
Hi Jefrey,
<br>
<br>You are totally right. All this context stuff introduces a performance hit and anybody using them should be aware of that.
<br>
<br>Now, when you start caching a method, this is because it is potentially long and intensive. Therefore, the performance hit is worth the price. In the example, I added a 1 second thread sleep which clearly shows up the caching speed up. Thanks for pointing AOP.
<br>
<br>Nat,
<br>
<br>In fact, RAIL could be used to instrument the classes instead of contextbound object... btw,
<br><a target="_new" href="http://blog.dotnetwiki.org/archive/2004/05/22/255.aspx">http://blog.dotnetwiki.org/archive/2004/05/22/255.aspx</a>
<br>
<br>
Jonathan de Halleux
Monday, June 06, 2005 5:34:57 PM UTC
I didn't see this one before. Thanks... good stuff.
Nat
Monday, June 06, 2005 5:34:58 PM UTC
Jonathan, I think you're comparing apples with oranges here.
<br>
<br>This approach saves time for the developer: there is no need to explicitly write the caching code.
<br>
<br>However, this is of little concern to the end user, who only experiences the added overhead of the ContextBoundObject.
<br>
<br>Thanks for pointing out RAIL... I hadn't seen it. It looks like a useful tool. The idea of post-processing a compiled assembly had only occurred to me in the context of obfuscation.
Jeffrey Sax
Monday, June 06, 2005 5:34:58 PM UTC
Jeffrey, You are totally right about staying suspicious about such approach.
<br>
<br>There is a price/complexity price to pay by using ContextBoundObject but I think it makes totally sense for particular object such as Business Object.
<br>
<br>Image you have a BO that feeds a web sites or somethings and looks like this:
<br>
<br>public class SomeComplexDataDB
<br>{
<br> DataSet GetComplexQuery(...){}
<br>}
<br>
<br>The GetComplexQuery executes a complex query on the database. If the query is longer that the ContextObjectBound + caching overhead, you should get a speed up using caching. As always, benchmarks would be welcome on this.
<br>
<br>RAIL API is very good and their authors are quite responsive and enthousiast. Good tool to have handy if you want to instrumant your code.
Jonathan de Halleux
Monday, June 06, 2005 5:34:58 PM UTC
<br>Everyone using RAIL come along to their SourceForge site and sign up to the mailing list:
<br>
<br><a target="_new" href="http://sf.net/projects/rail">http://sf.net/projects/rail</a>
<br>
<br>I've been working on ironing out some bugs that have affected my use of RAIL to read in arbitrary assemblies, and I'm pretty close to getting it to read in large (System.Web.dll sized) assemblies and write them out verbatim.
<br>
<br>Currently I'm struggling with CustomAttributes :(
<br>
<br>Kirk
Kirk Jackson
Monday, June 06, 2005 5:34:58 PM UTC
Aaron Junod's .net thinking
Monday, June 06, 2005 5:34:59 PM UTC
Aaron Junod's .net thinking
Monday, June 06, 2005 5:34:59 PM UTC
Hi,
<br>
<br>The Wiki (www.dotnetwiki.org) is out of work.... is there somewhere else we can download the source from ?
Ohad Israeli
Comments are closed.