Essential Interception using Contexts in .NET

This chapter is a result of my tinkering with the Advanced methods chapter of Essential .NET (Don Box and Chris Sells). This chapter is one of my favourites in the book.
Task : Boost thread priority temporarily to a level (specified by the component type). Keep in mind that we want to provide this as an Aspect of the class. The class authors should not have to write any special code or use convoluted/arcane ways to instantiate objects.
This is the example taken in the book. We can intercept calls and provide any pre/post processing as needed. Some of it is not well-documented in .NET. But I loved it nonetheless. This post is to make sure I don't forget the entire load and an attempt to unbox Don Box ( had to let that out of my system).

Keep in mind that any method call can be represented as a Message. System.Runtime.Remoting.Messaging contains IMessage, IMethodMessage, IMethodCallMessage, IMethodReturnMessage, IConstructionCallMessage and IConstructionReturnMessage. (Look up MSDN for more on these classes :)

Option A: The simple but slightly tedious way is to define a proxy class and a Factory method in the required class. This can be seen in the source file Interception1.cs. The essence here is the System.Runtime.Remoting.Proxies.RealProxy class.

  1. So we derive from this class e.g. PriorityProxy to wrap our target class. The target class defines a factory (public static) method like CreateInstance() to have a chance to intercept creation. And obviously, hide the ctor. Now in this method, we create our ProxyObject with a new instance of our target class. We now return a transparent proxy ( a class that mocks everything in our target class ) by returning obProxyObj.GetTransparentProxy(). This is what the calling code gets back.. a devious imposter !
  2. Next: The prime method RealProxy.Invoke() takes and returns an IMessage implementation. Any method call to our transparent proxy is delegated to RealProxy.Invoke(). We can do nothing and call the method via RemotingServices.ExecuteMessage(target, IMethodCallMessage). But we did not jump thru all these hoops to do nothing. Before and After this call lie endless possibilities. E.g. Here we can bump the Thread Priority as Pre-Op and restore it after as Post-Op code.
    Note to call RemotingServices.ExecuteMessage, our target class must derive from MarshalByRef object. This looks restrictive but I do not know of another way to "Call a method" on a target class

So there we have it. But we don't wanna write factory methods (and raise suspicion) so there has to be a more transparent method. And it turns out there is, we can use a special attribute (System.Runtime.Remoting.Proxies.ProxyAttribute) to do this. See Attribute.cs
Derive an Attribute class from ProxyAttribute e.g. PriorityProxyAttribute.

  1. The magic here is that if a class derives from System.ContextBoundObject and it is decorated with a ProxyAttribute, then as Don Box says, the CLR does a dance for you to intercept creation. On a "new", The CLR will first create the ProxyAttribute and call ProxyAttribute.CreateInstance(Type). The default implementation creates the type for you. You cannot call new here as it will trigger the same thing all over again. So here we can create our PriorityProxy here and return the transparent proxy. This eliminates the need for a factory method. The client just does a simple new to instantiate!

