Interceptors in Unity

Dino Esposito

image: Dino EspositoIn last month’s column, I briefly introduced the interception mechanism used in the Unity 2.0 dependency injection container. After illustrating the core principles of aspect-oriented programming (AOP), I presented a concrete example of interception that’s probably very close to the needs of many developers today.

Have you ever wanted to extend the behavior of an existing piece of code without having to touch the source code in any way? Have you just wanted to be able to run some additional code around the existing code?

AOP was created to provide an approach that isolates core code from other concerns that crosscut the core business logic. Unity 2.0 offers a Microsoft .NET Framework 4-based framework for achieving this in a surprisingly quick and easy way.

To fully comprehend the purpose of this follow-up article, I’ll begin by summing up what I discussed last month. As you’ll find out, in last month’s code I made some assumptions and used some default components. This month I’ll step back and discuss in more detail the choices and options that you typically encounter along the way.

AOP in Unity

Imagine you’ve a deployed application where, at some point, you perform some business-specific action. One day, your customer asks to extend that behavior to perform some more work. You grab the source code, modify it, and bill a few hours of consulting time for coding up and testing the new features. But wouldn’t it be better if you could add new behaviors seamlessly and without touching the existing source code?

Imagine a slightly different scenario. First, what if you’re not an independent consultant, but work full time for the company? The more requests for change you get, the more hours you spend outside your current project and, worse yet, you risk creating new (and not necessarily required) branches of your codebase. So, you’d heartily welcome any solutions that would let you add new behaviors seamlessly and with no exposure to the source code.

Finally, imagine that someone reports a bug or a serious performance hit. You need to investigate and fix the problem and you want it to be absolutely unobtrusive. In this case, too, it would be better to add new behaviors seamlessly and with no exposure to the source code.

AOP helps you in all of these scenarios.

Last month I demonstrated how to add pre- and post-processing code around an existing method without touching it by leveraging the interception API in Unity 2.0. That quick demo made a few assumptions, however.

First, it worked on a type registered with the Unity Inversion of Control (IoC) infrastructure and instantiated via the Unity factory layer.

Second, the pointcut was only defined through an interface. In AOP jargon, a pointcut represents a collection of places in the body of the target class where the framework will inject extra behaviors on demand. An interface-based pointcut means that only the members of the interface will be extended at runtime via code injection.

Third, I mostly focused on the configuration settings to enable interception and disregarded the fluent API that allows you to configure Unity in code.

In the rest of this article, I’ll explore the fluent API and alternative ways of defining Unity interceptors.

Interceptable Instances

To add a new behavior to an existing or freshly created instance of a class, you must take some control over the factory. In other words, AOP is not pure magic and you’ll never be able to hook up a plain CLR class instantiated via the standard new operator:

  1. var calculator = new Calculator();

The way in which an AOP framework gains control over the instance may differ quite a bit. In Unity, you can resort to some explicit calls that return a proxy for the original object or keep the whole thing running behind an IoC framework. For this reason, most IoC frameworks offer AOP capabilities. Spring.NET and Unity are two examples. When AOP meets IoC, the result is a seamless, easy and effective coding experience.

Let’s start with an example where no IoC capabilities are used. Here’s some basic code that makes an existing instance of the Calculator class interceptable:

  1. var calculator = new Calculator();
  2. var calculatorProxy = Intercept.ThroughProxy<ICalculator>(calculator,
  3.   new InterfaceInterceptor(), new[] { new LogBehavior() });
  4. Console.WriteLine(calculatorProxy.Sum(2, 2));

You end up working with an interceptable proxy that wraps your original object. In this case, I’m assuming that the Calculator class implements the ICalculator interface. To be interceptable, a class must either implement an interface or inherit from MarshalByRefObject. If the class derives from MarshalByRefObject, then the interceptor must be of type TransparentProxyInterceptor:

  1. var calculator = new Calculator();
  2. var calculatorProxy = Intercept.ThroughProxy(calculator,
  3.   new TransparentProxyInterceptor(), new[] { new LogBehavior() });
  4. Console.WriteLine(calculatorProxy.Sum(2, 2));

The Intercept class also offers a NewInstance method you can call to create an interceptable object in a more direct way. Here’s how to use it:

  1. var calculatorProxy = Intercept.NewInstance<Calculator>(
  2.   new VirtualMethodInterceptor(), new[] { new LogBehavior() });

Note that when you use NewInstance, the interceptor component has to be slightly different—neither InterfaceInterceptor nor TransparentProxyInterceptor, but rather a VirtualMethodInterceptor object. So how many types of interceptors exist in Unity?

Instance and Type Interceptors

