Fork me on GitHub

Interception and Decorators Edit on GitHub


All of the samples from this topic are part of the user acceptance tests in the main codebase. There is also another example of using an interception policy with open generic types at the bottom of Generic Types

Improving the interception facilities and the means of applying decorators during object construction was one of the primary goals of the big 3.0 release and is significantly different than the older 2.5/2.6 model.

Interception in StructureMap really comes in two basic flavors:

  1. Activates - Do some action on or with an object just created or resolved by StructureMap
  2. Decorates - Wrap (or optionally replace) an object just created with either a dynamic proxy or some sort of decorator

Interceptors can be configured explicitly on a single Instance registration, on all registrations to a PluginFamily, or conventionally to any concrete type implementing an interface, inheriting from a certain base class, or by some sort of user-supplied criteria.

Any type of Instance can be intercepted, meaning that even object literal values supplied to StructureMap at registration can be intercepted when they are resolved as dependencies or through service location calls.

See the Glossary for a refresher on terms like Instance and PluginFamily.

Also see Working with the IContext at Build Time for more information about using the Container state within interception.

Activation Interceptors

For right now, all activation interceptors are either using or subclassing the ActivatorInterceptor<T> class.

This class has two constructor functions that interest us:

To create an activator interceptor that acts on an object that can be cast to type T:


public ActivatorInterceptor(Expression<Action<T>> action, string description = null)

To create an activator interceptor that acts on an object that can be cast to type T and also uses the IContext service supplied by StructureMap itself.


public ActivatorInterceptor(Expression<Action<IContext, T>> action, string description = null)
{
    _action = action;
    _description = description;
}

In both cases, the description is optional and is only used for diagnostic purposes in the build plan visualization. If omitted, StructureMap tries to do a ToString() on the Expression for the description and that frequently suffices to understand what's going on in the build plan.

Please note that the Lambda supplied to ActivatorInterceptor<T> must be a .Net Expression so cannot be a multi-line Lambda. You can get around this limitation for more complex activation needs by simply making a wrapping method and using that to express the activation.

Decorators

To demonstrate a decorator in action, say that we have an interface called IWidget, and when we build any instance of IWidget we want those objects decorated by another type of IWidget that I clumsily named WidgetHolder in the acceptance tests:


public class WidgetHolder : IWidget
{
    private readonly IWidget _inner;

    public WidgetHolder(IWidget inner)
    {
        _inner = inner;
    }

    public IWidget Inner
    {
        get { return _inner; }
    }
}

Now, to see the decorator mechanism in action:


[Fact]
public void decorator_example()
{
    var container = new Container(_ =>
    {
        // This usage adds the WidgetHolder as a decorator
        // on all IWidget registrations and makes AWidget
        // the default
        _.For<IWidget>().DecorateAllWith<WidgetHolder>();
        _.For<IWidget>().Use<AWidget>();
    });

    container.GetInstance<IWidget>()
        .ShouldBeOfType<WidgetHolder>()
        .Inner.ShouldBeOfType<AWidget>();
}

In effect, doing a decorator this way has the same effect (and build plan) as:


var container = new Container(_ =>
{
    _.For<IWidget>().Use<WidgetHolder>()
        .Ctor<IWidget>().Is<AWidget>();
});

Custom Decorator Interceptors

The simplest usage is to just declare a type that will be the decorating type like we did above, but if you need some other mechanism for decorators like runtime AOP interception or you want to build the decorating object yourself, StructureMap provides the FuncInterceptor<T> type where T is the type you want to decorate.

These objects can be created in two ways, by a user-supplied Expression<Func<T, T>> and optional description:


public FuncInterceptor(Expression<Func<T, T>> expression, string description = null)

and by a user-supplied Expression<Func<IContext, T, T>> and optional description.


public FuncInterceptor(Expression<Func<IContext, T, T>> expression, string description = null)

In both cases, the description field is only used for diagnostic purposes.

Interception Policies

The Registry DSL includes shorthand methods for the most common ways of configuring decorators and activators by an individual Instance or by matching on implementing types. For more customized interception policies that don't fit these mechanisms, StructureMap allows you to directly define an interception policy with a class implementing this interface below:


public interface IInterceptorPolicy : IDescribed
{
    IEnumerable<IInterceptor> DetermineInterceptors(Type pluginType, Instance instance);
}

For a simple example, let's say that we want to decorate any IWidget object with the WidgetHolder class from earlier. We could build a small custom interceptor policy like this one:


public class CustomInterception : IInterceptorPolicy
{
    public string Description
    {
        get { return "good interception policy"; }
    }

    public IEnumerable<IInterceptor> DetermineInterceptors(Type pluginType, Instance instance)
    {
        if (pluginType == typeof(IWidget))
        {
            // DecoratorInterceptor is the simple case of wrapping one type with another
            // concrete type that takes the first as a dependency
            yield return new DecoratorInterceptor(typeof(IWidget), typeof(WidgetHolder));
        }
    }
}

To use this custom interception policy, use the Policies.Interceptor() methods like this example:


[Fact]
public void use_a_custom_interception_policy()
{
    var container = new Container(x =>
    {
        x.Policies.Interceptors(new CustomInterception());

        x.For<IWidget>().Use<AWidget>();
    });

    container.GetInstance<IWidget>()
        .ShouldBeOfType<WidgetHolder>()
        .Inner.ShouldBeOfType<AWidget>();
}

As a helper for creating your own interception policies, you can also use the InterceptorPolicy<T> base class to conventionally apply some sort of IInterceptor to any number of Instance's:


public InterceptorPolicy(IInterceptor interceptor, Func<Instance, bool> filter = null)

