Handling Lazy + Metadata instances in Managed Extensibility Framework (MEF)

Have you ever wanted to dynamically choose from many potential implementations of some code? Would you like to have the power of metadata filtering and lazy instantiation of the selected implementation? Here is how to do just that using Managed Extensibility Framework (MEF) and also a neat trick to handle your implementation/metadata pairs that I recently put to good use. Code samples and optimizations follow so don’t worry; I won’t remain all theoretical in this post. (MEF is part of .NET Framework 4.0 but is available also on CodePlex for other releases – MEF Community Site.)

Up front I want to show what I am aiming for so you can evaluate if this is a worth while read: Using the more advanced features of MEF suddenly I find I use these pretty gruesome variable and parameter declarations: Lazy<IWorkItem, IWorkItemView>. After applying the fix in this post I am left with the exact same behavior and this much improved declaration: IEnumerable<IWork>.

Background on MEF

Using MEF you can easily juggle the concept of lazily (using System.Lazy<T>) importing an open ended set of instances of a given contract. This is built into the library for you. It takes some getting used to the concept but it is quite powerful. As if this wasn’t enough you can also add metadata to the contracts (with the MEF version of Lazy<T> – System.Lazy<T, TMetadata>) to enable you to select which of the – potentially many – implementations of the contract that you want to lazily load into your app domain and instantiate. Does this all sound like geek speech? Well it sure is but as I said this is a very powerful concept. Given that you have a good requirement for this scenario you never have to code extensibility code yourself ever again. I would go so far as to say that MEF makes the extensibility scenario a solved problem!

My Scenario

Working with improving the Worker role concept in Windows AzureI I recently had a use for an open ended set of implementations of what I called “work methods”. I wound up making my work methods implementations of IWorkItem. The metadata I added to my work method classes I decorated with IWorkItemView metadata contract. This is what the two contracts look like:

public interface IWorkItem
{ bool HasWork { get; } void DoWork(); }
public interface IWorkItemView { string Name { get; } int Order { get; } }

When using these contracts lazily in MEF I get the ugly instances of Lazy<IWorkItem, IWorkItemView>. There is some really cool functionality here for sure but I just can’t bare the ugliness of the statement as I begin to pass these instances around in method calls etc. The cool thing with MEF is that I don’t know beforehand how many implementations I have. All I need to know is that I can get them all:

[ImportMany]
IEnumerable<Lazy<IWorkItem, IWorkItemView>> Work { get; set; }

This gives me a whole enumeration of lazy instances that are combined with the metadata of the metadata contract. On each instance of the lazy works you call .Metadata to access the IWorkItemView contract and .Value to instantiate and return the instance of the IWorkItem. Now I don’t know about you but this is beginning to look kind of hairy to me. After all in my code I am not really concerned with any laziness or metadata contracts. Not only that – provided you are a coder that believes in separation of concerns there is one more issue. To explain the last statement let’s go back to my Windows Azure scenario:

I import my many lazy instances with metadata into my code. Then I’d like to begin to use those instances. I wrote some code to filter out which instance I wanted to use. I wrote code to execute the selected instance. I wanted to separate these two code blocks from each other and I also wanted to be able to exchange filtering methods on the fly. I could continue but some of you have certainly begun to see the problem here. I was now passing around parameters of type Lazy<IWorkItem, IWorkItemView>. That’s not exactly pretty! What I found that I was conceptually doing was rather using an instance of something that had the combined contract of my two work-method, and work-metadata contracts:

public interface IWork : IWorkItem, IWorkItemView { }

Passing around parameters of type IWork is a lot better than Lazy<IWorkItem, IWorkItemView>. You have to all agree to that, right?

So now – finally – the issue becomes that of transforming one instance into the other. I also want to keep the Lazy capability. The transformation technique is mostly built into an abstract base class I’ve written which you can download here:

I have also put the code in the MEF Contrib project at Github.

Using the base class comes with a little bit of custom implementation per conversion you want to do from from “lazy with metadata” to “other domain object”. The alternative would have been for me to use a dynamic code generation approach for the implementation but I felt that dynamic code in this instance is a serious overkill. Besides we also need to make sure MEF picks up our implementation which in this case means we have to apply an attribute to our implementation.

The solution

To re-iterate: We had the ImportMany statement above which is a bit cumbersome. Instead we are aiming for this:

[Import]

IEnumerable<IWork> Work { get; set; }

