Building a Configuration Binder for MEF with POCO Support

After taking extensibility points of Managed Extensibility Framework to a spin,(which will be called “primitives” from next version on), Jason Olson has posted a nice way of enabling Fluent Interfaces and making MEF more compatible in a more DI ish way, and trying to enable support for POCOs.

When Krzystof Kcwalina announced first CTP of MEF, he made a comment that not attribute based programming model is feasible, and Jason has provided it in the post. But  appearantly the team is going to keep Import and Export model in the first CTP, according to David Kean’s reply to one of the replies in his blog post.

Now, that makes me cringe. Clearly, I don’t like this kind of magic. Somebody exports, somebody imports, and a-ha! I have a list of import info in my domain (which is another form of intrusiveness in your design).

In this post, I will build a Configuration Resolver that will use the application’s context configuration to resolve the values in the container with pure POCO support.  This is based on first CTP, and has a lot of hack, but I think involves some good work worth to look at.

All I want is to feed the container via an XML configuration (XmlRepository in our case) and get my dependencies injected in the configured way. If I had to mimic MEF approach, I would have come up a configuration like this:

   1: <?xml version=”1.0″ encoding=”utf-8″ ?>
   2: <configuration>
   3:   <configSections>
   4:     <section name=”xmlBinder” type=”XmlBinder.Configuration.XmlBinderConfigurationSection, XmlBinder” />
   5:   </configSections>
   6:   <xmlBinder>
   7:     <objects>
   8:       <object name=”HelloWorld” type=”XmlBinder.TestClasses.HelloWorld, XmlBinder.TestClasses”>
   9:         <import name=”Outputter” type=”XmlBinder.TestClasses.Interfaces.IOutputter, XmlBinder.TestClasses” contract=”outputContract”/>
  10:         <import name=”Greeter” type=”XmlBinder.TestClasses.Interfaces.IGreeter, XmlBinder.TestClasses” contract=”greetingContract”/>
  11:       </object>
  12:
  13:       <object name=”Outputter”>
  14:         <export name=”Outputter” type=”XmlBinder.TestClasses.Interfaces.Outputter, XmlBinder.TestClasses” contract=”outputContract” />
  15:       </object>
  16:
  17:       <object name=”Greeter”>
  18:         <export name=”" type=”XmlBinder.TestClasses.Interfaces.Greeter, XmlBinder.TestClasses” contract=”greetingContract” />
  19:       </object>
  20:     </objects>
  21:   </xmlBinder>
  22: </configuration>

But this didn’t seem natural to me. First, they need to share the contract name, which is prone to configuration errors.Second and export/import model is still in place - and in a worse format. I have gone for a model like this instead (looked a bit spring - unity mixture at the end)

   1: <?xml version=”1.0″ encoding=”utf-8″ ?>
   2: <configuration>
   3:   <configSections>
   4:     <section name=”xmlBinder” type=”XmlBinder.Configuration.XmlBinderConfigurationSection, XmlBinder” />
   5:   </configSections>
   6:   <xmlBinder>
   7:       <objects>
   8:         <object name=”HelloWorld” type=”XmlBinder.TestClasses.HelloWorld, XmlBinder.TestClasses”>
   9:           <properties>
  10:             <!–<property name=”PropertyName” destination=”XmlBinder.TestClasses.ConsoleOutputter, XmlBinder.TestClasses” /> Will be supported in the future, hopefully :)–>
  11:             <property name=”Outputter” type=”XmlBinder.TestClasses.Interfaces.IOutputter, XmlBinder.TestClasses” mapTo=”XmlBinder.TestClasses.ConsoleOutputter, XmlBinder.TestClasses” />
  12:             <property name=”Greeter” type=”XmlBinder.TestClasses.Interfaces.IGreeter, XmlBinder.TestClasses” mapTo=”XmlBinder.TestClasses.Greeter, XmlBinder.TestClasses” />
  13:           </properties>
  14:         </object>
  15:       </objects>
  16:   </xmlBinder>
  17: </configuration>

Here I have my XmlBinder.Configuration namespace to store my configuration related classes.

image

Figure 1. Configuration Classes

As you see, I am defining a configuration section which has a list of object configurations in it. Object has properties, and properties have names, source types and destination types to be mapped.  Now although there is an ok amount of code there, I am not gonna talk about how I parse the configuration information, if you are interested you can download the source and read the tests.

With all these in place, I want my POCO’s, who look like this like in Jason’s example, without any Imports or Exports:

   1: /// <summary>
   2: /// Plain old Hello World
   3: /// </summary>
   4: public class HelloWorld
   5: {
   6:     public IOutputter Outputter
   7:     {
   8:         get;
   9:         set;
  10:     }
  11: 
  12:     public IGreeter Greeter
  13:     {
  14:         get;
  15:         set;
  16:     }
  17: 
  18:     public void SayIt()
  19:     {
  20:         Outputter.Output(Greeter.Greet());
  21:     }
  22: }
  23: 
  24: public class ConsoleOutputter : IOutputter
  25: {
  26:     #region IConsoleOutputter Members
  27: 
  28:     public void Output(string message)
  29:     {
  30:         Console.WriteLine(message);
  31:     }
  32: 
  33:     #endregion
  34: }
  35: 
  36: public class Greeter : IGreeter
  37: {
  38:     #region IGreeter Members
  39: 
  40:     public string Greet()
  41:     {
  42:         return “Hellp World”;
  43:     }
  44: 
  45:     #endregion
  46: }
  47: 
  48: 

to be injected by the magic of only this code in the bind time:

   1: CompositionContainer container = new CompositionContainer(resolver);
   2: container.Bind();
   3: var helloWorld = container.TryGetBoundValue<HelloWorld>().Value;

To achieve this, we have to give the container the types and contracts in the format that it needs, and this is should be a cooked & ready to eat thing because since we are not giving the container Imports and exports, we have to tell it what to import and export. To find out what container wanted to do the binding was not as easy as I expected to be, I had to do a lot of reverse engineering. Here, TDD saved my day and helped me to divide my problem space into a distinct two - Provide the instances to Composition Container correctly(1) and Compose the requested objects by using the types that are provided by (1).

We need to write a resolver and a binder to achieve this. ValueResolver needs a repository to use in the resolving process, though it takes the repository as a parameter. This parameter is an ITypeRepository interface in my design which means that one can write their DbConfigRepository that implements this and pass it to the ConfigValueResolver, and expect the resolver to work in the same way. This approach decouples the value resolver from the internals of repository. ITypeRepository interface is defined as follows :

   1: public interface ITypeRepository
   2: {
   3:   /// <summary>
   4:   /// Gets the object meta.
   5:   /// </summary>
   6:   /// <returns>A list of object meta data info. This can be changed to return IEnumerable to enable lazy loading in the future.</returns>
   7:   IList<ObjectMeta> GetObjectMeta();
   8: }

And the implementation for this, that takes the Object Meta Data from configuration repository, in this case the application XML configuration, is as below:

   1: public class XmlTypeRepository : ITypeRepository
   2: {
   3:    #region ITypeRepository Members
   4:     public IList<ObjectMeta> GetObjectMeta()
   5:     {
   6:         XmlBinderConfigurationSection section = ConfigurationManager.GetSection(“xmlBinder”) as XmlBinderConfigurationSection;
   7:         Debug.Assert(section != null);
   8: 
   9:         IList<ObjectMeta> retVal = BuildObjectMetaListFromConfigurationSection(section);
  10: 
  11:         return retVal;
  12:     }
  13:    #endregion
  14: 
  15:     private IList<ObjectMeta> BuildObjectMetaListFromConfigurationSection(XmlBinderConfigurationSection section)
  16:     {
  17:             List<ObjectMeta> retVal = new List<ObjectMeta>();
  18: 
  19:         foreach (XmlBinderObjectElement objectElement in section.Objects)
  20:         {
  21:             ObjectMeta meta = BuildObjectMetaFromConfiguration(objectElement);
  22:             retVal.Add(meta);
  23:         }
  24:         return retVal;
  25:     }
  26: 
  27:     private ObjectMeta BuildObjectMetaFromConfiguration(XmlBinderObjectElement element)
  28:     {
  29:         Debug.Assert(element != null);
  30: 
  31:         ObjectMeta retVal = new ObjectMeta()
  32:         {
  33:              ObjectType = element.Type,
  34:         };
  35:
  36:         foreach (XmlBinderPropertyElement propertyElement in element.PropertyElements)
  37:         {
  38:             retVal.MappingPairs.Add(new TypeMappingPair(element.Type.GetProperty(propertyElement.Name), propertyElement.TypeToMap, propertyElement.Name));
  39:
  40:         }
  41: 
  42:         return retVal;
  43:     }
  44: }

