Source Code
This post is a repeat of the presentation I gave for the Pee Dee Area .NET User Group this past September.
Ever wanted to take some standard code and place it around a method, maybe a way to time the method, or use a database connection without creating one in each method. Yes, you can use a try..catch..finally block and make calls to some other methods, but how is that on managing the connection?
So, how do we pull this off in .NET? With the use of a few interfaces and classes from System.Runtime.Remoting and 4 custom classes we can accomplish our goal. We start with our base class which we will descend from ContextBoundObject and decorate with our InjectionAttribute (descendant of ContextAttribute). The ContextBoundObject will create a transparent-proxy which receives all the invocations of the target. It then serializes the call stack and passes it to a real-proxy (which it also created). This real-proxy calls an implementation of IMessageSink.
The following is a list and brief description of the class's responsibility:
InjectionAttribute: ContextAttribute
This attribute is used to decorate InjectionBase. This attribute allows all methods and field-setters to be injected. This attribute will be used to add an InjectionProperty to the context.
InjectionProperty: IContextProperty, IContributeServerContextSink
This class is used to insert a new message in front of the current message chain and check the context.
InjectionSink: IMessageSink
This class is used to process the method invocations of the sink chain. We limit our injection to only methods. When creating an instance of InjectionSink a reference to the next message sink is passed in so that all the sinks can be chained together.
[Injection]
InjectionBase: ContextBoundObject
The class where we bring everything back together. This class contains the 4 abstract methods we will use to inject our code. PressProcess, PostProcess, ProcessException, and ProcessSuccess. These methods will be called from the ProcessMethod which is responsible to invoking the method.
We know create base class implementations of InjectionBase. There are 2 included in the source, MyClass.cs that demonstrates the basic use and DBInjection.cs which implements a database connection.
The following is a create step-through of what occurs when creating and running a method.
InjectionBase ib = new InjectionBase()
Before the class constructor is called the framework instantiates InjectionAttribute and calls the GetPropertiesForNewContext passing a reference to IConstructionCallMessage. In this method an instance of InjectionProperty is created and added to the context. Now InjectionProperty.Freeze and InjectionProperty.IsNewContextOK are called. Finally, the contructor is called.
ib.DoSomeMethod(param)
Calls InjectionProperty.GetServerContextSink through the IContributeServerContextSink. This returns a newly created instance of InjectionSink which is used to call InjectionSink.SyncProcessMessage. InjectionSink.SyncProcessMessage fires the ProcessMessage on the InjectionBase which invokes our method and injection.