Reposted from http://structuremap.sourceforge.net/Interception.htm. I think this will be the last StructureMap post of the day for a while.
StructureMap 2.5+ added the ability to postprocess or even intercept and replace
the objects being created. While StructureMap will never include its own
Aspect Oriented Programming model (the world does not need a new one), the
interception techniques shown below could be used to apply runtime AOP from
existing AOP tools like the Policy Injection Application Block from Microsoft or the AOP library from Spring.Net.
In general, interception is specified in three ways:
- OnCreation() — Registers an Action to run against the new object after creation
- EnrichWith() — Registers a Func that runs against the new object after creation
and gives you the option of returning a different object than the original
object - A custom class that implements the TypeInterceptor interface (the runtime model
behind all the interception techniques)
Intercept a Single Instance
You can specify interception for a single Instance. This interception could
be combined with other Interception policies, but users should be cautious about
this.
Run an Action Against an Object
Some classes may require some extra bootstrapping work before they are ready to
be used. While I recommend building classes in such a way that the new
objects are ready to function after calling the constructor function, not every
class you will encounter will follow this rule. For that reason,
StructureMap has the ability to register an Action<T> to run against a newly
created object before it is returned to the requesting code. You can
register that Action on an individual Instance:
public class
InterceptionRegistry :
Registry
{
public
InterceptionRegistry()
{
// Perform an Action<T> upon the object of type T
// just created before it is returned to the caller
ForRequestedType<ClassThatNeedsSomeBootstrapping>().TheDefault.Is
.OfConcreteType<ClassThatNeedsSomeBootstrapping>()
.OnCreation(x => x.Start());
// or…
// You can also register an Action<IContext, T> to get access
// to all the services and capabilities of the BuildSession
ForRequestedType<ClassThatNeedsSomeBootstrapping>().TheDefault.Is
.OfConcreteType<ClassThatNeedsSomeBootstrapping>()
.OnCreation((context, x) =>
{
var connection = context.GetInstance<IConnectionPoint>();
x.Connect(connection);
});
}
}
Wrap or Substitute the Returned Object
Sometimes you may want to wrap the constructed object in some sort of Decorator
or apply runtime AOP to the new object. In this case, StructureMap will
allow you to substitute the constructed object for the new wrapped object —
with the restriction that the object returned must be assignable to the
requested PluginType. Let’s consider the case of using a
Decorator pattern to add logging to an existing service:
public class
LoggingDecorator :
IConnectionListener
{
private
readonly
IConnectionListener _inner;
public
LoggingDecorator(IConnectionListener inner)
{
_inner = inner;
}
}
When you register an instance of IConnectionListener, you can specify that the
constructed object get wrapped with a decorator using the EnrichWith() syntax
like this:
public
InterceptionRegistry()
{
ForRequestedType<IConnectionListener>().TheDefault.Is
.OfConcreteType<ClassThatNeedsSomeBootstrapping>()
.EnrichWith(x => new
LoggingDecorator(x));
}
In the sample registration above, a call to
Container.GetInstance<IConnectionListener>() will result in a new
ClassThatNeedsSomeBootstrapping wrapped in a LoggingDecorator object:
[Test]
public
void
see_the_enrichment_with_a_decorator_in_action()
{
var container = new
Container(new
InterceptionRegistry());
container.GetInstance<IConnectionListener>()
.ShouldBeOfType<LoggingDecorator>()
.Inner.ShouldBeOfType<ClassThatNeedsSomeBootstrapping>();
}
There is also an overload of EnrichWith() that takes in the IContext object:
ForRequestedType<IConnectionListener>().TheDefault.Is
.OfConcreteType<ClassThatNeedsSomeBootstrapping>()
.EnrichWith((context, x) =>
{
var connection = context.GetInstance<IConnectionPoint>();
x.Connect(connection);
return new
LoggingDecorator(x);
});
See Using the Session Context for more
information on using the IContext.
With a Custom Interceptor
To write a custom Interceptor for a single Instance, create a new class that
implements the InstaneInterceptor interface:
public interface
InstanceInterceptor
{
object Process(object target,
IContext context);
}
with a class like this:
public class
CustomInterceptor :
InstanceInterceptor
{
public
object Process(object
target, IContext context)
{
// manipulate the target object and return a wrapped version
return wrapTarget(target);
}
private
object wrapTarget(object
target)
{
throw new
NotImplementedException();
}
}
Then, register the a new object instance of the CustomerInterceptor:
ForRequestedType<IConnectionListener>().TheDefault.Is
.OfConcreteType<ClassThatNeedsSomeBootstrapping>()
.InterceptWith(new
CustomInterceptor());
Apply Interception to all Instances of a PluginType
The same OnCreation(), EnrichWith(), and InterceptWith() methods can be applied
to all Instances of a given PluginType off of the ForRequestedType() or
BuildInstancesOf() methods of the Registry DSL:
// Place the Interception at the PluginType level
ForRequestedType<IConnectionListener>()
.OnCreation(x => x.StartConnection())
// OnCreation
.EnrichWith(x => new
LoggingDecorator(x))
// Enrich
.InterceptWith(new
CustomInterceptor())
// Custom Interceptor
.TheDefaultIsConcreteType<ClassThatNeedsSomeBootstrapping>();
Note that these methods can be used in combination with each other and even
multiple times for the same type. All additional calls are additive.
Use with caution!
Apply Interception to all Types Matching a Criteria
If an interception policy is simple, you can just register the interception
policy with the IfTypeMatches( Predicate<Type> ).InterceptWith(
Lambda ) syntax:
registry.IfTypeMatches(type
=> type.Equals(typeof (BlueSomething)))
.InterceptWith(rawInstance => new
WrappedSomething((IAnInterfaceOfSomeSort) rawInstance));
Please note that when StructureMap encounters a new concrete type for the first
time, it searches for all TypeInterceptors that match that the concrete type,
and caches these TypeInterceptors against the concrete type for future usage.
The long and short of this is that any filter on the type is only going to be
evaluated once.
Creating a Custom Type Interceptor
Sooner or later the Fluent Interface registration of TypeInterceptors will not be
adequate. In that case, you can create a custom class that implements the
TypeInterceptor interface:
///
<summary>
/// A
TypeInterceptor that is only applied if the MatchesType()
/// method is
true for a given Type
///
</summary>
public interface
TypeInterceptor :
InstanceInterceptor
{
/// <summary>
/// Does this TypeInterceptor apply to the given type?
/// </summary>
/// <param
name=”type”></param>
/// <returns></returns>
bool MatchesType(Type type);
}
Registered TypeInterceptor objects are applied against any object created by
StructureMap if the type of the new object meets the MatchesType() method of
TypeInterceptor. You can happily use multiple TypeInterceptors.
Here’s a fairly advanced example. Let’s say that you’re using an
Event Aggregator pattern. You’ll typically have some sort of Event
Aggregator, and a listener interface to register with the Event Aggregator.
public interface
IEventListener<T>
{
void ProcessEvent(T
@event);
}
public interface
IEventAggregator
{
void
RegisterListener<T>(IEventListener<T>
listener);
void
PublishEvent<T>(T @event);
}
Let’s say that anytime an object of any sort (Presenter, Controller, View, who
knows what) is created by StructureMap, we want to see if that object implements
any sort of IEventListener<T> interface, and if it does, register that object as
a listener with the IEventAggregator. Here’s the custom TypeInterceptor
that does just this (most of the code is actually just massaging the generic
types, but I wanted a nontrivial example):
public class
ListenerInterceptor :
TypeInterceptor
{
public
object Process(object
target, IContext context)
{
// Assuming that “target” is an implementation of IEventListener<T>,
// we’ll do a little bit of generics sleight of hand
// to register “target” with IEventAggregator
var eventType = target.GetType().FindInterfaceThatCloses(typeof (IEventListener<>)).GetGenericArguments()[0];
var type = typeof (Registration<>).MakeGenericType(eventType);
Registration registration = (Registration)
Activator.CreateInstance(type);
registration.RegisterListener(context, target);
// we didn’t change the target object, so just return it
return target;
}
public
bool MatchesType(Type type)
{
// ImplementsInterfaceTemplate is an Extension method in the
// StructureMap namespace that basically says:
// does this type implement any closed type of the open template type?
return type.ImplementsInterfaceTemplate(typeof
(IEventListener<>));
}
// The inner type and
interface is just a little trick to
// grease the generic
wheels
public
interface
Registration
{
void RegisterListener(IContext
context, object listener);
}
public
class
Registration<T> : Registration
{
public void RegisterListener(IContext context,
object listener)
{
var aggregator = context.GetInstance<IEventAggregator>();
aggregator.RegisterListener<T>((IEventListener<T>)
listener);
}
}
Finally, you can register the new ListenerInterceptor like this:
public class
ListeningRegistry :
Registry
{
public
ListeningRegistry()
{
RegisterInterceptor(new
ListenerInterceptor());
}
}
By the way, this is cooked up sample code. Don’t dream for a second that
it’ll work without some testing.