Here, ObjectMeta represents an object’s meta data to be processed further to be meaningful to bind.

image

Figure 2: TypeMappingPair and ObjectMeta entity structures

Now that we have the repository, we can safely build the resolver. The relationship between resolver and a binder is this as far as I could find out: A binder is there for a type, is responsible of it to be build properly. Binder tells to the container for the type “this type exports these, and imports these. Now go build.”. So it is reasonable for a binder to take 3 piece of information: Target type, its imports and its exports. I wrapped them up in a BindingInfo entity whose class diagram is shown below with side by side to ObjectMeta:

image

Figure 3: BindingInfo and ObjectMeta objects

Have you noticed the mismatch between two ? That’s the key point, BindingInfo is what binder (and so container) needs, and ObjectMeta is what we have, and is more intuitive and is there to support POCO model. Now we need to implement the magic on our own to convert from ObjectMeta listo to Binding Info list. I implemented this method called GetBindingInfo() into the resolver. Resolver queries the underlying repository the first time it is asked to do so, and retrieves a set of ObjectMeta from it. GetBindingInfo does the necessary conversion for us to be able to easily create our Binder (XmlBinder in this case).

Following test shows what we expect from the resolver’s GetBindingInfo method, I expect it to be rather self explanatory:

   1: [TestMethod()]
   2: public void should_transform_metadata_format_into_the_needed_format_for_mef()
   3: {
   4:     ITypeRepository rep = new XmlTypeRepository();
   5:     ConfigValueResolver target = new ConfigValueResolver(rep); // TODO: Initialize to an appropriate value
   6:     IList<ObjectMeta> objectsFromRepository = rep.GetObjectMeta(); // get from xml repository
   7:     IList<BindingInfo> actual;
   8:     actual = target.GetBindingInfo();
   9:     Assert.AreEqual(3, actual.Count);
  10:
  11:     // see if types are registered
  12:     Assert.IsTrue(actual.Any<BindingInfo>(bi => bi.TypeToCompose == typeof(ConsoleOutputter)));
  13:     Assert.IsTrue(actual.Any<BindingInfo>(bi => bi.TypeToCompose == typeof(HelloWorld)));
  14:     Assert.IsTrue(actual.Any<BindingInfo>(bi => bi.TypeToCompose == typeof(Greeter)));
  15: 
  16:     // see if infos set properly
  17:     BindingInfo helloWorld = actual.First<BindingInfo>(bi => bi.TypeToCompose == typeof(HelloWorld));
  18:     BindingInfo consoleOutputter = actual.First<BindingInfo>(bi => bi.TypeToCompose == typeof(ConsoleOutputter));
  19:     BindingInfo greeter = actual.First<BindingInfo>(bi => bi.TypeToCompose == typeof(Greeter));
  20: 
  21:     // for parent type
  22:     Assert.IsTrue(helloWorld.ExportsOfTypeToCompose.Count > 0);
  23:     Assert.IsTrue(helloWorld.ExportsOfTypeToCompose.Any<Type>(t => t == typeof(HelloWorld)));
  24: 
  25:     // verify expectations on injection
  26:     Assert.AreEqual(2, helloWorld.ImportsOfTypeToCompose.Count);
  27:     Assert.IsTrue(helloWorld.ImportsOfTypeToCompose.Any<PropertyInfo>(t => t.PropertyType == typeof(IOutputter)));
  28:     Assert.IsTrue(helloWorld.ImportsOfTypeToCompose.Any<PropertyInfo>(t => t.PropertyType == typeof(IGreeter)));
  29: 
  30:     Assert.AreEqual(2, consoleOutputter.ExportsOfTypeToCompose.Count);
  31:     Assert.IsTrue(consoleOutputter.ExportsOfTypeToCompose.Any<Type>(t => t == typeof(ConsoleOutputter)));
  32:     Assert.IsTrue(consoleOutputter.ExportsOfTypeToCompose.Any<Type>(t => t == typeof(IOutputter)));
  33: 
  34:     Assert.AreEqual(2, greeter.ExportsOfTypeToCompose.Count);
  35:     Assert.IsTrue(greeter.ExportsOfTypeToCompose.Any<Type>(t => t == typeof(Greeter)));
  36:     Assert.IsTrue(greeter.ExportsOfTypeToCompose.Any<Type>(t => t == typeof(IGreeter)));
  37: }

