Unity Community Contributions and Interception

Now that Unity has been released into the wild, there has definitely been a bit of interest swirling around it.  One of my key wants for a good IoC container is basic interception capabilities.  My criteria for evaluating a container usually comes down to the following:
  • Configurability (XML, DSL, Code)
  • Dependency Resolution, usually opinionated
  • Lifetime Management (Per Thread, Pooled, Singleton, Transient, etc)
  • Extensibility for Interception
So, I realized that Unity was missing some of these things in the first go around.  Since Unity was built on top of ObjectBuilder2, a next generation version of the often maligned ObjectBuilder which also included some sample DI containers and a flexible interception model.  Today's post will cover that and a bit more.  I realize that I still am working on a post of AOP in the Enterprise and Spring.NET which is coming soon.

Where We Are


But, before we begin today, let's see what we've already covered in the past:
Now that we've look at the history of this series, let's go ahead and get started.

Unity Contributions

Since Unity was released last Friday, there has been a community effort much like the ASP.NET MVC Contributions, MVCContrib, called Unity Community Contributions.  This project is intended to fill in the gaps where the community feels that Unity can be extended, much like most other IoC containers.  As I stated above, one of those crucial things that I thought was missing was taking the ObjectBuilder2 Interception model and applying it towards Unity.  I wasn't interested as much in the Policy Injection Application Block, although nice, is a bit heavyweight for the basic operations I would like.  Instead, the OB2 would suffice for most things that I'd want with a container.  So, that led me to another topic...

ObjectBuilder2 Interception == Unity Interception?

Since I was particularly motivated to see the OB2 Interception model pushed into Unity, I decided to give it a go myself.  For the most part, I was successful in a way, but I wasn't using the right extensibility model.  From that point, I got in contact with Scott Densmore, a PM within the Patterns & Practices group and one of the authors of ObjectBuilder2 along with Brad Wilson.  From that point, he was able to take the original OB2 Interception model and fit it nicely into the Unity extensibility model.  The code is now available on the Unity Community Contributions and you can find those commits by Scott here.

So, let's take a look at what it entails.  As I tell most people, you learn the most about the software with good unit, I mean behavioral tests where I can really grok the intent of the code much better than most documentation.  I encourage most people to do the same.  I'd rather stay away from those projects that don't have tests to back up their work.

Like I said before, ObjectBuilder2 had several ways of intercepting calls.  There are several interception strategies worth mentioning:
  • Virtual Method interception
  • Interface Method interception
  • .NET Remoting interception (MarshalByRefObject)
From that, you have two ways of registering your interest for interception:
  • Intercept via code
  • Intercept via attributes
Now that we've laid down the basics, let's take a look at some of the unit tests.  You'll notice that xUnit.net was used for the unit tests.  Yes, I know, I've mentioned it a few times lately.  Anyhow, let's look first at virtual method interception.

Before we get started on that, in order to perform interception, we need to implement the IInterceptionHandler interface.  For the unit tests, this will be used to record the various actions that happened during the interception process.  But you can imagine it to do any number of things such as logging, transaction management, security and so on.  Any of those cross cutting concerns, really.

    public delegate IMethodReturn InovkeHandlerDelegate(IMethodInvocation call,
                                                        GetNextHandlerDelegate getNext);

    public delegate InovkeHandlerDelegate GetNextHandlerDelegate();

    public interface IInterceptionHandler
    {
        IMethodReturn Invoke(IMethodInvocation call,
                             GetNextHandlerDelegate getNext);
    }
}


   public class RecordingHandler : IInterceptionHandler
    {
        readonly string message;

        [InjectionConstructor]
        public RecordingHandler()
        {
            message = "";
        }

        public RecordingHandler(string message)
        {
            this.message = string.Format(" ({0})", message);
        }

        public IMethodReturn Invoke(IMethodInvocation call,
                                    GetNextHandlerDelegate getNext)
        {
            Recorder.Records.Add("Before Method" + message);
            IMethodReturn result = getNext().Invoke(call, getNext);
            Recorder.Records.Add("After Method" + message);
            return result;
        }
    }


We have the ability through the Invoke method to do things before the method as well as after.  Should we need to modify values before the method is called, we can go ahead and do so.  Now that we looked at that, let's get to some unit tests.  First off, let's look at interception through just code.  I like this approach as opposed to attributes due to not cluttering the domain model and making it more configurable at run time.

            [Fact]
            public void InterceptViaCode()
            {
                Recorder.Records.Clear();
                IUnityContainer container = new UnityContainer();
                container.AddNewExtension<InterceptionExtension>();
               container.Configure<IInterceptionConfiguration>().InterceptVirtual<SpyVirtual>

                   (typeof(SpyVirtual).GetMethod("InterceptedMethod"), new RecordingHandler());

                SpyVirtual obj = container.Resolve<SpyVirtual>();
                obj.InterceptedMethod();
                obj.NonInterceptedMethod();

                Assert.Equal(4, Recorder.Records.Count);
                Assert.Equal("Before Method", Recorder.Records[0]);
                Assert.Equal("In Method", Recorder.Records[1]);
                Assert.Equal("After Method", Recorder.Records[2]);
                Assert.Equal("In Non-Intercepted Method", Recorder.Records[3]);
            }


            public class SpyVirtual
            {
                public virtual void InterceptedMethod()
                {
                    Recorder.Records.Add("In Method");
                }

                public void NonInterceptedMethod()
                {
                    Recorder.Records.Add("In Non-Intercepted Method");
                }

                public virtual void ThrowsException()
                {
                    Recorder.Records.Add("In Method");
                    throw new Exception("This is my exception!");
                }
            }


As you see above, we can simply register our interest through the registration of the InterceptionExtension and then set up the configuration through the IInterceptionConfiguration.  From that point, we then say which kind of interception we want whether it be virtual, remoting or interfaces.  We specify the method we want to intercept by name.

You can also do the interception through the use of attributes.  From this point, we need to decorate our methods with the attribute and the particular interception handler we want to use.  We need to be aware of which interception strategy we're using as well when we decorate our classes.  You can intercept methods as well as property accessors by decorating the get or set.  Below is a simple example of a virtual interceptor registration.

             public class SpyVirtualAttributes
            {
                [VirtualIntercept(typeof(RecordingHandler))]
                public virtual void InterceptedMethod()
                {
                    Recorder.Records.Add("In Method");
                }
            }



I'm not going to post all of the tests, but I want to give you an idea of the power of this.  It's still early in the process and there is some cleanup still going on.  But, I encourage you to pick up the source, read it, grok it, give it a try and give feedback.

Conclusion

As you can see, the extensibility model of Unity works in that we can add interception to Unity.  So, we can start thinking about moving our cross cutting concerns to these layers should we so choose if Unity is your container of choice.  There are plenty of containers out there to choose from, so it's best to give them all a try and pick one based upon features, programming style, and heck, maybe even licensing.  I'll be showing some of this stuff off tomorrow at the CMAP Code Camp, so if you'll be at my 1PM session, be prepared.  Until next time...

kick it on DotNetKicks.com

No Comments