2012-09-13 14 views
6

Encontré esta pregunta difícil de expresar (particularmente en forma de título), así que por favor tengan paciencia conmigo.¿Cómo puedo usar MEF para administrar módulos interdependientes?

Tengo una aplicación que continuamente estoy modificando para hacer cosas diferentes. Parece que MEF podría ser una buena forma de administrar las diferentes funciones. En términos generales, hay tres secciones de la aplicación que forman una tubería de clases:

  1. Adquisición
  2. Transformación
  3. Expresión

En su forma más simple, que puede expresar cada una de estas etapas como una interfaz (IAcquisition etc.). Los problemas comienzan cuando quiero usar componentes de adquisición que proporcionan datos más ricos que los estándares. Quiero diseñar módulos que utilicen esta información más completa, pero no puedo confiar en que esté allí.

Podría, por supuesto, agregar todos los datos a la especificación de la interfaz. Podría tratar con fuentes de datos más pobres arrojando una excepción o devolviendo un valor nulo. Esto parece muy lejos del ideal.

Preferiría hacer el enlace MEF en tres etapas, de modo que los módulos se ofrecen al usuario solo si son compatibles con los seleccionados previamente.

Así que mi pregunta es: ¿Puedo especificar los metadatos que restringen el conjunto de importaciones disponibles?

Un ejemplo:

Acquision1 ofrece BasicData única

Acquision2 ofrece BasicData y AdvancedData

Transformation1 requiere BasicData

Transformation2 requiere BasicData y AdvancedData

Acquisi El módulo de decisión se selecciona primero.

Si se selecciona Acquisition1, no ofrezca Transformation 2; de lo contrario, ofrezca ambos.

¿Esto es posible? ¿Si es así, cómo?

Respuesta

4

Su pregunta sugiere una estructura como esta:

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

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

Ahora usted tiene sus componentes de adquisición, transformación y expresión.¿Quieres ser capaz de tratar con diferentes tipos de datos, por lo que son genéricos:

public interface IAcquisition<out TDataKind> 
{ 
    TDataKind Acquire(); 
} 

public interface ITransformation<TDataKind> 
{ 
    TDataKind Transform(TDataKind data); 
} 

public interface IExpression<in TDataKind> 
{ 
    void Express(TDataKind data); 
} 

Y ahora quiere construir una tubería de ellos que tiene este aspecto:

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

Así que vamos a empezar la construcción de un oleoducto constructor:

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); 

La clase contiene las identidades de tipo MEF para sus tres interfaces de contrato. Los necesitaremos más tarde para identificar las exportaciones correctas. Nuestro método BuildPipeline devuelve Action. Esa va a ser la tubería, así que podemos hacer pipeline(). Se necesita ComposablePartCatalog y tres Func s (para seleccionar una exportación). De esa forma, podemos mantener todo el trabajo sucio dentro de esta clase. Entonces comenzamos creando un CompositionContainer.

Ahora tenemos que construir ImportDefinition s, en primer lugar para el componente de adquisición:

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

Este ImportDefinition simplemente filtra todas las exportaciones de la interfaz IAcquisition<>. Ahora podemos darle al contenedor:

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

aExports ahora tiene todos IAcquisition<> exportaciones en el catálogo. Así que vamos a ejecutar el selector en esto:

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

Y ahí tenemos nuestro componente de adquisición:

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

Ahora vamos a hacer lo mismo para la transformación y los componentes de la expresión, pero con una ligera diferencia: el ImportDefinition garantizará que cada componente pueda manejar la salida del componente anterior.

 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"]; 

Y ahora podemos conectar todo en un árbol de expresión:

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

Y eso es todo! Una aplicación de ejemplo simple se vería así:

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

Muchas gracias por la respuesta tan completa! –

Cuestiones relacionadas