So is that all ? Not at all. We're just getting warmed up. For more control (read control-freaky control), we have Contexts and sinks (not the kitchen variety but message sinks). Contexts are logical divisions within an Appdomain analogous to the relationship between AppDomains and Processes. Deriving from ContextBoundObject means that the object is attached to a specific Context i.e. not "Context-Agile". So let's dive into this. For Source, see Contexts.cs

  1. First we decorate our target class with an Attribute that implements IContextAttribute. System.Runtime.Remoting.Contexts.IContextAttribute defines two methods


    void GetPropertiesForNewContext(IConstructionCallMessage msg);
    bool IsContextOK(Context ctx, IConstructionCallMessage msg);


    On a new on our ContextBound type, The CLR will find all context attributes for this class and begin Pass 1. Here it will inquire IsContextOK for each attribute i.e. is the current context fine by you, O mighty context attribute? The context attribute can perform a check and say Nay by returning false. In my example, I call GetProperty on the context to check if the context has a property "ThreadPriority" AND it matches the requested priority. If any one fails, I return false.
    This lets the CLR know that a new context needs to be created. It does so and then begins Pass 2. This time it will call upon GetPropertiesForNewContext(). Each attribute is given a chance to add "context properties" to the new context. This is it's reason for existence. A context property implements System.Runtime.Remoting.Contexts.IContextProperty


    void Freeze(Context newContext);
    bool IsNewContextOK(Context newCtx);
    string Name { get; }


    The third member defines the name of the context property (e.g. "ThreadPriority"). The second member is there to handle conflicts. A type may have multiple context attributes, each adding properties that may have conflicting requirements. So a property can return false to cancel instantiation and throw an exception. Our amiable property return true.. it can get along with any one.
  2. Ok but where is the interception ? For that we now bring in the heavy artillery, Message Sinks. A context property has the ability to add sinks in the message chain. There are four types of sinks
    1. Server Context Sink
    2. Object Sink
    3. ClientContext Sink
    4. Envoy Sink
    All other types other than the Object Sink will be covered in a later post (all my constraints permitting). To add an Object Sink, the property needs to implement System.Runtime.Remoting.Contexts.IContributeObjectSink. This interface has a single method, which returns the MessageSink to be added. (We add our PrioritySink in our property PriorityContextProp). So there we have it, our control point.
    Our PrioritySink is a Message sink, why ? because it derives from IMessageSink.


    IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink);
    IMessage SyncProcessMessage(IMessage msg);
    IMessageSink NextSink { get; }


    Our focus here would be on SyncProcessMessage. On a (synchronous) method call, this method would be called on each sink (a chain may be present). Our implementation of this method would boost the thread priority before calling SyncProcessMessage on the next sink in the chain. Then it restores the old priority.

To use this aspect, we can define any class


[PriorityContext(ThreadPriority.AboveNormal)]
public class Amadeus : ContextBoundObject
{
public void ComposeAGermanOpera()
{
Logger.LogMessage(" Amadeus.ComposeAGermanOpera() ");
Console.WriteLine("Mozart goes to work!!!");
}
}


Object usage

Amadeus Mozart = new Amadeus();
Logger.LogMessage("Commission Mozart.");
Mozart.ComposeAGermanOpera();
Mozart.ComposeAGermanOpera();
Logger.LogMessage("Mozart - Out.");


Here is the output due to some logging code (mainly for inspection purposes)

Thread-2 Context-0 Priority-Normal : Commission Mozart.
Thread-2 Context-1 Priority-Normal : IContributeObjectSink.GetObjectSink()
PrioritySink.SyncProcessMessage (ObjectSink impl) Obe-wan boost my force!
Thread-2 Context-1 Priority-AboveNormal : Amadeus.ComposeAGermanOpera()
Mozart goes to work !!!
PrioritySink.SyncProcessMessage (ObjectSink impl) Obe-wan boost my force!
Thread-2 Context-1 Priority-AboveNormal : Amadeus.ComposeAGermanOpera()
Mozart goes to work !!!
Thread-2 Context-0 Priority-Normal : Mozart - Out.

Fantastic – excuse me while I pat myself on the back again ! From the object usage, do you suspect anything going on behind the scenes ?

Amadeus Mozart = new Amadeus();
Mozart.ComposeAGermanOpera();


Without the logging, everything would be like Magic. All method calls to Amadeus instances are on Nitro!! To quote The Ghost who walks ‘Everything’s simple when you know how’. There you have it. No more interfaces I promise..

References:
Essential .NET – Don Box and Chris Sells.
Msdn.microsoft.com/msdnmag/issues/03/03/contextsinnet/default.aspx – Juval Lowy

For the source file I am trying out a free hosting site - Hope this works! file name: Interception.zip file size: 12439 bytes File number: 00080238 file link: http://www.sendmefile.com/00080238
Everything written here is based on my understanding, which is nothing to write home about. Use your better judgment... FINE PRINT



3 comments:

  1. Great stuff!
    Can you please make the zip file available again? The link you gave is not working anymore...

    ReplyDelete
  2. Nice info,
    can u please provide a working example (code) of it.
    Thanks.

    ReplyDelete
  3. I'm really sorry but the site on which the files were hosted seems to have been out of commission. It's been 5+years since I wrote this.. so chances of finding the backup are slim. So the fastest way would be to grab a copy of the Essential .Net book and jump to the chapter... Meanwhile I find something I'll update the post. But no promises :)

    ReplyDelete