How can I use MEF to manage interdependent modules?

I found this question difficult to express (especially in the form of a title), so please bear with me.

I have an application that I am constantly changing to do different things. It looks like MEF can be a good way to manage various features. Generally speaking, there are three sections of the application that form a pipeline like:

  • Acquisition
  • Conversion
  • Expression

In this simplest form, I can express each of these stages as an interface ( IAcquisition , etc.). Problems arise when I want to use acquisition components that provide richer data than standard ones. I want to create modules that use this richer data, but I cannot rely on it.

I could, of course, add all the data to the interface specification. I could work with weaker data sources by throwing an exception or returning a null value. It seems long from the ideal.

I would prefer to do MEF binding in three steps, so that the modules are offered to the user only if they are compatible with those that were previously selected.

So my question is: Can I specify metadata that limits the set of available import data?

Example:

Acquision1 only offers BasicData p>

Acquision2 offers BasicData and AdvancedData p>

Transformation1 requires BasicData p>

Transformation2 requires BasicData and AdvancedData p>

First, the data acquisition module is selected.

If Acquisition1 is selected, do not offer Transformation 2, otherwise offer both.

Is it possible? If so, how?

+6
source share
1 answer

Your question suggests this structure:

 public class BasicData { public string Basic { get; set; } // example data } public class AdvancedData : BasicData { public string Advanced { get; set; } // example data } 

You now have your components for acquisition, transformation, and expression. You want to deal with different types of data, so they are general in nature:

 public interface IAcquisition<out TDataKind> { TDataKind Acquire(); } public interface ITransformation<TDataKind> { TDataKind Transform(TDataKind data); } public interface IExpression<in TDataKind> { void Express(TDataKind data); } 

And now you want to build a pipeline from them that looks like this:

 IExpression.Express(ITransformation.Transform(IAcquisition.Acquire)); 

So, let's start building the pipeline builder:

 using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; using System.ComponentModel.Composition.Primitives; using System.Linq; using System.Linq.Expressions; // namespace ... public static class PipelineBuidler { private static readonly string AcquisitionIdentity = AttributedModelServices.GetTypeIdentity(typeof(IAcquisition<>)); private static readonly string TransformationIdentity = AttributedModelServices.GetTypeIdentity(typeof(ITransformation<>)); private static readonly string ExpressionIdentity = AttributedModelServices.GetTypeIdentity(typeof(IExpression<>)); public static Action BuildPipeline(ComposablePartCatalog catalog, Func<IEnumerable<string>, int> acquisitionSelector, Func<IEnumerable<string>, int> transformationSelector, Func<IEnumerable<string>, int> expressionSelector) { var container = new CompositionContainer(catalog); 

The class contains identifiers of type MEF for three contract interfaces. We will need this later to determine the correct export. Our BuildPipeline method returns Action . This will be a pipeline, so we can just do pipeline() . ComposablePartCatalog and three Func required (for export selection). This way we can save all the dirty work inside this class. Then we start by creating a CompositionContainer .

Now we need to build ImportDefinition s, first for the receive component:

  var aImportDef = new ImportDefinition(def => (def.ContractName == AcquisitionIdentity), null, ImportCardinality.ZeroOrMore, true, false); 

This ImportDefinition simply filters out the entire export of the IAcquisition<> interface. Now we can pass it to the container:

  var aExports = container.GetExports(aImportDef).ToArray(); 

aExports now contains all IAcquisition<> exports in the directory. So run the selector:

  var selectedAExport = aExports[acquisitionSelector(aExports.Select(export => export.Metadata["Name"] as string))]; 

And we have our acquisition component:

  var acquisition = selectedAExport.Value; var acquisitionDataKind = (Type)selectedAExport.Metadata["DataKind"]; 

Now we will do the same for the transform and expression components, but with a slight difference: ImportDefinition will make sure that each component can process the output of the previous component.

  var tImportDef = new ImportDefinition(def => (def.ContractName == TransformationIdentity) && ((Type)def.Metadata["DataKind"]).IsAssignableFrom(acquisitionDataKind), null, ImportCardinality.ZeroOrMore, true, false); var tExports = container.GetExports(tImportDef).ToArray(); var selectedTExport = tExports[transformationSelector(tExports.Select(export => export.Metadata["Name"] as string))]; var transformation = selectedTExport.Value; var transformationDataKind = (Type)selectedTExport.Metadata["DataKind"]; var eImportDef = new ImportDefinition(def => (def.ContractName == ExpressionIdentity) && ((Type)def.Metadata["DataKind"]).IsAssignableFrom(transformationDataKind), null, ImportCardinality.ZeroOrMore, true, false); var eExports = container.GetExports(eImportDef).ToArray(); var selectedEExport = eExports[expressionSelector(eExports.Select(export => export.Metadata["Name"] as string))]; var expression = selectedEExport.Value; var expressionDataKind = (Type)selectedEExport.Metadata["DataKind"]; 

And now we can relate all this in the expression tree:

  var acquired = Expression.Call(Expression.Constant(acquisition), typeof(IAcquisition<>).MakeGenericType(acquisitionDataKind).GetMethod("Acquire")); var transformed = Expression.Call(Expression.Constant(transformation), typeof(ITransformation<>).MakeGenericType(transformationDataKind).GetMethod("Transform"), acquired); var expressed = Expression.Call(Expression.Constant(expression), typeof(IExpression<>).MakeGenericType(expressionDataKind).GetMethod("Express"), transformed); return Expression.Lambda<Action>(expressed).Compile(); } } 

