PIAB: Aborting out of the Handler Pipeline

Topics: Policy Injection Application Block
Mar 29, 2007 at 5:43 PM
I've been researching using the PIAB, and this question has come up. Do we have to throw and exception to abort the Pipeline? What happens if I would just avoid the call to getNext(), and just Return? Would there be any unresolved "stuff" hanging out there waiting to be executed, or is the pipeline built as the call to getNext() is made and not established before that?

Mar 30, 2007 at 2:15 PM
Great question for Tom, but here is my thinking to date.

You don't have to throw an exception to short circuit the pipeline. You can simply call the following in your handler:

return IMethodInvocation.CreateMethodReturn(...);

like:

public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
{
    if (IWantToShortCiruitThePipeline)
        return IMethodInvocation.CreateMethodReturn(...);
 
    // Don't Want To Short Circuit.
    // Be Good and Call Next Handler.
    return getNext()(input, getNext);
}


If done during the pre-method call sequence, I believe this will keep any new handlers and the target method from being called, but if there are any handlers that have already been called I would hope they should still get a follow-up call to cleanup.

The only CallHandlers done at this point ( Feb 2007 CTP ) are the ones that throw an exception. When Ent Lib 3.0 is released and I can get a peek at the CachingCallHandler, I bet it will just call

return IMethodInvocation.CreateMethodReturn(...);

if it finds data in the cache during the pre-method call. Doing so will gracefully keep from calling other handlers further down the pipe and the class method which would probably normally go and get the data from a datastore. I would think, however, that any handlers that have already run would get a follow-up call to finish their tasks.

The only gotcha about all this is that you have to be careful about the order in which handlers are lined up in the pipeline. Handlers may never get called depending on the handlers before them. However, that is what also makes this block really cool :)

Tom or someone else correct me if I am wrong.

Regards,

Dave

_______________________

David Hayden
Microsoft MVP C#
Mar 30, 2007 at 4:02 PM
Spot on Dave. Here's the Caching Call Handler's implementation of Invoke. Note that it only calls getNext if it doesn't find the value in the cache:

        public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
        {
            if (TargetMethodReturnsVoid(input))
            {
                return getNext()(input, getNext);
            }
 
            object[] inputs = new object[input.Inputs.Count];
            for(int i = 0; i < inputs.Length; ++i)
            {
                inputs[i] = input.Inputs[i];
            }
 
            string cacheKey = keyGenerator.CreateCacheKey(input.MethodBase, inputs);
 
            object[] cachedResult = (object[])HttpRuntime.Cache.Get(cacheKey);
 
            if(cachedResult == null )
            {
                IMethodReturn realReturn = getNext()(input, getNext);
                if (realReturn.Exception == null)
                {
                    AddToCache(cacheKey, realReturn.ReturnValue);
                }
                return realReturn;
            }
 
            IMethodReturn cachedReturn = input.CreateMethodReturn(cachedResult[0], input.Arguments);
            return cachedReturn;
        }

Tom
Mar 30, 2007 at 5:01 PM
Tom,

Here is how I tend to look at the process unless it is short circuited:

public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
{
    // Pre-Method Call
	
    return getNext()(input, getNext);
	
    // Post-Method Call
}

The code for the CachingCallHandler confuses me a bit, however, as you have two of the following commands:

return getNext()(input, getNext);

The second one makes sense as you want to cache the returning data. However, the first one looks wrong:

public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
{
     // Pre-Method Call
    if (TargetMethodReturnsVoid(input))
    {
        return getNext()(input, getNext);
		
        // Post-Method Call
    }
	
    // ...
}

If the method indeed returns void why would you ever want to run the code below the top getNext() call after the other handlers complete? I would think you may get an error or at least be spinning your wheels in code that doesn't need to be run. Am I missing something?

Unless I am mistaken, I also see you are using System.Web.Caching and not the Caching Application Block for caching:

object[] cachedResult = (object[])HttpRuntime.Cache.Get(cacheKey);

This actually makes me very happy :), but is there a reason for this decision and will the Caching Application Block be supported?

Regards,

Dave

_______________________

David Hayden
Microsoft MVP C#
Mar 30, 2007 at 5:15 PM
Hi Dave -

The logic behind this code is that void methods are intended to do something, not return something. While we could have simply not called the method if caching is turned on, we thought this was unlikely to be the desired behavior most of the time.

You are correct that we are using the System.Web.Caching cache, and our implementation does not support the Caching Application Block at all (although it wouldn't be that hard to change it yourself). We made this decision as we felt that the ASP.NET cache was a better fit for this usage scenario. The biggest reason to use the Caching Application Block over the ASP.NET cache is the support for persistent backing stores, and we did not hear that this was an important scenario for method-level caching. Do you think this is reasonable? If not, I'm sure a Caching Appliation Block-based implementation will show up before long...

Tom
Mar 30, 2007 at 5:40 PM
Tom,

I may be wrong, but doesn't this code get called after the top return getNext()(input, getNext); statement executes for void methods:

            object[] inputs = new object[input.Inputs.Count];
            for(int i = 0; i < inputs.Length; ++i)
            {
                inputs[i] = input.Inputs[i];
            }
 
            string cacheKey = keyGenerator.CreateCacheKey(input.MethodBase, inputs);
 
            object[] cachedResult = (object[])HttpRuntime.Cache.Get(cacheKey);
 
            if(cachedResult == null )
            {
                IMethodReturn realReturn = getNext()(input, getNext);
                if (realReturn.Exception == null)
                {
                    AddToCache(cacheKey, realReturn.ReturnValue);
                }
                return realReturn;
            }
 
            IMethodReturn cachedReturn = input.CreateMethodReturn(cachedResult[0], input.Arguments);
            return cachedReturn;
 

You don't really want that code to run for void methods do you?

As for supporting the Caching Application Block, I don't personally need it, but someone who does may be interested in the fact that you guys chose not to support it in the PIAB.

Regards,

Dave

________________________

David Hayden
Microsoft MVP C#
Mar 30, 2007 at 6:01 PM
There's a return statement in the top if code block, so the remainder of the code will not be executed. Personally I would have put the rest of the code in an else block to clarify this, but it should work fine (and Chris is a better coder than me so I'll give him the benefit of the doubt :)

Tom
Apr 1, 2007 at 4:45 AM
Thanks for the vote of confidence, Tom. :-)

David, your concept of the process above (which I'll repost here):
public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
{
    // Pre-Method Call
	
    return getNext()(input, getNext);
	
    // Post-Method Call
}

is incorrect. the return getNext()(input, GetNext); call exits the Invoke method, just like any return statement will. As a result, the // Post-Method Call will not execute at all.

What it really looks like is this:

public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
{
    // Pre-Method Call
	
    IMethodReturn result = getNext()(input, getNext);
	
    // Post-Method Call
 
    // And return to the previous handler
    return result;
}

You store the result from the rest of the pipeline (which may or may not come from the target, since a later handler could short-circuit), possibly change or replace the result, and then return it back up the chain.

I hope this helps; if not, please let me know. If you're confused a lot of other people probably are too.

-Chris
Apr 1, 2007 at 2:11 PM
Chris,

Excellent!

You must have been reading my mind because I was still a bit confused, and now the code in the CachingCallHandler makes sense. I should have known better in thinking that the return was part of the separation between pre- and post-method calls.

Thanks for the clarification.

Best Regards,

Dave

______________________

David Hayden
Microsoft MVP C#
Apr 2, 2007 at 6:59 PM
Thanks for all the replies. This answered my question and then some. Can't wait for the final release.