Question

I have a interface as follows:

namespace Contract
{
    [InheritedExport(typeof(ITransform))]
    public interface ITransform
    {
       string process(string name);
    }
}

Now, I have two classes:

using Contract;
namespace ProjectA
{
    public class ProjectA:ITransform
    {

        public string process(string name)
        {
            ProjectXYZ.ProjectXYZ obj = new ProjectXYZ.ProjectXYZ();
            return obj.process("Project A calling");
        }
    }
}

And

using Contract;
namespace ProjectB
{
    public class Datawarehouse:ITransform
    {

        public string process(string name)
        {
            ProjectXYZ.ProjectXYZ obj = new ProjectXYZ.ProjectXYZ();
            return obj.process("Project B calling");
        }
    }
}

I have another project ProjectXYZ(auto generated by third party tool(Altova Mapforce 2012 SP1)).

For ProjectA customized auto generated code from altova mapforce 2012:

namespace ProjectXYZ
{
    public class ProjectXYZ
    {
        public string process(string name)
        {
            name = "This is for Project A :: "+name;
            return name;
        }
    }
}

For ProjectB customized auto generated code from altova mapforce 2012:

namespace ProjectXYZ
{
    public class ProjectXYZ
    {
        public string process(string name)
        {
            string n = "This is for Project B ::"+Result();
            return n;
        }
        public string Result()
        { 
            int op1 = 1;
            int op2 = op1+3;
            return op2.ToString();
        }
    }
}

Third party auto generated codes are not exported, But its binaries I used as reference of ProjectA.Transform and ProjectB.Transform.So I am using [DirectoryCatalog] for loading all binaries of ProjectA.Transform and ProjectB.Transform in CompositionContainer of MEF. Each project is compiled and their binaries(build output) location is given as an input to DirectoryCatalog

for further composition.

using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition;
namespace AppConsole
{       
    class Program
    {
        static void Main(string[] args)
        {
            Program p = new Program();
            p.Run();
        }
        public void Run() {

            List<string> extensionPath = new List<string>();
            //Change the extension Path
            extensionPath.Add(@"E:\MEF\MEFForProjectA\ProjectA\bin\Debug");
            extensionPath.Add(@"E:\MEF\MEFForProjectB\ProjectB\bin\Debug");
            foreach (var extension in extensionPath)
            {
                ITransform transform = GetExtension(extension);
                Console.WriteLine("Extension Loaded :{0}", transform.process(extension));

            }
            Console.ReadLine();
        }
        private ITransform GetExtension(string extensionPath)
        {            
            IEnumerable<ITransform> extensions = null;          
            try
            {                
                AggregateCatalog catalog = new AggregateCatalog();
                catalog.Catalogs.Add(new DirectoryCatalog(extensionPath));      
                CompositionContainer container = new CompositionContainer(catalog);
                container.ComposeParts(catalog);
                extensions = container.GetExportedValues<ITransform>();
                return extensions.FirstOrDefault();
            }
            catch (Exception ex) { Console.WriteLine(ex.Message); }
            return extensions.FirstOrDefault(); 
        }        
    }
}

ProjectA.Transform uses ProjectXYZ.ClassA, whereas ProjectB.Transform uses ProjectXYZ.ClassB from another implementation of ProjectXYZ. The implementation and classes

of ProjectXYZ varies across for different implementation of ITransform. The classes in ProjectXYZ are automatically generated through some third-party tools, which I

need to use directly. So, I cannot make any changes to ProjectXYZ.

So, when first time MEF loads ProjectA.Transform, it also loads ProjectXYZ to be used as a reference for ProjectA. When ProjectB.Transform is getting loaded/exported,

then as ProjectXYZ Assemblies being already in MEF memory, it uses the ProjectXYZ Assemblies reference available from "C:\ProjectDemo\ProjectA.Transform\Bin\Debug". Thus, when ProjectB.Transform is executing, it searches for ProjectXYZ Assemblies from "C:\ProjectDemo\ProjectB.Transform\Bin\Debug", which it does not gets as MEF has load ProjectXYZ Assemblies reference available in "C:\ProjectDemo\ProjectA.Transform\Bin\Debug".

How to resolve this problem. The MEF loads the parts correctly, but it does not load the supporting dll's references in a desired manner. I have also tried

PartCreationPolicy attribute, but the results are same.

Expected Result :
         Extension Loaded :This is for Project A :: Project A calling
         Extension Loaded :This is for Project B :: 4

Actual Result: 
         Extension Loaded :This is for Project A :: Project A calling
         Extension Loaded :This is for Project A :: Project B calling
Was it helpful?

Solution

It not a MEF problem. The problem is in the loading model of .NET. (or better the way you're objects are loaded by .net)

When MEF loads it returns the correct objects. But when looking for class ProjectXYZ when projectB is loaded there is already a ProjectXYZ dll loaded with the correct assembly name projectB is referring to. And the loader the dll actually referenced by projectB is not loaded.

You can try it you're self just by changing the sequence of the added folders into

extensionPath.Add(@"E:\MEF\MEFForProjectB\ProjectB\bin\Debug"); extensionPath.Add(@"E:\MEF\MEFForProjectA\ProjectA\bin\Debug");

Then you get

Extension Loaded :This is for Project B :: 4 Extension Loaded :This is for Project B :: 4

The solution to you're problem is renaming the assembly. When all ProjectXYZ assemblies have there own filename you get the expected result.

Regards, Piet

OTHER TIPS

I think this would be a case for metadata. MEF can't determine which instance of ITransform to use, because you are always using GetExportedValues<ITransform>().FirstOrDefault(). If you supplied metadata to your parts, e.g.:

Firstly, define a metadata interface:

public interface ITransformMetadata
{
    string Name { get; }
}

And a custom export attribute:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false), MetadataAttribute]
public class ExportTransformAttribute : ExportAttribute, ITransformMetadata
{  
    public ExportTransformAttribute(string name)
        : base(typeof(ITransform))
    {
        Name = name;
    }

    public string Name { get; set; }
}

You can then start enriching your exports with additional metadata, which you can later query, e.g.:

[ExportTransform("ClassB")]
public class ClassBTransform : ITransform { }

And with a query:

var part = container.GetExports<ITransform, ITransformMetadata>()
    .Where(e => e.Metadata.Name.Equals("value"))
    .FirstOrDefault();
return part.Value;

edit: when a type is exported, a special piece of metadata is provided, called an ExportTypeIdentity, which uses the namespace + name of the exported type.

In your code, you have two parts on two assemblies, with the same namespace and name. ProjectXYZ.ProjectXYZ. Combining that with your FirstOrDefault is likely to be your problem.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top