And this! A simple sample application would look like this:

 [Export(typeof(IAcquisition<>))] [ExportMetadata("DataKind", typeof(BasicData))] [ExportMetadata("Name", "Basic acquisition")] public class Acquisition1 : IAcquisition<BasicData> { public BasicData Acquire() { return new BasicData { Basic = "Acquisition1" }; } } [Export(typeof(IAcquisition<>))] [ExportMetadata("DataKind", typeof(AdvancedData))] [ExportMetadata("Name", "Advanced acquisition")] public class Acquisition2 : IAcquisition<AdvancedData> { public AdvancedData Acquire() { return new AdvancedData { Advanced = "Acquisition2A", Basic = "Acquisition2B" }; } } [Export(typeof(ITransformation<>))] [ExportMetadata("DataKind", typeof(BasicData))] [ExportMetadata("Name", "Basic transformation")] public class Transformation1 : ITransformation<BasicData> { public BasicData Transform(BasicData data) { data.Basic += " - Transformed1"; return data; } } [Export(typeof(ITransformation<>))] [ExportMetadata("DataKind", typeof(AdvancedData))] [ExportMetadata("Name", "Advanced transformation")] public class Transformation2 : ITransformation<AdvancedData> { public AdvancedData Transform(AdvancedData data) { data.Basic += " - Transformed2"; data.Advanced += " - Transformed2"; return data; } } [Export(typeof(IExpression<>))] [ExportMetadata("DataKind", typeof(BasicData))] [ExportMetadata("Name", "Basic expression")] public class Expression1 : IExpression<BasicData> { public void Express(BasicData data) { Console.WriteLine("Expression1: {0}", data.Basic); } } [Export(typeof(IExpression<>))] [ExportMetadata("DataKind", typeof(AdvancedData))] [ExportMetadata("Name", "Advanced expression")] public class Expression2 : IExpression<AdvancedData> { public void Express(AdvancedData data) { Console.WriteLine("Expression2: ({0}) - ({1})", data.Basic, data.Advanced); } } class Program { static void Main(string[] args) { var pipeline = PipelineBuidler.BuildPipeline(new AssemblyCatalog(typeof(Program).Assembly), StringSelector, StringSelector, StringSelector); pipeline(); } static int StringSelector(IEnumerable<string> strings) { int i = 0; foreach (var item in strings) Console.WriteLine("[{0}] {1}", i++, item); return int.Parse(Console.ReadLine()); } } 
+4
source

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


All Articles