Your question suggests this structure:
public class BasicData { public string Basic { get; set; }
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;
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()); } }