An interceptor is the Unity component responsible for capturing the original call to the target object and routing it through a pipeline of behaviors so that each behavior has its chance to run before or after the regular method call. Interception can be of two types: instance interception and type interception.

Instance interceptors create a proxy to filter incoming calls directed at the intercepted instance. Type interceptors generate a new class derived from the type being intercepted, and work on an instance of that derived type. Needless to say, the delta between the original and derived type is all in the logic required to filter incoming calls.

In case of instance interception, the application code first creates the target object using a classic factory (or the new operator), and then is forced to interact with it through the proxy provided by Unity.

In the case of type interception, the application creates the target object through the API or Unity, then works with that instance. (You can’t create the object directly with the new operator and get type interception.) The target object, however, is not of the original type. The actual type is derived by Unity on the fly and incorporates interception logic (see Figure 1).

image: Instance Interceptor and Type Interceptor

Figure 1 Instance Interceptor and Type Interceptor

InterfaceInterceptor and TransparentProxyInterceptor are two Unity interceptors that belong to the instance interceptor category. VirtualMethodInterceptor belongs to the type interceptor category.

InterfaceInterceptor can intercept public instance methods on just one interface on the target object. The interceptor can be applied to new and existing instances.

TransparentProxyInterceptor can intercept public instance methods on more than one interface, and marshal-by-reference objects. This is the slowest approach for interception, but it can intercept the widest set of methods. The interceptor can be applied to new and existing instances.

VirtualMethodInterceptor can intercept virtual methods both public and protected. The interceptor can only be applied to new instances.

It should be noted that instance interception can be applied to any public instance methods, but not to constructors. This is fairly obvious for the scenario in which interception applies to an existing instance. It’s a bit less obvious when interception is applied to a newly created instance. The implementation of instance interception is such that the constructor has already executed by the time the application code gets back an object to work with. As a result, any interceptable action necessarily follows the creation of the instance.

Type interception uses dynamic code generation to return an object that inherits from the original type. In doing so, any public and protected virtual methods are overridden to support interception. Consider the following code:

  1. var calculatorProxy = Intercept.NewInstance<Calculator>(
  2.   new VirtualMethodInterceptor(), new[] { new LogBehavior() });

The Calculator class looks like this:

public class Calculator { public virtual Int32 Sum(Int32 x, Int32 y) { return x + y; } }

Figure 2 shows the actual name of the type that results from a dynamic inspection of the calculatorProxy variable.

image: Actual Type After Type Interception

Figure 2 Actual Type After Type Interception

It’s also worth noting that there are some other significant differences between instance and type interception—for example, intercepting calls by the object on itself. If a method is calling another method on the same object when using type interception, then that self-call can be intercepted because the interception logic is in the same object. However, with instance interception, the interception only happens if the call goes through the proxy. Self-calls, of course, don’t go through the proxy and therefore no interception happens.

Using the IoC Container

In last month’s example, I used the IoC container of the Unity library to take care of object creation. An IoC container is an extra layer around object creation that adds flexibility to the application. This is even truer if you consider IoC frameworks with additional AOP capabilities. Furthermore—and as I see things—the level of code flexibility grows beyond imagination if you also combine IoC containers with offline configuration. However, let’s start with an example that uses the Unity’s container with code-based fluent configuration:

  1. // Configure the IoC container
  2. var container = UnityStarter.Initialize();
  3. // Start the application
  4. var calculator = container.Resolve<ICalculator>();
  5. var result = calculator.Sum(2, 2);

Any code needed to bootstrap the container can be isolated in a distinct class and invoked once at application startup. The bootstrap code will instruct the container how to resolve types around the application and how to deal with interception. A call to the Resolve method shields you from all the details of interception. Figure 3 shows a possible implementation of the bootstrap code.

Figure 3 Bootstrapping Unity

  1. public class UnityStarter {
  2.   public static UnityContainer Initialize() {
  3.     var container = new UnityContainer();
  4.     // Enable interception in the current container
  5.     container.AddNewExtension<Interception>();
  6.     // Register ICalculator with the container and map it to
  7.     // an actual type. In addition, specify interception details.
  8.     container.RegisterType<ICalculator, Calculator>(
  9.       new Interceptor<VirtualMethodInterceptor>(),
  10.       new InterceptionBehavior<LogBehavior>());
  11.     return container;
  12.   }
  13. }

The nice thing is that this code can be moved to a separate assembly and loaded or changed dynamically. More importantly, you have a single place to configure Unity. This won’t happen as long as you stick to the Intercept class that behaves like a smart factory and needs to be prepared every time you use it. So if you need AOP in your applications, by all means get it via an IoC container. The same solution can be implemented in an even more flexible way by moving the configuration details off to the app.config file (or web.config if it’s a Web application). In this case, the bootstrap code consists of the following two lines:

  1. var container = new UnityContainer();
  2. container.LoadConfiguration();

