After I had a chance to dance with MEF, I wanted to go a step further and create my own logic to Bind the dependencies, and integrate it to the existing composition system. I have to say that, oh god, it was NOT as easy that as I expected. Current nature of the APIs heavily depend on counter intuitive usage. Again, I am having a difficult time to save my critics as a separate post as you can see
There are currently 2 main extensibility points in MEF. Custom Binders and Value Resolvers.
The current nature of the Composition Container is, basically, Container is the god. It needs to have everything to bind, if you want to add a type binding logic, you are welcome. But first, you need to add your type to the container. Well, that’s a fair game, since our logic is Binding(wiring really) logic - not a type or instance management one. This means that we have to write the following as usual:
1: CompositionContainer container = new CompositionContainer();
Where Provider and Consumer are just defined with Export & Import model as MEF needs them :
1: public class Consumer
4: public IProvider Provider
10: public Consumer()
15: public int GetAnInteger()
17: return this.Provider.GetAnInteger();
And hence is the provider:
2: public class FavoredProvider : IProvider
4: #region IProvider Members
6: public int GetAnInteger()
8: return 5;
And this makes the following test to pass, which just expects the hard coded “5″ value to be returned:
1: /// <summary>
2: ///A test for GetAnInteger
5: public void GetAnIntegerTest()
7: Consumer target = new Consumer();
8: CompositionHelper.InitializeContainerForConsumer(target); // this is where container configuration goes
9: int expected = 5;
10: int actual;
11: actual = target.GetAnInteger();
12: Assert.AreEqual(expected, actual);
Now, let’s come up with a hypothetical scenario : We need the same instance for every consumer (singleton). How hypothetical that is for a DI container! So while configuring I would expect to write something close to the following, and still expect test to pass! (Note that you can use IsSingleton for the contract, but the one and only life time controlling mechanism isn’t singleton is it? )
1: CompositionContainer container = new CompositionContainer();
2: var myVeryCustomBinder = new SampleBinder();
4: // not adding the provider exlusively, expecting the binder to handle
Q: So we didn’t add the provider to the container, haven’t you just said that this is a deal breaker ?
A: Yes it is, you are right. My test now breaks. But I can make that up, in my custom binder. Gimme a break.
To write a custom binder I need to inherit from the abstract class ComponentBinder . In ComponentBinder, there are several virtual methods that are waiting to be overridden depending on my logic:
- public override CompositionResult Export() : This is the place that we’ll add the container the results of our resolve operations. The return value also indicates whether the operation is succeeded or not.
- public override CompositionResult Import(IEnumerable<string> changedValueNames) : And this is the place that we will retrieve the demanding objects for the services that are exported.
- public override CompositionResult BindCompleted() : This kind of acts as an event, (why is it not one?) and will be called when the binding is completed. Here you can do your sanity checks after your bind operations needed for your binder,
- public override IEnumerable<string> ExportNames ; These are the “names” for the types. This can be the name specified in the Export() declaration in the attribute or a default one.
- public override IEnumerable<string> ImportNames : Same goes with imports, but instead import process.
So here would be my Custom Binder:
1: public class SampleBinder : ComponentBinder
3: private FavoredProvider ProviderToInject
9: private static readonly object syncRoot = new object();
11: public SampleBinder()
13: if (ProviderToInject == null)
15: lock (syncRoot)
17: ProviderToInject = new FavoredProvider();
22: public override CompositionResult Export()
25: this.AddValueToContainer(CompositionServices.GetContractName(typeof(IProvider)), this.ProviderToInject, “Provider”);
26: return CompositionResult.SucceededResult; ;
29: public override CompositionResult Import(System.Collections.Generic.IEnumerable<string> changedValueNames)
31: return base.Import(changedValueNames);
34: public override CompositionResult BindCompleted()
36: return base.BindCompleted();
39: public override IEnumerable<string> ExportNames
43: return base.ExportNames;
47: public override IEnumerable<string> ImportNames
51: return base.ImportNames;
As you see, we are not doing anything on import. For any import of I provider type, we Are providing the Favored Provider. Also notice the badly named utility class CompositionServices which currently only has 1 method, and it is a useful one : GetContractName. Without this, I’d have to hardcode it or get it with a drop of reflection magic.
Adding this Binder Makes my test pass, and I am happy.
This is an enabled model in this CTP, but the Usage of it is not enabled (at least I couldnt find out a way, since the Resolver value in the container does not have a setter - any feedback on this appreciated(1)) . Value Resolver is the part to provide the types, and probably in a sane implementation life time logic will go here. It is an abstract class and 2 abstract methods are waiting to be overriden, below is a sample Value Resolver:
1: public class CustomValueResolver : ValueResolver
3: public override CompositionResult<IImportInfo>
4: TryResolveToValue(string name, IEnumerable<string> requiredMetadata)
6: // do your resolve, and send it back
7: ImportInfo<FavoredProvider> resolvedValue =
8: new ImportInfo<FavoredProvider>(null);
9: return new CompositionResult<IImportInfo>(true,
10: Enumerable.Empty<CompositionIssue>(), resolvedValue);
13: public override CompositionResult<ImportInfoCollection>
14: TryResolveToValues(string name, IEnumerable<string> requiredMetadata)
16: // do the same if you have more than one service to provide to one consumer
17: ImportInfo<FavoredProvider> resolvedValue =
18: new ImportInfo<FavoredProvider>(null);
20: return new CompositionResult<IImportInfo>(true,
21: Enumerable.Empty<CompositionIssue>(), resolvedValue);
In the future hopefully we will be able to create our own component catalogs and associate resolvers to them, like the way we can’t to now to both Container and to AssemblyComponentCatalog.(2)
Hope this article gave a deeper hint on what’s going on MEF side of things. Any comments on this are welcome as always.
(1): Jason pointed out that there is an overload of CompositionContainer, that takes ValueResolver as a parameter. doh! (see comments)
(2): Still couldnt find a way of doing the same to Component Catalog.