Search code examples
c#mef

How to export parts from an object not instantiated by the MEF container


Introduction

Class SessionModel is a service locator providing several services (I am going to elaborate my system architecture in the future, but for now I need to do it that way).

Code

I edited the following code part to be a Short, Self Contained, Correct (Compilable), Example (SSCCE):

using System;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;

namespace ConsoleApplication1
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            var sessionModel = new SessionModel(3);

            // first case (see text down below):
            var compositionContainer = new CompositionContainer();

            // second case (see text down below):
            //var typeCatalog = new TypeCatalog(typeof (SessionModel));
            //var compositionContainer = new CompositionContainer(typeCatalog);

            compositionContainer.ComposeExportedValue(sessionModel);

            var someService = compositionContainer.GetExportedValue<ISomeService>();
            someService.DoSomething();
        }
    }

    public class SessionModel
    {
        private int AValue { get; set; }

        [Export]
        public ISomeService SomeService { get; private set; }

        public SessionModel(int aValue)
        {
            AValue = aValue;
            // of course, there is much more to do here in reality:
            SomeService = new SomeService();
        }
    }

    public interface ISomeService
    {
        void DoSomething();
    }

    public class SomeService : ISomeService
    {
        public void DoSomething()
        {
            Console.WriteLine("DoSomething called");
        }
    }
}

Problem

I would like MEF to consider the parts (i.e. SomeService) exported by the service locator when composing other parts, but unfortunately this does not work.

First Case

When I try to get the exported value for ISomeService there is a System.ComponentModel.Composition.ImportCardinalityMismatchException telling me there are no exports with this contract name and required type identity (ConsoleApplication1.ISomeService).

Second Case

If I create the CompositionContainer using the TypeCatalog the exception is slightly different. It is a System.ComponentModel.Composition.CompositionException telling me MEF doesn't find a way to create a ConsoleApplication1.SessionModel (which is right and the reason why I am doing it myself).

Additional Information

mefx says for both cases:

[Part] ConsoleApplication1.SessionModel from: DirectoryCatalog (Path=".")
  [Export] ConsoleApplication1.SessionModel.SomeService (ContractName="ConsoleApplication1.ISomeService")

[Part] ConsoleApplication1.SessionModel from: AssemblyCatalog (Assembly="ConsoleApplication1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")
  [Export] ConsoleApplication1.SessionModel.SomeService (ContractName="ConsoleApplication1.ISomeService")

What do I have to do? Is this possible with MEF or do I have to use Unity or StructureMap, or something else? Can this be done implementing an ExportProvider?


Solution

  • OK, that's how I did it:

    I implemented my own SessionModelExportProvider finding exports in my SessionModel (see code below). Class SessionModelExport is just for holding the export data and – instead of creating an instance of a service – returning the value of the property of the SessionModel.

    public class SessionModelExportProvider : ExportProvider
    {
        private List<Export> Exports { get; set; }
    
        public SessionModelExportProvider(SessionModel sessionModel)
        {
            // get all the properties of the session model having an Export attribute
            var typeOfSessionModel = typeof (SessionModel);
            PropertyInfo[] properties = typeOfSessionModel.GetProperties();
            var propertiesHavingAnExportAttribute =
                from p in properties
                let exportAttributes = p.GetCustomAttributes(typeof (ExportAttribute), false)
                where exportAttributes.Length > 0
                select new
                           {
                               PropertyInfo = p,
                               ExportAttributes = exportAttributes
                           };
    
            // creating Export objects for each export
            var exports = new List<Export>();
            foreach (var propertyHavingAnExportAttribute in propertiesHavingAnExportAttribute)
            {
                var propertyInfo = propertyHavingAnExportAttribute.PropertyInfo;
                foreach (ExportAttribute exportAttribute in propertyHavingAnExportAttribute.ExportAttributes)
                {
                    string contractName = exportAttribute.ContractName;
                    if (string.IsNullOrEmpty(contractName))
                    {
                        Type contractType = exportAttribute.ContractType ?? propertyInfo.PropertyType;
                        contractName = contractType.FullName;
                    }
    
                    var metadata = new Dictionary<string, object>
                                       {
                                           {CompositionConstants.ExportTypeIdentityMetadataName, contractName},
                                           {CompositionConstants.PartCreationPolicyMetadataName, CreationPolicy.Shared}
                                       };
                    var exportDefinition = new ExportDefinition(contractName, metadata);
                    var export = new SessionModelExport(sessionModel, propertyInfo, exportDefinition);
                    exports.Add(export);
                }
            }
    
            Exports = exports;
        }
    
        protected override IEnumerable<Export> GetExportsCore(ImportDefinition definition,
                                                              AtomicComposition atomicComposition)
        {
            return Exports.Where(e => definition.IsConstraintSatisfiedBy(e.Definition));
        }
    }
    
    public class SessionModelExport : Export
    {
        private readonly SessionModel sessionModel;
        private readonly PropertyInfo propertyInfo;
        private readonly ExportDefinition definition;
    
        public SessionModelExport(SessionModel sessionModel, PropertyInfo propertyInfo, ExportDefinition definition)
        {
            this.sessionModel = sessionModel;
            this.propertyInfo = propertyInfo;
            this.definition = definition;
        }
    
        public override ExportDefinition Definition
        {
            get { return definition; }
        }
    
        protected override object GetExportedValueCore()
        {
            var value = propertyInfo.GetValue(sessionModel, null);
            return value;
        }
    }