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

Introduction

The SessionModel class is a service locator that provides several services (I will develop my system architecture in the future, but now I need to do it that way).

Code

I edited the following part of the code as 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 the MEF to consider the details (i.e. SomeService ) exported by the service locator when linking the 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 saying that there is no export with this contract name and identifier of the required type ( ConsoleApplication1.ISomeService ).

Second case

If I create a CompositionContainer using TypeCatalog , the exception is a little different. This System.ComponentModel.Composition.CompositionException tells me that MEF does not find a way to create ConsoleApplication1.SessionModel (this is correct and the reason I do 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 should I do? Is this possible using MEF or do I need to use Unity or StructureMap or something else? Can this be done with ExportProvider ?

+6
source share
4 answers

OK how I did it:

I implemented my own SessionModelExportProvider export search in my SessionModel (see code below). The SessionModelExport class SessionModelExport intended only for storing export data and, instead of creating an instance of the service, returning the value of the SessionModel property.

 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; } } 
+1
source

The problem is that SomeService is an instance property. You may have multiple SessionModel objects on your system, and the MEF will not know which SessionModel returns an ISomeService instance that must be mapped to the import.

Instead, just make SessionModel a static class and SomeService a static property. Alternatively, make SessionModel a singleton. The SomeService property will still be static, but only one will be exported from the SessionModel instance.

0
source
 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(); } } } 
0
source

The first case : by passing sessionModel to ComposeExportedValue , you add part of the sessionModel type, and not from ISomeService . For this case to work, you must pass the ComposeExportedValue service.

 compositionContainer.ComposeExportedValue(sessionModel.SomeService); 

The second case . In this case, you leave the creation of parts in the container. A container can create new parts if there is either a constructor without parameters or a constructor with parameters decorated with ImportingConstructorAttribute . This most likely means that you need to change your design a bit.

Personally, I would go with the first thing, but I will try to minimize this. In the end, the normal (and proposed) use of MEF allows the container to create and process parts.

0
source

Source: https://habr.com/ru/post/950616/


All Articles