oddparity
oddparity

Reputation: 436

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?

Upvotes: 6

Views: 3823

Answers (4)

oddparity
oddparity

Reputation: 436

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;
    }
}

Upvotes: 1

Panos Rontogiannis
Panos Rontogiannis

Reputation: 4172

First case: By passing sessionModel to ComposeExportedValue you add a part of type SessionModel and not of ISomeService. To make this case work you nee to pass the service to ComposeExportedValue.

compositionContainer.ComposeExportedValue(sessionModel.SomeService);

Second case: In this case you leave the creation of parts to the container. The container can create new parts if there is either a parameter-less constructor or a constructor with parameters decorated with the ImportingConstructorAttribute. This most probably means that you will need to change your design a bit.

Personally I would go with the first case, but try to keep this to a minimum. After all the normal (and suggested) usage of MEF is letting the container create and handle parts.

Upvotes: 0

Amit Bagga
Amit Bagga

Reputation: 648

using System;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.ReflectionModel;
using System.Reflection;
using System.Linq;

namespace ConsoleApplication1
{
    internal class Program
    {
        private static void Main(string[] args)
        {

            var catalogs = new AggregateCatalog();
            var catalog = new System.ComponentModel.Composition.Hosting.AssemblyCatalog(Assembly.GetExecutingAssembly());
            catalogs.Catalogs.Add(catalog);
            var sessionModel = new SessionModel(3);
            var container = new CompositionContainer(catalog); 
            ISomeService someService = container.GetExportedValueOrDefault<ISomeService>(sessionModel.cname);
            if (someService != null)
            {
                someService.DoSomething();
            }
        }
    }

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

        //[Import("One",typeof(ISomeService))]
        //public ISomeService SomeService { get; private set; }

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

        public string cname { get { return "One"; } }
    }

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

        //[Import("Two",typeof(ISomeService))]
        //public ISomeService SomeService { get; private set; }

        public SessionModel1(int aValue)
        {
            AValue = aValue;
        }
        public string cname { get { return "Two"; } }

    }

    public interface ISomeService
    {
        void DoSomething();
    }

    [Export("One",typeof(ISomeService))]
    public class SomeService : ISomeService
    {
        public SomeService()
        {
            Console.WriteLine("Some Service Called");
        }
        public void DoSomething()
        {
            Console.WriteLine("DoSomething called");
            Console.ReadKey();
        }
    }

    [Export("Two",typeof(ISomeService))]
    public class SomeService1 : ISomeService
    {
         public SomeService1()
        {
            Console.WriteLine("Some Service1 Called");
        }
        public void DoSomething()
        {
            Console.WriteLine("DoSomething called 1");
            Console.ReadKey();
        }
    }
}

Upvotes: 0

DLCross
DLCross

Reputation: 704

The problem is that the SomeService is an instance property. You could have several SessionModel objects in your system, and MEF would have no way of knowing which SessionModel is returning the ISomeService instance that is supposed to be matched to an import.

Instead, just make SessionModel a static class and SomeService a static property. Alternatively, make SessionModel a singleton. The SomeService property would still be static, but would export the service from the one-and-only instance of SessionModel.

Upvotes: 0

Related Questions