Managed Extensibility Framework uses the concept of catalogs. A single catalog is responsible for providing access to parts, which are mostly components in the system (ordinary .NET classes). I said mostly because MEF is not limited to operating on types, it also supports method, property and field composition. The catalog is then used by the CompositionContainer to pull parts from it. Out of the box MEF ships with 4 core catalogs, which are meant to discover attributed parts (an attributed part is simply a component which uses attributed programming model for discovery purposes). These catalogs are TypeCatalog, DirectoryCatalog, AssemblyCatalog, and AggregateCatalog. Catalogs serve one other crucial purpose – they are responsible for instantiating parts. The thing is that the underlying infrastructure (aka Reflection Model) responsible for that process is hidden from the developer and it is not possible to attach anything to the creation pipeline. People who just begin their MEF adventure might feel a little disappointed at first, because all of the IoC containers available today provide neat infrastructure for integrating custom build strategies into the creation pipeline. Furthermore, some of them, support interception out of the box (like the Unity container from MS Patterns & Practices).
The InterceptingCatalog, initially written by Glenn Block, has been introduced to fix this gap. It provides a convenient mechanism which allows to intercept values exported via MEF, it allows to chain interceptors together to form custom instance processing pipeline (Chain of Responsibility design pattern). The catalog also enables to create exports on the fly (which is used to provide open-generics support for MEF) and to filter existing exports based on some criteria. The catalog itself has been built around the Decorator pattern – it decorates any ComposablePartCatalog. You can find it in the MefContrib project available at mefcontrib.com.
In this series of posts I am going to discuss all the benefits of using the InterceptingCatalog. In the first part I will focus on intercepting capabilities of the catalog, and in subsequent posts I will cover filtering scenario and show how to enable the open-generics support.
Interception
Let’s begin with a simple example and then build on top of it. Assume we have IStartable interface defined as follows:public interface IStartable
{
bool IsStarted { get; }
void Start();
}
We want every component, which implements the IStartable interface, have its Start method automatically called by the IoC container as part of the creation process. First, lets investigate the solution on the Unity IoC example. Note that the interception term does not apply to the Unity solution as the Unity fully supports custom build strategies =)
public class MyExtension : UnityContainerExtension
{
protected override void Initialize()
{
Context.Strategies.AddNew<StartableStrategy>(UnityBuildStage.Initialization);
}
public class StartableStrategy : BuilderStrategy
{
public override void PreBuildUp(IBuilderContext context)
{
var instance = context.Existing as IStartable;
if (instance != null)
{
instance.Start();
}
}
}
}
The code is dead simple. We define our own extension named MyExtension, which does nothing more than registering the StartableStrategy during the instance initialization phase. The inner StartableStrategy does all the job - if the instance being created implements IStartable, it calls its Start method. The usage is also pretty straightforward. All we have to do is to add MyExtension to the container!
IUnityContainer container = new UnityContainer();
container.AddNewExtension<MyExtension>();
var foo = container.Resolve<StartableFoo>();
Console.WriteLine(foo.IsStarted); // Prints True
Assuming that the StartableFoo part implements the IStartable interface, it will have its Start method called during the Resolve method call. Now let's look at how this scenario could be implemented in MEF. As I stated previously, MEF doesn't allow the developer to register custom build strategies within the build process. Hence, we need to use interception. This is where the InterceptingCatalog comes to play. To use the InterceptingCatalog, we first need to create proper configuration. The configuration is provided by means of InterceptionConfiguration class:
public class InterceptionConfiguration : IInterceptionConfiguration
{
public InterceptionConfiguration AddInterceptor(IExportedValueInterceptor interceptor);
public InterceptionConfiguration AddHandler(IExportHandler handler);
public InterceptionConfiguration AddInterceptionCriteria(IPartInterceptionCriteria partInterceptionCriteria);
}
In this example we are interested in the AddInterceptor and AddInterceptionCriteria methods which add interceptors to the catalog. The two methods correspond to two interception levels supported by the catalog. The first is the catalog wide interception level. Interceptors registered on this level are applied to all parts available in the decorated catalog. The second level is the per part interception level. Interceptors registered on that level apply only to selected parts, and the developer specifies to which parts the interceptor should be applied by specifying a predicate which accepts ComposablePartDefinition and returns bool. Next example will show this in action. Meanwhile, this is how our StartableStrategy might look like:
public class StartableStrategy : IExportedValueInterceptor
{
public object Intercept(object value)
{
var startable = value as IStartable;
if (startable != null)
{
startable.Start();
}
return value;
}
}
As you can see, all interceptors have to implement the IExportedValueInterceptor interface, which defines single Intercept method. The method gets called as soon as the exported value is requested, and the value itself is passed as the parameter. To demonstrate the usage, we will use two parts, namely IFoo and IBar. The IBar part is defined as follows:
public interface IBar
{
void Foo();
}
[Export(typeof(IBar))]
public class Bar : IBar, IStartable
{
public Bar()
{
Console.WriteLine("Bar()");
}
public bool IsStarted { get; private set; }
public void Start()
{
IsStarted = true;
Console.WriteLine("Bar.Start()");
}
public void Foo()
{
Console.WriteLine("Bar.Foo()");
}
}
The code which uses the StartableStrategy is also very simple.
// Create the catalog which will be intercepted
var catalog = new TypeCatalog(typeof(Bar), typeof(Foo));
// Create interception configuration
var cfg = new InterceptionConfiguration()
// Add catalog wide startable interceptor
.AddInterceptor(new StartableStrategy());
// Create the InterceptingCatalog with above configuration
var interceptingCatalog = new InterceptingCatalog(catalog, cfg);
// Create the container
var container = new CompositionContainer(interceptingCatalog);
var barPart = container.GetExportedValue<IBar>();
Because the IBar part implements the IStartable interface, it will have its Start method called as part of GetExportedValue call. This example concludes the basics of using InterceptingCatalog. Lets move on to the next interception example which uses Castle.DynamicProxy to create proxies for objects. We will add logging capabilities to the IFoo part. We do not want to log method execution on the IBar part, so we are going to leverage the per part interception. The IFoo part looks identical to the IBar part, except it is decorated with ExportMetadata attribute, which tells the system that we want to log method execution on that part.
public interface IFoo
{
void Bar();
}
[Export(typeof(IFoo))]
[ExportMetadata("Log", true)]
public class Foo : IFoo, IStartable
{
public Foo()
{
Console.WriteLine("Foo()");
}
public bool IsStarted { get; private set; }
public void Bar()
{
Console.WriteLine("Foo.Bar()");
}
public void Start()
{
IsStarted = true;
Console.WriteLine("Foo.Start()");
}
}
Next comes the logging interceptor which implements Castle's IInterceptor interface.
public class LoggingInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
Console.WriteLine("--- LOG: About to invoke [{0}] on [{1}] ---",
invocation.Method.Name,
invocation.InvocationTarget.GetType().Name);
// Invoke the intercepted method
invocation.Proceed();
Console.WriteLine("--- LOG: Invoked [{0}] ---", invocation.Method.Name);
}
}
The interceptor simply prints information to the console before and after a method execution. The code which puts this sample together is almost a copy of the previous bootstrapping code:
var catalog = new TypeCatalog(typeof(Bar), typeof(Foo));
// Create interception configuration
var cfg = new InterceptionConfiguration()
// Add Castle DynamicProxy based logging interceptor for parts
// which we want to be logged
.AddInterceptionCriteria(
new PredicateInterceptionCriteria(
new DynamicProxyInterceptor(new LoggingInterceptor()), def =>
def.ExportDefinitions.First().Metadata.ContainsKey("Log") &&
def.ExportDefinitions.First().Metadata["Log"].Equals(true)));
// Create the InterceptingCatalog with above configuration
var interceptingCatalog = new InterceptingCatalog(catalog, cfg);
// Create the container
var container = new CompositionContainer(interceptingCatalog);
var barPart = container.GetExportedValue<IBar>();
barPart.Foo();
var fooPart = container.GetExportedValue<IFoo>();
fooPart.Bar();
Because we wanted to apply the logging interceptor on a per part basis, we use the AddInterceptionCriteria method, and pass the PredicateInterceptionCriteria instance. The PredicateInterceptionCriteria object takes any instance implementing the IExportedValueInterceptor interface along with a predicate which is used to determine which parts should be intercepted with the given interceptor. In the above example we intercept all parts declaring Export metadata named Log which equals to True. Note that our logging interceptor is based on Castle.DynamicProxy. To use it with the catalog, we need to wrap it with DynamicProxyInterceptor which implements IExportedValueInterceptor and can take any number of IInterceptor implementations. The sample produces the following output:
You can see that indeed the logging interceptor has only been applied to the IFoo part. Please note also that the IFoo has actually been intercepted by a chain of two interceptors, the StartableStartegy and the LoggingInterceptor (although the above sample code omits the first one for clarity). This forms a processing pipeline similar to pipelines found in regular IoC containers. This is all about interception in MEF. You can download sample code from my code gallery here. In the next post I will look at filtering capabilities. Stay tuned!
5 comments:
Many thanks. It's just this sort of explanation that is lacking in MefContrib. Would love to see more of them!
If you export Foo instead of IFoo, you will get an "unable to cast" error.
As with nearly all MEF error messages, this one is unhelpful...
"Cannot cast the underlying exported value of type 'MefContrib.Samples.Interception.Foo (ContractName="MefContrib.Samples.Interception.Foo")' to type 'MefContrib.Samples.Interception.Foo'."
You would have to implement your own Castle DynamicProxy interceptor which supports class interception. The one shipped with MefContrib is suitable for interface interception only.
Piotr
Hi, thank you for the InterceptingCatalog!
I have made a small addition to it so that you can use Unity as a proxy so that EntLib's PIAB attributes/interceptors work with MEF just like it was Unity.
You can check it here: http://baud.cz/blog/unity-entlib-interceptors-in-mef and if possible, add it to MEFContrib.
Hi. First thanks for very nice article. I have one question when i try to move your demo to asp.net mvc app i got an error:
"Cannot cast the underlying exported value of type 'MefContrib.Samples.Interception.Foo (ContractName="MefContrib.Samples.Interception.Foo")' to type 'MefContrib.Samples.Interception.Foo'."
I dont now why but its work at simple app and dont work at asp.net mvc app (
Post a Comment