As you see, every exporter needs to export themselves and the mutual contract, that’s why I am checking them with 2. To make this test pass, I came up with the following implementation for the resolver and  its over smart GetBindingInfo:

   1: public class ConfigValueResolver : ValueResolver
   2: {
   3:     ITypeRepository Repository
   4:     {
   5:         get;set;
   6:     }
   7: 
   8:     private IList<ObjectMeta> metaList;
   9:     private IList<ObjectMeta> Objects
  10:     {
  11:         get
  12:         {
  13:             if (metaList == null)
  14:             {
  15:                 metaList = Repository.GetObjectMeta();
  16:             }
  17:             return metaList;
  18:         }
  19:     }
  20:
  21: 
  22:     public ConfigValueResolver(ITypeRepository repository)
  23:     {
  24:         this.Repository = repository;
  25:     }
  26: 
  27:     protected override void OnContainerSet()
  28:     {
  29:         base.OnContainerSet();
  30:         ConfigureContainer();
  31:     }
  32: 
  33:     protected override void OnContainerDisposed()
  34:     {
  35:         base.OnContainerDisposed();
  36:     }
  37: 
  38:     public override CompositionResult<IImportInfo> TryResolveToValue(string name, IEnumerable<string> requiredMetadata)
  39:     {
  40:         CompositionResult<ImportInfoCollection> result = TryResolveToValues(name, requiredMetadata);
  41: 
  42:         return new CompositionResult<IImportInfo>(result.Succeeded, result.Issues, result.Value.First());
  43:     }
  44: 
  45:     public override CompositionResult<ImportInfoCollection> TryResolveToValues(string name, IEnumerable<string> requiredMetadata)
  46:     {
  47:         return TryGetContainerLocalImportInfos(name, requiredMetadata);
  48:     }
  49: 
  50:     private void ConfigureContainer()
  51:     {
  52:         // load up the types and add the binder for them
  53: 
  54:         IList<BindingInfo> bindingList = GetBindingInfo();
  55: 
  56:         foreach (var bindingInfo in bindingList)
  57:         {
  58:             this.Container.AddBinder(new XmlBinder(bindingInfo));
  59:         }
  60: 
  61:
  62:     }
  63: 
  64:     public IList<BindingInfo> GetBindingInfo()
  65:     {
  66:         Debug.Assert(Objects != null);
  67: 
  68:         List<BindingInfo> retVal = new List<BindingInfo>();
  69:         foreach (var objectMeta in Objects)
  70:         {
  71:             IList<Type> exports = new List<Type>();
  72:             exports.Add(objectMeta.ObjectType);
  73:             IList<PropertyInfo> imports = new List<PropertyInfo>();
  74:             //TODO:if imported properties are not in the objects list themselves, it means that they arent exporting anything. 
  75:             // So we can add them safely.
  76:             Debug.Assert(objectMeta.MappingPairs != null);
  77: 
  78:             foreach (var propertyToBeInjected in objectMeta.MappingPairs)
  79:             {
  80:                 // mapping pairs themselves should be in the container in order to be considered to bind
  81:                 Debug.Assert(propertyToBeInjected != null);
  82: 
  83:         retVal.Add(new BindingInfo()
  84:         {
  85:             TypeToCompose = propertyToBeInjected.ConcreteImplementation,
  86:             // exports itsself and its contract
  87:             ExportsOfTypeToCompose = new List<Type>()
  88:             {
  89:                     propertyToBeInjected.ConcreteImplementation,
  90:                     propertyToBeInjected.PropertyToInject.PropertyType
  91:             },
  92:             ImportsOfTypeToCompose = new List<PropertyInfo>(), // currently not implemented
  93:             });
  94:
  95:             imports.Add(propertyToBeInjected.PropertyToInject);
  96:          }
  97:
  98:          retVal.Add(new BindingInfo()
  99:          {
 100:            TypeToCompose = objectMeta.ObjectType,
 101:            ExportsOfTypeToCompose = exports,
 102:            ImportsOfTypeToCompose = imports
 103:           });
 104:         }
 105: 
 106:         return retVal;
 107:     }
 108: }

