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

4

6

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?

Antigorite answered 30/7, 2013 at 10:56 Comment(6)
What exception is thrown? You also might want to include the output of mefxCommendation
You may not want to do this... but if it is safe in your code base, to make the property SomeService public, and then change the export: compositionContainer.ComposeExportedValue(sessionModel.SomeService); var someService = CompositionContainer.GetExportedValue<ISomeService>(); someService.DoSomething();Hemocyte
@Hemocyte To make it public is no problem. I just did that. But I don't want to publish each and every service that way. That doesn't look neat, does it? (I assume you consider it that way yourself.) I thought I cannot be the only one having this problem and there must be a solution implemented in MEF which just I don't know.Antigorite
Doesn't look neat to publish everything, you are right. However, consider that a protected object is protected for a reason... which means the only the class itself and subclasses should have access to it. To use it somewhere else requires an accessor anyway... so it really depends what each of your services are doing. I digress, in the end, there could be a way to do exactly what you need to do, I am just not aware of it.Hemocyte
Please change the export to Import on the property and Add Export Attribute to the Class SomeService with the ContractInfo.Hydraulics
@AmitBagga That's not that easy. Let me explain. These services are created by Spring.NET. What does Spring.NET do? It gives me transaction management by encapsulating my services in runtime generated types &ndash; Spring.NET's CompositionAopProxy (NHibernate in background, all I have to do is put a [Transaction] attribute at a method involving the database). These are generated in a way that they are implementing the interfaces, but I cannot instantiate them. (Yes, I come from the Java world. It's quite standard to do it this way there, more or less, just using Spring and Hibernate.)Antigorite
A
1

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;
    }
}
Antigorite answered 4/9, 2013 at 10:8 Comment(4)
This is too much of effort for a small functionality. But if you think this is the best then yes. The solution is not Abstract and your SessionModel is physical know before hand; which defeats the purpose of MEF.Hydraulics
@AmitBagga Well, this solution can be generalized by passing any object to the WhateverItWillBeCalledExportProvider. So it can even find its way into MEF itself.Antigorite
Yes I do agree with what you said, but pushing something on the stack that can be achieved with simplicity is a bad design. You have made things complex to achieve a simple property access.Hydraulics
@AmitBagga But I have eliminated the dependency to a central class. I am using MEF for a reason: I have plug-ins on the "client-side" of my system. They all had a dependency to SessionModel just for getting the service to communicate with on the "server-side". Now I am one step further to just depending on the service interface of the server-side. (I am not quite there because of central image resources and such things). In my eyes it was worth the effort. It took me roughly four hours to get this to work without any experience in C# reflection and the internals of MEF.Antigorite
C
0

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.

Cressida answered 6/8, 2013 at 21:20 Comment(5)
Hm, but as far as I know in general MEF does not have a problem with managing more than one instance of a type. I'd just have to provide different contract names. So I cannot imagine how this is the problem why MEF does not consider the exports of my SessionModel. In my case all the services are quasi-singletons. I am following the idea of "just create one", because I consider "Singleton" to be an anti-pattern.Antigorite
Additional comment to my point of view regarding the Singleton pattern: Perhaps that's because I come from the Java world and my years as a C# developer don't suffice to confince me otherwise.Antigorite
I have to admit that I've never heard Singleton described as an anti-pattern before. Regardless, you could just make everything static, since it looks like you would not ever want or need a second instance of SessionModel.Cressida
Actually...I need to confess that I missed where you were explicitly adding the SessionModel instance to the container as a composable part. It seems to me that that you're giving MEF enough information to make it work, at least in theory. I have never attempted to supply an export using an instance property: I have always used static properties to do this sort of thing.Cressida
At the moment I only have one SessionModel (and a three-tier architecture in a fat client), but I wanted to implement as much MVVM as possible. In that logic the SessionModel is my connection to the "server" (at the moment: services using the same database connection). In future the application might be able to connect to more than one server, whilst the name.Antigorite
H
0
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();
        }
    }
}
Hydraulics answered 8/8, 2013 at 23:27 Comment(9)
This solution does not incorporate MEF at all, does it?Antigorite
Don't take me wrong is MEF magic? No!! its all about registering the Types with the container and get getting the value of a type by using extension methods. If I know the type i am looking for on an object then why register that with the container and get the value. All you are trying to achieve is to make it more generic and abstract. The example above does that but through a custom extension method. I am not a Master in MEF but could not figure out how can i get exported value of a property if the type is not registered with the container.Hydraulics
I would suggest you a build an interface with the a property type of ISomeService and implement the same with the various SessionModel class and convert your session model object to the Interface Type and you will have access to the property you are looking on the object. Don't you think this is more simple and clean approach.Hydraulics
I would like to use some kind od dependency injection method. I don't plan to get the exported values directly. I'll rather use some Compose... method. I think I will implement an 'ExportProvider' and read the export attributes of the properties myself.Antigorite
I have Updated my code. I hope this is what you are trying to achieve.Hydraulics
The services are instantiated by Spring.NET. I cannot create instances of them. (Also they don't have a parameterless constuctor.) I must admit when I saw your last changes to your answer I was a bit disappointed, because now I can use less code of it.Antigorite
Somehow I am in trouble now. On one hand, you have invested a serious amount of interest and work in that question, Amit Bagga. On the other hand this doesn't help me much. This is what you also suggested in this comment, and I commented on that comment.Antigorite
@Antigorite Investment in learning will always pay; if not today.Hydraulics
@Antigorite I would like to solve this problem of yours. Its quite challenging and you have a real scenario to apply solution. If possible. I would like to discuss things in more detail.Hydraulics
R
0

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.

Resolved answered 21/8, 2013 at 7:25 Comment(2)
Thank you Panos. I assume your reasons for keeping the first solution "to a minimum" are the same as mine: It is forgotton to easily and doesn't look neat. The second solution doesn't work, because the services are in fact created by Spring.NET. The code above is just an SSCCE.Antigorite
@Antigorite The first solution is not suggested as a best practice for MEF. In the discussions section of MEF's Codeplex site (mef.codeplex.com/discussions) and in SO you will find answers by members of the MEF team that suggest the usage of catalogs instead of manually adding/removing parts. Of course there are cases were you still have to do these things and that's the reason why they have included them in the library. Your problem is probably more fitting to the manual addition/management of parts.Resolved

© 2022 - 2024 — McMap. All rights reserved.