Building A Fluent Interface for MEF

9 minutes read

Originally posted on: http://geekswithblogs.net/jolson/archive/2008/07/04/building-a-fluent-interface-for-mef.aspx

How would you like to use the following way to configure dependencies in MEF? A fluent interface with POCO support (no attributes necessary)? Yup.

            var resolver = new FluentResolver();
            resolver.Register<HelloWorld>().
                And<HelloGreeting>().As<IGreeting>().
                And<ConsoleOutputter>().As<IOutputter>();

            var domain = new CompositionContainer(resolver);

            // HelloWorld has dependencies on IGreeting and IOutputter
            var helloWorld = domain.Resolve<HelloWorld>();
            helloWorld.SaySomething();

Lately I've been digging more and more into the first CTP of the Managed Extensibility Framework (MEF) coming out of Krzysztof Cwalina's team here at Microsoft. By default, a developer needs to sprinkle Export and Import attributes in their classes at the point they are needing something to be injected, or on classes that need to be exported in order to be injected into other classes. However, if you don't like this behavior, MEF provides several extension points you can use to provide a different interface into MEF.

In my "ideal" Dependency Injection interface for MEF, there are several requirements that I would like:

  • No Export/Import attributes necessary; hence, support for POCOs (Plain Ol' CLR Objects)
  • Registration of types with container, not registration of object instances (where I would have to create the object myself before adding it's type to the container).
  • Simply ask container to resolve a type into an instance for me. Other than the container and resolver, none of my "domain objects" should be "new"-ed up by me.
  • No need to tell the CompositionContainer to Bind(). It should know about all the types necessary.
  • For "ease of use", I want to configure dependencies via a Fluent Interface.

I'm on a little bit of a Fluent Interface kick right now. I admit it. My name is Jason Olson and I'm a Fluent Interface junkie. If I could type emails via a Fluent Interface in code (rather than Outlook), I probably would. My addiction is just... that... bad.

Before we dig into how we will leverage MEF's extension points, let's just take a quick peek at the classes we are wanting to wire up via the interface shown above:

    class HelloWorld
    {
        public IGreeting Greeting { get; set; }
        public IOutputter Outputter { get; set; } 

        public void SaySomething()
        {
            // Our dependencies above determine where the output
            // is written to, and what the greeting will be.
            Outputter.WriteLine(Greeting.Greet());
        }
    }

    interface IGreeting
    {
        string Greet(); 
    }

    class HelloGreeting : IGreeting
    {
        public string Greet() 
        {
            return "Hello Fluent Interface!";
        }
    }

    interface IOutputter
    {
        void WriteLine(string message); 
    }

    class ConsoleOutputter : IOutputter
    {
        public void WriteLine(string message)
        {
            Console.WriteLine(message);
        }
    }

So how do we enable this new behavior? There are two primary MEF extension points we will use, a custom ComponentBinder and a custom ValueResolver. According to the CTP documentation (emphasis mine):

"The role of the ComponentBinder is to create a collection of primitives associated with its component. There is only one ComponentBinder per component. The CompositionContainer will use the collection of binders during the bind operation to determine how components need to be wired-up."

In this case, "collection of primitives" essentially means the export and imports of the component it is bound to. One of the interesting points to note is that the CompositionContainer uses these ComponentBinders to determine how various components need to be created. So for MEF to determine what the various imports and exports are for wiring up components, it can use our custom ComponentBinder to do so. So while the default ReflectionBinder will generate this information via attribute declarations on the components in question, we can change this behavior if we wish.

The other extension point we will use is a custom ValueResolver. Once again, according to the CTP documentation (emphasis mine):

"A ValueResolver allows the composition container to retrieve components from some sort of repository. The composition container’s bind operation has the single goal of satisfying all imports for all components that were explicitly added to it. During a bind operation, the composition container can query the ValueResolver for exports if it deems it necessary (e.g. no exports currently in the composition container satisfy a specific import, an import is requesting a collection of all available exports, etc.)."

To implement these two extension points, we will need two custom classes: FluentResolver and FluentBinder. Our FluentResolver is what we use to build up a map of our dependencies via the fluent interface shown above. When the FluentResolver is then passed into a CompositionContainer, we will go through our dependency map and crank out a bunch of FluentBinders that MEF can then use when needing to create various objects the user is asking for.

First, our FluentResolver. FluentResolver contains the methods that make up our fluent interface and that will hold the dependency map we are building up. And then we our FluentResolver is hooked up to a CompositionContainer, we will use this "dependency map" in order to build up all the FluentBinder instances that MEF will use to wire up all our dependencies.

    class FluentResolver : ValueResolver, IRegisterChainer
    {
        private Type lastRegisteredType;
        private IDictionary<Type, IList<Type>> exportTypes = new Dictionary<Type, IList<Type>>();
        private IList<Type> registeredTypes = new List<Type>();

        // The following three methods define our fluent interface
        // (Register<T>, And<T>, and As<T>).
        public IRegisterChainer Register<T>() where T : class, new()
        {
            if (!exportTypes.ContainsKey(typeof(T)))
            {
                exportTypes.Add(typeof(T), new List<Type>());
            }

            exportTypes[typeof(T)].Add(typeof(T));
            registeredTypes.Add(typeof(T));

            lastRegisteredType = typeof(T);
            return this;
        }

        public IRegisterChainer As<T>() where T: class
        {
            // Add exported type to last registered type (the call right
            // before .As<T>() in the fluent interface). This is used
            // to register a type as an interface, rather than the
            // concrete type.
            exportTypes[lastRegisteredType].Add(typeof(T));
            return this;
        }

        public IRegisterChainer And<T>() where T : class, new()
        {
            // Just a different way to register so that the fluent
            // interface is easily readable.
            return Register<T>();
        }

        // This is where the "magic" happens. When our container is set (like
        // when we are passed into the constructor of a container), we will
        // create all of our custom binders that MEF will then use to wire-up
        // dependencies.
        protected override void OnContainerSet()
        {
            base.OnContainerSet();

            foreach (var type in registeredTypes)
            {
                // An "Import" (read: injected dependency) is defined as any property whose
                // type is a type that is "exported" from any other registered type
                var imports = (from pi in type.GetProperties()
                               from export in exportTypes
                               where export.Value.Contains(pi.PropertyType)
                               select pi).Distinct();

                // Export all types our specific type has been asked to export and all the 
                // properties from above that are dependencies we need to inject.
                Container.AddBinder(new FluentBinder(type, exportTypes[type], imports.ToList()));
            }
            
            // Since all the dependencies are known from our fluent interface,
            // automatically bind the container so that the user doesn't have to
            // call Bind() themselves.
            Container.Bind();
        }

        public override CompositionResult<IImportInfo> TryResolveToValue(string name, IEnumerable<string> requiredMetadata)
        {
            // Same as TryResolveToValues below, except we just return a
            // single value, rather than a collection of values.
            var result = TryResolveToValues(name, requiredMetadata);

            return new CompositionResult<IImportInfo>(result.Succeeded, 
                result.Issues, 
                result.Value.First());
        }

        public override CompositionResult<ImportInfoCollection> TryResolveToValues(string name, IEnumerable<string> requiredMetadata)
        {
            // Based on all the custom binders we have created, have
            // the container get the various dependent components for us.
            return TryGetContainerLocalImportInfos(name, requiredMetadata);
        }
    }

IRegisterChainer is simply an interface our three fluent methods (Register<T>, And<T>, As<T>) return (and that FluentResolver implements) that allows us to keep on chaining our method calls one after the other, hence enabling our fluent interface.

    interface IRegisterChainer
    {
        // Allows registering another dependency into our resolver
        IRegisterChainer And<T>() where T : class, new();

        // Enables registering a depedency as a specific type (like
        // an interface) on top of the concrete type it directly implements
        IRegisterChainer As<T>() where T : class;
    }

And now on to our FluentBinder:

    class FluentBinder : ComponentBinder
    {
        // An instance of the type we are the binder for
        private object instance;

        // The types we export (could be our concrete type, interfaces, etc.)
        private IList<Type> exports;

        // Our properties that are dependencies needing resolving
        private IList<PropertyInfo> imports;
       

        public FluentBinder(Type type, IList<Type> exports, IList<PropertyInfo> imports) 
        {
            this.exports = exports;
            this.imports = imports;

            // In the future, we could use constructor injection here. For now,
            // just use the default constructor and property injection.
            instance = type.GetConstructor(new Type[] {}).Invoke(new object[] {});
        }


        public override IEnumerable<string> ExportNames
        {
            // Return the name of the Types we are Exporting
            get { return exports.Select(t => t.ToString()); }
        }

        public override CompositionResult Export() 
        {
            // Add our object instance into the container for every type
            // that we are exporting.
            foreach (var type in exports)
            {
                AddValueToContainer(type.ToString(), instance);
            }

            return new CompositionResult(true, new List<CompositionIssue>());
        }

        public override IEnumerable<string> ImportNames
        {
            // Return the name of the Types we need Imported
            get { return imports.Select(pi => pi.PropertyType.ToString()); }
        }

        public override CompositionResult Import(IEnumerable<string> changedValueNames) 
        {
            // Import every property we have that is a dependency
            foreach(var propertyInfo in imports)
            {
                // Inject an instance of the type of our property from the container
                var component = Container.TryGetBoundValue(propertyInfo.PropertyType.ToString(), 
                    propertyInfo.PropertyType);

                propertyInfo.SetValue(instance, component.Value, null);
            }

            return new CompositionResult(true, new List<CompositionIssue>());
        }
    }

This FluentBinder is essentially the heart of the our behavior. We use the the results from our FluentResolver that are passed in to our binder to let MEF know what types we need to import and what types we are exporting from the type FluentBinder is bound to.

The final thing we need to enable our desired API from above is an extension method to CompositionContainer called Resolve<T>() which we just give the type that we would like an instance of. I just happen to think this thin wrapper around TryGetBoundValue<T>() is easier to use and better to read.

    static class CompositionContainerExtensions
    {
        public static T Resolve<T>(this CompositionContainer domain)
        {
            return domain.TryGetBoundValue<T>().Value;
        }
    }

And now we can combine all the pieces together to build our sample application:

    class Program
    {
        static void Main(string[] args)
        {
            // Declare dependencies via a fluent interface.
            // No need for attributes, XML config, or 
            // registration of instances of objects.
            var resolver = new FluentResolver();
            resolver.Register<HelloWorld>().
                And<HelloGreeting>().As<IGreeting>().
                And<ConsoleOutputter>().As<IOutputter>();

            // Pass in our custom resolver and have it help
            // take care of the binding process.
            var domain = new CompositionContainer(resolver);

            // Ask the container for an instance of a type.
            var helloWorld = domain.Resolve<HelloWorld>();
            helloWorld.SaySomething();

            Console.ReadKey(true);
        }
    }

While the fluent interface might be seen as a potential "big departure" from the way MEF works out-of-the-box, you can see that it is very possible (and not too difficult) to change how dependencies are configured with MEF. You might want your configuration in an XML file, in a DSL defined in a map.txt file, in a database (no I don't know why you'd want that :P), etc. And no matter how you want it done, it is possible for you to do so with just a little coding.

Until next time, Happy Coding!

Obviously, this is just a sample prototype and doesn't have several features necessary in DI containers. So, more reason for me to write some more code :). Also, remember this is built on the first CTP of MEF, so the odds are that it will become out-of-date at a later time.

Updated:

Leave a Comment