As you see in the implementation, for every binding info I am adding its Binder. This makes Binder’s implementation on this info relatively straightforward but crucial: It extends the ComponentBinder base class and provides the export info, import info and contract names for the composition operation. Here, I am using the relevantType.ToString() like Jason does in the fluent interface example, but the rest approach is a bit different:

   1: /// <summary>
   2: /// Each XML Binder stands for a type to resolve. 
   3: /// </summary>
   4: /// <remarks>No lifetime supported</remarks>
   5: public class XmlBinder : ComponentBinder
   6: {
   7:     private object instance;
   8:     private static object SyncRoot = new object();
   9: 
  10:     public IList<Type> Exports
  11:     {
  12:         get;
  13:         set;
  14:     }
  15: 
  16:     /// <summary>
  17:     /// List of the properties those are determined to be injected. 
  18:     /// Since the binder is a one-use-only object, setter is private and import list can not be changed during the composition
  19:     /// to synch with the current nature of the container/
  20:     /// </summary>
  21:     /// <value>The imports.</value>
  22:     public IList<PropertyInfo> Imports
  23:     {
  24:         get;
  25:         private set;
  26:     }
  27: 
  28:     /// <summary>
  29:         /// Gets or sets the type of the target resolve type.
  30:     /// </summary>
  31:     /// <value>The type of the resolve.</value>
  32:     public Type TargetResolveType
  33:     {
  34:         get;
  35:         private set;
  36:     }
  37: 
  38:     /// <summary>
  39:     /// Gets the current instance.
  40:     /// </summary>
  41:     /// <value>The current instance, it i singleton for the time being.</value>
  42:     private object CurrentInstance
  43:     {
  44:         get
  45:         {
  46:             if (instance == null)
  47:             {
  48:                 lock (SyncRoot)
  49:                 {
  50:                     if (instance == null)
  51:                     {
  52:                         // a really dummy instance, we can pass take these constructors from the taken types in XML repository
  53:                         // if we want to enable constructor injection. Assuming the type resolved has a default constructor for the time being. 
  54:                         instance = TargetResolveType.GetConstructor(new Type[] { }).Invoke(new object[] { });
  55:                     }
  56:                 }
  57:             }
  58:             return instance;
  59:         }
  60:     }
  61: 
  62:     public XmlBinder(BindingInfo bindingInfo)
  63:     {
  64:         this.Exports = bindingInfo.ExportsOfTypeToCompose;
  65:         this.Imports = bindingInfo.ImportsOfTypeToCompose;
  66:         this.TargetResolveType = bindingInfo.TypeToCompose;
  67:     }
  68: 
  69:     /// <summary>
  70:     /// Gets the export names.
  71:     /// </summary>
  72:     /// <value>The export names.</value>
  73:     public override IEnumerable<string> ExportNames
  74:     {
  75:         get
  76:         {
  77:             return Exports.Select<Type, string>(t => t.ToString());
  78:          }
  79:     }
  80: 
  81:     /// <summary>
  82:     /// Gets the import names.
  83:     /// </summary>
  84:     /// <value>The import names.</value>
  85:     public override IEnumerable<string> ImportNames
  86:     {
  87:         get
  88:         {
  89:             return Imports.Select(info => info.PropertyType.ToString());
  90:          }
  91:     }
  92: 
  93:     /// <summary>
  94:     /// Exports this instance.
  95:     /// </summary>
  96:     /// <returns></returns>
  97:     public override CompositionResult Export()
  98:     {
  99:         foreach (var type in Exports)
 100:         {
 101:             AddValueToContainer(type.ToString(), CurrentInstance);
 102:         }
 103: 
 104:         return CompositionResult.SucceededResult;
 105:     }
 106: 
 107:     /// <summary>
 108:     /// Imports the specified changed value names.
 109:     /// </summary>
 110:     /// <param name=”changedValueNames”>The changed value names, not really used.</param>
 111:     /// <returns></returns>
 112:     public override CompositionResult Import(IEnumerable<string> changedValueNames)
 113:     {
 114:             foreach (var info in Imports)
 115:             {
 116:                 CompositionResult<object> component = Container.TryGetBoundValue(info.PropertyType.ToString(), info.PropertyType);
 117:                 // do the injection. Currently assuming that only non-indexed values are to be resolved
 118:                 if (component.Succeeded)
 119:                 {
 120:                     info.SetValue(CurrentInstance, component.Value, null);
 121:                 }
 122:                 else
 123:                 {
 124:                     throw new InvalidOperationException(component.Issues[0].Description, component.Issues[0].Exception);
 125:                 }
 126:
 127:             }
 128: 
 129:             return CompositionResult.SucceededResult;
 130:         }
 131: 
 132:     public override bool Equals(object obj)
 133:     {
 134:         XmlBinder binder = obj as XmlBinder;
 135:         if (binder != null)
 136:         {
 137:             return binder.TargetResolveType == this.TargetResolveType;
 138:         }
 139:         return false;
 140:     }
 141: }

