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:
- Activates - Do some action on or with an object just created or resolved by StructureMap
- 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:
- For decorator interceptors,
InterceptorPolicy<T>
will only apply if thepluginType
matchesT
- For activation interceptors,
InterceptorPolicy<T>
will apply to any concrete type returned by anInstance
that can be cast toT
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!"); });
});