Here's an example of InterceptorPolicy<T> in usage from the acceptance tests:


[Fact]
public void apply_policy_selectively_with_a_func()
{
    var activator1 = new ActivatorInterceptor<ITarget>(x => x.Activate());
    var policy = new InterceptorPolicy<ITarget>(activator1, i => i.Name.StartsWith("A"));

    var container = new Container(x =>
    {
        x.Policies.Interceptors(policy);
        x.For<ITarget>().AddInstances(targets =>
        {
            targets.Type<ATarget>().Named("A");
            targets.Type<ATarget>().Named("A1");
            targets.Type<ATarget>().Named("A2");
            targets.Type<ATarget>().Named("B");
            targets.Type<ATarget>().Named("C");
            targets.Type<ATarget>().Named("D");
        });
    });

    container.GetInstance<ITarget>("A").ShouldBeOfType<ATarget>().WasActivated.ShouldBeTrue();
    container.GetInstance<ITarget>("A1").ShouldBeOfType<ATarget>().WasActivated.ShouldBeTrue();
    container.GetInstance<ITarget>("A2").ShouldBeOfType<ATarget>().WasActivated.ShouldBeTrue();
    container.GetInstance<ITarget>("B").ShouldBeOfType<ATarget>().WasActivated.ShouldBeFalse();
    container.GetInstance<ITarget>("C").ShouldBeOfType<ATarget>().WasActivated.ShouldBeFalse();
    container.GetInstance<ITarget>("D").ShouldBeOfType<ATarget>().WasActivated.ShouldBeFalse();
}

public class ATarget : ITarget
{
    public void Activate()
    {
        WasActivated = true;
    }

    public bool WasActivated { get; set; }

    public void Debug()
    {
        throw new NotImplementedException();
    }
}

Some quick things to note:

  1. For decorator interceptors, InterceptorPolicy<T> will only apply if the pluginType matches T
  2. For activation interceptors, InterceptorPolicy<T> will apply to any concrete type returned by an Instance that can be cast to T

Apply Activation Interception by Type

Let's say that in your system you have a marker interface or in this case an abstract class that exposes a single Activate() method to start up stateful, long-running services created within your container:


public abstract class Activateable
{
    public bool Activated { get; set; }

    public void Activate()
    {
        Activated = true;
    }
}

An implementation of Activateable from StructureMap's unit tests is shown below:


public class AWidget : Activateable, IWidget
{
}

If you decide that you'd like StructureMap to call the Activate() method on any object it creates as part of its object creation and resolution process, we can register an interception policy in a Registry like this:


[Fact]
public void activate_by_action()
{
    var container = new Container(x =>
    {
        x.For<IWidget>().Use<AWidget>();
        x.For<Activateable>()
            .OnCreationForAll("Mark the object as activated", o => o.Activated = true);
    });

    container.GetInstance<IWidget>()
        .ShouldBeOfType<AWidget>()
        .Activated.ShouldBeTrue();
}

There are several overloads of OnCreationForAll() covering cases with and without IContext

Apply Decoration across a Plugin Type

As shown above, you can use the Registry.For<T>().DecorateAllWith<TDecorator>() to apply decorators to all Instance's registered to a Plugin Type:


[Fact]
public void decorate_with_type()
{
    var container = new Container(x =>
    {
        x.For<IWidget>().DecorateAllWith<WidgetHolder>();
        x.For<IWidget>().Use<AWidget>();
    });

    container.GetInstance<IWidget>()
        .ShouldBeOfType<WidgetHolder>()
        .Inner
        .ShouldBeOfType<AWidget>();
}

There are also several other overloads of DecorateAllWith() for user supplied expressions, filters, and descriptions. See the acceptance tests for interception in the StructureMap codebase for many more sample usages.

Add Interception to a Single Instance

You can also define interceptors directly to individual Instance's inside of a StructureMap Registry using the OnCreation() and DecorateWith methods or the more generic Instance.AddInterceptor() method. Here is some sample usage from StructureMap's unit tests on interception:


_container = new Container(r =>
{
    r.For<ContextRecorder>().Use(recorder);

    r.For<IService>().AddInstances(x =>
    {
        x.Type<ColorService>()
            // Registers an activation action on this Instance
            .OnCreation("last service", s => _lastService = s)
            .Named("Intercepted")
            .Ctor<string>("color").Is("Red");

        x.Type<ColorService>()
            // Activation using IContext
            .OnCreation("last touched", (c, s) => c.GetInstance<ContextRecorder>().WasTouched = true)
            .Named("InterceptedWithContext")
            .Ctor<string>("color").Is("Red");

        x.Type<ColorService>()
            .Named("NotIntercepted")
            .Ctor<string>("color").Is("Blue");

        x.Object(new ColorService("Yellow"))
            .Named("Yellow")
            .OnCreation("set the last service", s => _lastService = s);

        x.ConstructedBy(() => new ColorService("Purple")).Named("Purple")
            // Applies a decorator to this instance. Not sure *why*
            // you'd want to do it this way
            .DecorateWith(s => new DecoratorService(s));

        x.ConstructedBy(() => new ColorService("Purple")).Named("DecoratedWithContext")
            // Fancier decorator
            .DecorateWith("decorated with context", (c, s) =>
            {
                c.GetInstance<ContextRecorder>().WasTouched = true;
                return new DecoratorService(s);
            });

        x.Type<ColorService>().Named("Decorated").DecorateWith(
            s => new DecoratorService(s))
            .Ctor<string>("color").Is("Orange");

        x.Object(new ColorService("Yellow")).Named("Bad")
            .OnCreation("throw exception", obj => { throw new Exception("Bad!"); });
    });