And now we have done all the dirty infrastructure work. Those was all for this test to pass:

   1: [TestMethod]
   2: public void should_print_hello_world_to_console()
   3: {
   4:     ConfigValueResolver resolver = new ConfigValueResolver(new XmlTypeRepository());
   5:     //IOutputter outputter = null;
   6:     //IGreeter greeter = null;
   7:     HelloWorld helloWorld = null;
   8:     try
   9:     {
  10:         CompositionContainer container = new CompositionContainer(resolver);
  11:         container.Bind();
  12:         helloWorld = container.TryGetBoundValue<HelloWorld>().Value;
  13:     }
  14:     catch (CompositionException ex)
  15:     {
  16:         foreach (CompositionIssue issue in ex.Issues)
  17:         {
  18:             Console.WriteLine(“issue = {0}”, issue.ToString());
  19:         }
  20:     }
  21:
  22:     Assert.IsNotNull(helloWorld);
  23:     Assert.IsNotNull(helloWorld.Outputter);
  24:     Assert.IsNotNull(helloWorld.Greeter);
  25:
  26:     helloWorld.SayIt();
  27: }

Ignore the try catch, that’s because of the *damn* error handling mechanism of MEF depending on the issues and not providing a concatenated representation of the error message. At the end, dependencies are injected, “hello world” is printed to the test console, and the world is a better place to live. Thank you all !

You can download the sources from here, I have %80 test coverage now. Any comments, criticisms, cheques with a lot of trailing zeros well appreciated as always.
kick it on DotNetKicks.com

Share it on: These icons link to social bookmarking sites where readers can share and discover new web pages.
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google
  • Blogosphere News
  • e-mail
  • YahooMyWeb
  • DotNetKicks
  • DZone

3 Responses so far »

  1. 1

    Bookmarks about Thumbnail said,

    July 31, 2008 @ 5:17 pm

    […] - bookmarked by 2 members originally found by WiLLvolution on July 16, 2008 Building a Configuration Binder for MEF with POCO Support […]

  2. 2

    Ed McPadden said,

    September 21, 2008 @ 9:12 pm

    Hey Sidar,

    Great posts on MEF. I am just starting to grok MEF and try to get a handle on how it differs from IOC and DI and if its meant to compliment these or replace these technologies. Since there is limited info posts like yours are very valuable. Thanks!

    …Ed (twitter: @emcpadden)

  3. 3

    Sidar Ok said,

    September 22, 2008 @ 11:49 am

    Hi Ed,

    Thanks a lot. There is a lot of misunderstanding going around MEF, that we addressed while talking to Glenn Block (the pm in the team) and thus creating a confusion.

    I am willing to write a blog post to cover those points all up. Thanks for keeping an eye on it.

Comment RSS · TrackBack URI

Say your words

You must be logged in to post a comment.