Please notice the two different attributes ImportMany and Import. There is an important difference. The first one will find a lot of implementations of the contract (zero to many) while the latter will find only one exact match to the contract. In the first case we want MEF to find all IWorkItem implementations that are decorated with the IWorkItemView metadata. In the second case we want to find one single collection of IWork.

The trick is to collect all the instances (ImportManyAttribute) and hand them back out (ExportAttribute) as a single collection. What we do in the base class is import the many lazy with metadata instances and then immediately export a whole collection of the new contract to be picked up somewhere in the code as needed.

Details

public abstract class ValueMetadataCompactorBase<TValue, TMetadata, TValueMetadata> :
    IEnumerable<TValueMetadata>
    where TValueMetadata : TValue, TMetadata
{
    protected abstract TValueMetadata Compact(Lazy<TValue, TMetadata> lazyValueMetadata);

    [ImportMany(AllowRecomposition = true)]
    internal IEnumerable<Lazy<TValueMetadata>> ValueMetadatas { get; set; }

    [ImportMany(AllowRecomposition = true)]
    internal IEnumerable<Lazy<TValue, TMetadata>> ValuesWithMetadata { get; set; }
     // Cliped IEnumerable implementation for brevity [...]
}

There are a few things to notice here:

  • We ImportMany instances of the Lazy<TValue, TMetadata> and also for good measure we ImportMany istances of Lazy<TValueMetadata>. The reason for the latter is that since we offer the contract IWork for any developer to pick up an implement we can’t be 100% certain some one doesn’t use it in a direct implementation. It is no crime to do so even though the intent is to implement IWorkItem and decorate it with the metadata IWorkItemView. The real reason for not implementing the IWork direct is because we can remain lazy with the metadata if it is real metadata. If we want to start looking at the members of an implementation that does not have metadata we need to load that implementations assembly into the AppDomain.
  • The class implements IEnumerable<TValueMetadata>. This is used in the derived classes to Export the behavior and make the transformation accessible to our needy develoeprs. ;~)

To use this base class you have to do two things:

1) Implement the base class which constitutes implementing one method for compacting together a TValue and TMetadata into a TValueMetadata.

2) Apply the ExportAttribute with the parameter typeof(<your type>).

Let me show you my implementation of the compactor for compacting IWorkItems with IWorkItemView into instances of IWork:

[Export(typeof(IEnumerable<IWork>))]
internal class WorkCompactor : ValueMetadataCompactorBase<IWorkItem, IWorkItemView, IWork>
{
    protected override IWork Compact(Lazy<IWorkItem, IWorkItemView> lazyValueMetadata)
    {
        return new WorkWrapper(lazyValueMetadata);
    }

    private class WorkWrapper : IWork
    {
        private readonly Lazy<IWorkItem, IWorkItemView> lazyWorkWithMetadata;

        public WorkWrapper(Lazy<IWorkItem, IWorkItemView> lazyWorkWithMetadata)
        {
            this.lazyWorkWithMetadata = lazyWorkWithMetadata;
        }

        public bool HasWork
        {
            get { return lazyWorkWithMetadata.Value.HasWork; }
        }

        public void DoWork()
        {
            lazyWorkWithMetadata.Value.DoWork();
        }

        public int Order
        {
            get { return lazyWorkWithMetadata.Metadata.Order; }
        }

        public string Name
        {
            get { return lazyWorkWithMetadata.Metadata.Name; }
        }
    }
}

Notice:

  • The annoyance here is that I have to custom implement a boring IWork wrapper ourselves since I am not using a dynamic code approach.
  • The implementation of said IWork retains the lazy behavior in my implementation since I only forward calls to the members to the contained lazy instance.
  • The [Export(typeof(IEnumerable<IWork>))] attribute on the implementation is that which makes my IEnumerable<IWork> available to the rest of my code through MEF.
  • The implementation is marked as internal. MEF doesn’t care – it finds internal, protected and private implementations as well as public.. This means that user coders do not even know that this conversion exists. All the handling of lazy etc. is removed from the “subscribing” code.

Summary

Note to self: Write less epic blog posts. ;~)

I hope this post gives you some insight into how MEF works as well as a neat trick to simplify some of your MEF-Import code and lazy-metadata craziness. The functionality is very good to have but I really do not want library specific concerns to affect my code too much. Do you?

Cheers,

M. (the NoOpMan)

This Post Has One Comment

  1. Hi, thanks a lot for the epic blog post! :-D It really helped me simplify the code in my project. As a feedback, I would like to tell you that the Crayon Syntax Highlighter seems to be broken. It is escaping the html tags and it is very difficult to read the code samples.

Leave a Reply

Close Menu