简体   繁体   中英

How do I combine the Decorator pattern with C# MEF?

I'm trying to enhance my current established assemblies that use C# MEF. Since these assemblies are already used in production, modifying the individual classes directly is NOT a viable approach at the moment. Primarily I'm adding new behaviors to currently existing ones. For example I have:

public IExtension
{
     Object Execute(); 
}


public BaseExtension : IExtension
{
     // other methods and members

     public virtual Object Execute()
     {
         // do operations here. 
     }
}

[Export(typeof(IExtension)]
public AppRecordExtension : BaseExtension
{
     // .. other methods and members
     public override Object Execute()
     {
         base.Execute(); // shown just for example..
         this.someOperation(); 
     }
}

// other extensions made.

Now the above works when the MEF container calls the extension in a driver's method:

[ImportMany(typeof(IExtension)]
private IEnumerable<Lazy<IExtension>> operations;

public void ExecuteExtensions() 
{
     var catalog = new AggregateCatalog( new AssemblyCatalog(Assembly.GetExecutingAssembly()), new DirectoryCatalog("extensions", ".dll")); 
     CompositionContainer container = new CompositionContainer(catalog); 
     container.ComposeParts(this); 

     Dictionary<IExtension, object> result = new Dictionary<IExtension, object>(); 

     foreach(Lazy(IExtension> extension in operations) 
     {
         result.Add((extension.Value, extension.Value.Execute()); 

     }
}

However if I want to implement specific decorators for the IExtension or BaseExtension, I'm at a loss where I should put them in the container, or how I should put the attributes on the decorators so that all the original IExtension concrete classes are loaded and executed with the additional behaviors. An example of a IExtension decorator:

// do I put an attribute here? 
// if an attribute is put here, how does the MEF container call it?
public BatchableExtension : BaseExtension 
{
     private IExtension extension = null; 


     public BatchableExtension( IExtension extension) 
     {
        this.extension = extension; 
     }

     public override Object Execute() 
     {
        this.extension.Execute(); 
        doSomeBatchSpecificOperation(); 
     }
}

// do I put an attribute here? 
// if an attribute is put here, how does the MEF container call it?
public  MonitoringExtension : BaseExtension 
{
     private IExtension extension = null; 


     public MonitoringExtension( IExtension extension) 
     {
        this.extension = extension; 
     }

     public override Object Execute() 
     {
        this.extension.Execute(); 
        doSomeMonitoringSpecificOperation(); 
        doSomeMoreBehaviors(); 
     }

Can someone help out here? I want to make sure that when the container picks up the extensions, the new behaviors are picked up as well, depending on the passed parameters (eg, if isBatchable = true, add BatchableExtension, etc). If it were non-MEF, the above would look something like:

 public void Main(String[] args) 
 {
     IExtension ext = new AppRecordExtension(); 
     // this is the part where I want to simulate when I use MEF. 
     IExtension ext2 = new MonitoringExtension(new BatchableExtension(ext)); 
     ext2.Execute(); 
 }

MEF does not support this kind of functionality, so you'll have to do it yourself. You can expose the data for constructing the decorated object by using Export Metadata - then you'll export your extensions like this:

[ExtensionExport(IsBatch = true, IsMonitoring = false)]
public AppRecordExtension : BaseExtension
{
     // ...
}

and in the class that imports the extensions:

[ImportMany]
private IEnumerable<Lazy<IExtension, IExtensionMetadata>> operations;

public void ExecuteExtensions()
{
    // ...

    foreach(Lazy(IExtension, IExtensionMetadata> extension in operations) 
    {
        IExtension decoratedExtension = DecorateExtension(extension);
        result.Add(decoratedExtension, decoratedExtension.Execute()); 
    }
}

private IExtension DecorateExtension(Lazy<IExtension, IExtensionMetadata> exportedExtension)
{
    IExtension ext = exportedExtension.Value;
    if (exportedExtension.Metadata.IsBatch)
    {
        ext = new BatchableExtension(ext);
    }
    if (exportedExtension.Metadata.IsMonitoring)
    {
        ext = new MonitoringExtension(ext);
    }

    // Other decorating logic...

    return ext;
}

U could add basic support easily. U just need a custom catalog which rewrites contracts in the way u want the decoration to happen:

public class DecoratorChainCatalog : ComposablePartCatalog
{
    private List<Type> myDecoratorChain;
    private List<ComposablePartDefinition> myParts;

    private string myContractName;

    public DecoratorChainCatalog( Type contract )
        : this( AttributedModelServices.GetContractName( contract ) )
    {
    }

    public DecoratorChainCatalog( string contract )
    {
        Contract.RequiresNotNullNotEmpty( contract, "contract" );

        myContractName = contract;

        myDecoratorChain = new List<Type>();
        myParts = new List<ComposablePartDefinition>();
    }

    public void Add( Type type )
    {
        Contract.Invariant( !myParts.Any(), "Recomposition not supported" );

        myDecoratorChain.Add( type );
    }

    public override IQueryable<ComposablePartDefinition> Parts
    {
        get
        {
            ComposeDecoration();
            return myParts.AsQueryable();
        }
    }

    [SecuritySafeCritical]
    private void ComposeDecoration()
    {
        if ( myParts.Any() )
        {
            return;
        }

        Trace.WriteLine( "!! ComposeDecoration !!" );

        var contracts = new List<string>();
        foreach ( var type in myDecoratorChain )
        {
            var originalPart = AttributedModelServices.CreatePartDefinition( type, null );

            var importDefs = originalPart.ImportDefinitions.ToList();

            if ( type != myDecoratorChain.First() )
            {
                RewriteContract( importDefs, contracts.Last() );
            }

            var exportDefs = originalPart.ExportDefinitions.ToList();

            if ( type != myDecoratorChain.Last() )
            {
                contracts.Add( Guid.NewGuid().ToString() );
                RewriteContract( exportDefs, type, contracts.Last() );
            }

            // as we pass it to lazy below we have to copy it to local variable - otherwise we create a closure with the loop iterator variable
            // and this will cause the actual part type to be changed
            var partType = type;
            var part = ReflectionModelServices.CreatePartDefinition(
                new Lazy<Type>( () => partType ),
                ReflectionModelServices.IsDisposalRequired( originalPart ),
                new Lazy<IEnumerable<ImportDefinition>>( () => importDefs ),
                new Lazy<IEnumerable<ExportDefinition>>( () => exportDefs ),
                new Lazy<IDictionary<string, object>>( () => new Dictionary<string, object>() ),
                null );

            myParts.Add( part );
        }

        // no add possible any longer
        myDecoratorChain = null;
    }

    [SecuritySafeCritical]
    private void RewriteContract( IList<ImportDefinition> importDefs, string newContract )
    {
        var importToDecorate = importDefs.Single( d => d.ContractName == myContractName );
        importDefs.Remove( importToDecorate );

        Contract.Invariant( importToDecorate.Cardinality == ImportCardinality.ExactlyOne, "Decoration of Cardinality " + importToDecorate.Cardinality + " not supported" );
        Contract.Invariant( ReflectionModelServices.IsImportingParameter( importToDecorate ), "Decoration of property injection not supported" );

        var param = ReflectionModelServices.GetImportingParameter( importToDecorate );
        var importDef = ReflectionModelServices.CreateImportDefinition(
            param,
            newContract,
            AttributedModelServices.GetTypeIdentity( param.Value.ParameterType ),
            Enumerable.Empty<KeyValuePair<string, Type>>(),
            importToDecorate.Cardinality,
            CreationPolicy.Any,
            null );

        importDefs.Add( importDef );
    }

    [SecuritySafeCritical]
    private void RewriteContract( IList<ExportDefinition> exportDefs, Type exportingType, string newContract )
    {
        var exportToDecorate = exportDefs.Single( d => d.ContractName == myContractName );
        exportDefs.Remove( exportToDecorate );

        var exportDef = ReflectionModelServices.CreateExportDefinition(
            new LazyMemberInfo( exportingType ),
            newContract,
            new Lazy<IDictionary<string, object>>( () => exportToDecorate.Metadata ),
            null );

        exportDefs.Add( exportDef );
    }
}

See also: http://blade.codeplex.com/SourceControl/latest#src/Blade.Core/Composition/DecoratorChainCatalog.cs

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM