Search code examples
c#unity-containermefwindows-phone-8.1

Use Unity's Resolve for a with MEF filled Lazy List


I really like MEF for export and import but for DI I prefer Unity. That's the problem I'm having at the moment while trying to resolve a lazy list with Unity.

I import a List of ViewModelBases:

[ImportMany]
public IEnumerable<Lazy<ViewModelBase, ViewModelMetadata>> ViewModelsLazy { get; set;}

This works fine, but when objects get initialized I want to resolve their constructor paramters with Unity:

ViewModelsLazy.Single(v => v.Metadata.Name.Equals(viewModel)).Value

Something like:

unityContainer.Resolve<ViewModelsLazy.Single(v => v.Metadata.Name.Equals(viewModel)).Value>();

would be nice.

I know that MEF provides Constructor-Injection with [ImportingConstructor] but as already mentioned I prefer Unity for DI and usually use MEF only for export/import.


Solution

  • You can always read the catalog to get the types that export as a given type.

    This code won't read from the [ImportMany] but instead of the ComposeParts on the class you could assign the IEnumerable.

    Also MEF metadata is ducked typed and using this the metadata would actually need to implement the interface. Looks like you have a class called ViewModelMetadata and it would need to be an interface instead.

    public static class MEFExtensions
    {
        public static IEnumerable<Lazy<T, M>> GetExportsResolved<T, M>(this CompositionContainer mefContainer,
                                                                       IUnityContainer unityContainer) 
            where T: class where M: class
        {
            // wrap the resolve around unity resolve then change type to T
            return mefContainer.GetExportTypesWithMetadata<T, M>()
                               .Select(kv => new Lazy<T, M>(() => unityContainer.Resolve(kv.Key) as T, kv.Value));
        }
    
        public static IEnumerable<KeyValuePair<Type, M>> GetExportTypesWithMetadata<T, M>(
            this CompositionContainer mefcontainer)
            where T : class where M : class
        {
    
            // need to examine each type to see if they have the correct export attribute and metadata
            foreach (var type in mefcontainer.GetExportTypes<T>())
            {
                // should just be one if more than one will throw exception
                // metadata or export attribute has to implement the interface
                var metadataAttribute =
                    type.GetCustomAttributes()
                        .SelectMany(
                            a =>
                            a.GetType()
                             .GetCustomAttributes()
                             .OfType<MetadataAttributeAttribute>()
                             .Concat<Attribute>(new[] { a }.OfType<ExportAttribute>()))
                        .OfType<M>().SingleOrDefault();
    
                // if we found the correct metadata
                if (metadataAttribute != null)
                {
                    // return the lazy factory
                    yield return new KeyValuePair<Type, M>(type, metadataAttribute);
                }
            }
        }
    
        //Idea from http://www.codewrecks.com/blog/index.php/2012/05/08/getting-the-list-of-type-associated-to-a-given-export-in-mef/
        public static IEnumerable<Type> GetExportTypes<T>(this CompositionContainer mefContainer)
            where T : class
        {
            // look in the mef catalog to grab out all the types that are of type T
            return mefContainer.Catalog.Parts.Where(part => part.ExportDefinitions
                                                                .Any(
                                                                    def =>
                                                                    def.Metadata.ContainsKey(
                                                                        "ExportTypeIdentity") &&
                                                                    def.Metadata["ExportTypeIdentity"]
                                                                        .Equals(
                                                                            typeof (T).FullName)))
                               .AsEnumerable()
                               .Select(part => ReflectionModelServices.GetPartType(part).Value);
        }
    }
    

    It also looks like you are trying to use this as a unity container and it would be better to just use a unit container. We currently use MEF to find all the registrations - which are export attributes with metadata of how they should be register. Then do something similar as above to fill the unity container instead of a property.

    You would use it like

    ViewModelsLazy = mefContainer.GetExportsResolved<ViewModelBase, IViewModelMetadata>(unityContainer);
    

    Again might be better to just use a unity container and do something like

    var regs = mefContainer.GetExportTypesWithMetadata<ViewModelBase, IViewModelMetadata>();
    foreach (var reg in regs)
    {
        unityContainer.RegisterType(typeof (ViewModelBase), reg.Key, reg.Value.Name);
    }
    

    When you want a class resolved you would go to unity and do a resolve passing in the name as the contract name.