Figure 4 shows the script you need to have in the configuration file. Here I registered two behaviors for the ICalculator type. This means that any calls to public members of the interface will be pre- and post-processed by LogBehavior and BinaryBehavior.

Figure 4 Adding Interception Details Via Configuration

  1. <unity xmlns=”http://schemas.microsoft.com/practices/2010/unity”&gt;
  2.   <assembly name=”SimplestWithConfigIoC”/>
  3.   <namespace name=”SimplestWithConfigIoC.Calc”/>
  4.   <namespace name=”SimplestWithConfigIoC.Behaviors”/>
  5.   <sectionExtension
  6.     type=”Microsoft.Practices.Unity.
  7.       InterceptionExtension.Configuration.
  8.       InterceptionConfigurationExtension,
  9.       Microsoft.Practices.Unity.Interception.Configuration” />
  10.   <container>
  11.     <extension type=”Interception” />
  12.     <register type=”ICalculator” mapTo=”Calculator”>
  13.       <interceptor type=”InterfaceInterceptor”/>
  14.       <interceptionBehavior type=”LogBehavior”/>
  15.       <interceptionBehavior type=”BinaryBehavior”/>
  16.     </register>
  17.     <register type=”LogBehavior”>
  18.     </register>
  19.     <register type=”BinaryBehavior”>
  20.     </register>
  21.   </container>
  22. </unity>

Note that, because LogBehavior and BinaryBehavior are concrete types, you actually don’t need to register them at all. Unity’s defaults will automatically work for them.

Behaviors

In Unity, behaviors are objects that actually implement the crosscutting concerns. A class that implements the IInterceptionBehavior interface, a behavior rewrites the execution cycle of the intercepted method and can modify method parameters or return values. Behaviors can even stop the method from being called at all or call it multiple times.

A behavior is made of three methods. Figure 5 shows a sample behavior that intercepts the method Sum and rewrites its return value as a binary string. The method WillExecute is simply a way to optimize the proxy. If it returns false, the behavior won’t execute.

Figure 5 A Sample Behavior

  1. public class BinaryBehavior : IInterceptionBehavior {
  2.   public IEnumerable<Type> GetRequiredInterfaces() {
  3.     return Type.EmptyTypes;
  4.   }
  5.   public bool WillExecute {
  6.     get { return true; }
  7.   }
  8.   public IMethodReturn Invoke(
  9.     IMethodInvocation input,
  10.     GetNextInterceptionBehaviorDelegate getNext) {
  11.     // Perform the operation
  12.     var methodReturn = getNext().Invoke(input, getNext);
  13.     // Grab the output
  14.     var result = methodReturn.ReturnValue;
  15.     // Transform
  16.     var binaryString = ((Int32)result).ToBinaryString();
  17.     // For example, write it out
  18.     Console.WriteLine(“Rendering {0} as binary = {1}”,
  19.       result, binaryString);
  20.     return methodReturn;
  21.   }
  22. }

This is actually a bit subtler. Invoke will always be called, so your behavior will in fact execute even if you returned false. However, when the proxy or derived type is being created, if all the behaviors registered for the type have WillExecute set to false, then the proxy itself won’t be created and you’ll be working with the raw object again. It’s really about optimizing proxy creation.

The GetRequiredInterfaces method allows the behavior to add new interfaces to the target object; interfaces returned from this method will be added to the proxy. Hence, the core of a behavior is the Invoke method. The parameter input gains you access to the method being called on the target object. The parameter getNext is the delegate for you to move to the next behavior in the pipeline and eventually execute the method on the target.

The Invoke method determines the actual logic used to execute a call to a public method on the target object. Note that all intercepted methods on the target object will execute according to the logic expressed in Invoke.

What if you want to use more specific matching rules? With plain interception as I described in this article, all you can do is run through a bunch of IF statements to figure out which method is actually being invoked, like so:

  1. if(input.MethodBase.Name == “Sum”) {
  2.   …
  3. }

Next month I’ll resume from here to discuss more effective ways to apply interception and define matching rules for intercepted methods.


Dino Esposito is the author of “Programming Microsoft ASP.NET MVC” (Microsoft Press, 2010) and coauthored “Microsoft .NET: Architecting Applications for the Enterprise” (Microsoft Press, 2008). Based in Italy, Esposito is a frequent speaker at industry events worldwide. You can join his blog at weblogs.asp.net/despos.

Thanks to the following technical expert for reviewing this article: Chris Tavares

This entry was posted in 未分类. Bookmark the permalink.

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s