Custom ExportProvider and MEF recomposition

Jan 18, 2010 at 2:35 PM
Edited Jan 18, 2010 at 2:47 PM

Hi all,

I've got a question about custom ExportProviders in combination with MEF recomposition. I've got an ExportProvider which finds exports through a configuration file. This is parsed using LINQ to XML on creation of the exportprovider into a Dictionary. A dictionary of contract names and related types. The GetExportsCore method is implemented in such a way that when it is called with an ImportDefinition, it looks in the Dictionary, finds the type belonging to the contractname of the ImportDefinition and creates the Export and ExportDefinition objects. So far so good and everything works as expected.

I enhanced the ExportProvider with a FileSystemWatcher, which monitors the configurationfile. When it changes, it parses the file again, finds out which exportdefinitions have been added and which have been removed and fires the OnExportsChanging and OnExportsChanged events accordingly. This also works correctly as I can see the events "bubble up" to the CompositionContainer. Whenever I fire these events in the ExportProvider, they are automatically fired in the CompositionContainer.

Now the problem:

I hoped :) that calling the OnExportsChanging and the OnExportsChanged methods in my ExportProvider, would be enough for MEF to recomposition all the parts that have been composed, but nothing really happens except for the container firing the events. I tried to delve into the source code and it seems that such "automatic" recomposition is only possible for catalogs (I thinks it's actually the CatalogExportProvider which does this). Am i correct? Isn't there an easy way for custom ExportProviders to trigger recomposition when their Exports change?

If i'm correct, are there any plans to implements this before the final version ships?

 Thanks in advance for your answer.....

 

 

 

 

Developer
Jan 18, 2010 at 4:16 PM

Recomposition should work fine for custom export providers as well. Are you sure that you are firing the OnExportsChanging event properly? That is the key place where the recomposition should occur. Perhaps if you post your code we might be able to point out what could be happening.

Jan 19, 2010 at 10:15 AM

Hi,

Thanks for your answer. Are you sure about the ExportProvider and the recomposition? When I went through the source code of the MEF framework, the only thing doing something when the OnChanging event fires is the CatalogExportProvider, which is wrapped around a catalog when you create the CompositionContainer. That's why I assumed recomposition only works for catalogs implementing a certain interface..... I don't want to question your knowledge, I'm just curious were the handling of the OnChanging event of a custom exportprovider is implemented, maybe I missed it. As I said, when my custom ExportProvider fires these events, the container also fires them.

I should mention that the recomposition wanted is a single import recomposition, so the value imported needs to be replaced with the new value. It;s not an IEnumerable. I'm not at work today, so I will post the code tomorrow.

Thanks for your time and expertise....

Developer
Jan 19, 2010 at 5:55 PM

The real magic of recomposition happens inside the ImportEngine since it is the one that knows about and controls the imports. When a custom export provider triggers the OnExportsChanged/ing events the CompositionContainer will propagate those events. The ImportEngines which are used by our two standard ExportProviders (CatalogExportProvider and ComposablePartExportProvider) both subscribe to the OnExportsChanging event of the CompositionContainer so when the container fires that event they receive it and update the appropriate imports. I am describing this from memory but I believe that is basically how it works, hopefully it makes some since.

Out of curiosity for the imports that you expect to get recomposed are they on parts that live in either a CatalogExportProvider or ComposablePartExportProvider? If not and they are from your custom export provider how are you actually satisfying the imports for them initially? 

Jan 20, 2010 at 9:01 AM

The imports that I expect that need to get recomposed are on a part which I supply to the CompositionContainer with the ComposeParts() method. The CompositionContainer is created and given my custom exportprovider in the constructor. The parts that the exportprovider provides don't have any imports on them.

Im sorry that I still can't post the code today, so I'll hopefully post that tomorrow.

Thanks again for your time!

Jan 21, 2010 at 9:22 AM

Thanks to your help i fixed the issue! When you said that most of the magic happens inside the ImportEngine, I decided to put a few break points there. I ultimately breaked into the GetImportsAffected method, which returned none. I found out that this method uses the ImportDefintion.IsConstraintSatisfiedBy() method. Breaking into that method of the ContractBasedImportDefinition, it seemed that an ExportDefinition satisfies an ImportDefinition if both the the contractname and the metadata match. This was logical and was as I assumed. I had no metadata in my ExportDefintions so the MetaData dictionary of an ExportDefinition was empty. But the ContractBasedImportDefinition demands that an ExportDefinition has a metadata keyvalue, where the key is "CompositionConstants.ExportTypeIdentityMetadataName" and the value is also the contractname. After putting this metadata in my exportdefinitions, all worked as expected. I think this should be mentioned in the documentation, as it applies to every custom ExportProvider you make in combination with the attribute based programming model.

Thanks for all your help, time and knowledge :)

 

Dec 8, 2010 at 2:57 PM
weshaggard wrote:

Recomposition should work fine for custom export providers as well. Are you sure that you are firing the OnExportsChanging event properly? That is the key place where the recomposition should occur. Perhaps if you post your code we might be able to point out what could be happening.

Hello,

I've spent several hours already trying to resolve issue with custom provider and recomposition in Silverlight 4. Here's the details for implementation:

1. I took the FakeExportProvider sample taken from here: http://www.thecodejunkie.com/2009/03/creating-fakeexportprovider-to-help.html

2. Extened "AddExport" method to raise both "OnExportChanging" and "OnExportChanged" methods

using (var atomic = new AtomicComposition())
        {
          OnExportsChanging(new ExportsChangeEventArgs(newDefinitions, removedDefinitions, atomic));
          atomic.Complete();
        }
        OnExportsChanged(new ExportsChangeEventArgs(newDefinitions, removedDefinitions, null));

3. Created 3 test classes where 2 of them are marked with [ExportAttribute] and 1 is not

public interface IDebugActivity
  {
    string Name { get;  }
  }

  [Export(typeof(IDebugActivity))]
  public class Activity1 : IDebugActivity
  {
    public string Name
    {
      get { return "Activity 1"; }
    }
  }

  [Export(typeof(IDebugActivity))]
  public class Activity2 : IDebugActivity
  {
    public string Name
    {
      get { return "Activity 2"; }
    }
  }

  public class Activity3 : IDebugActivity
  {
    public string Name
    {
      get { return "Activity 3"; }
    }
  }

4. Declared an import within the MainPage.xaml.cs like the following:

[ImportMany(AllowRecomposition = true)]
public List<IDebugActivity> Activities { get; set; }

5. Call the following methods in the "MainPage" constructor (where FunctionExportProvider is my custom provider)

var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new DeploymentCatalog());
      
_provider = new FunctionExportProvider();
_container = new CompositionContainer(catalog, _provider);
      
_container.ComposeParts(this);

At this point everything works ok. The "Activities" property contains 2 instances like expected.

6. Created a button that should add a new export programmatically:

private void OnDebug(object sender, RoutedEventArgs e)
{
  _provider.AddExport(typeof(IDebugActivity).FullName, new Activity3());
}

At this point nothing happens. The "Activities" property is not recomposed. I've tried checking "OnImportsSatisfied" method but it's not called and property contains only 2 instances.

However if I call "_container.ComposeParts(this)" from code the property is refreshed with "3" instances as expected.

What I really need is "Activities" property to be recomposed automatically whenever I add new export to my custom provider. Wiring "Changed" event for the provider and calling "Container.ComposeParts" manually is not an option for me as recomposition should take place in various different places.

I would be gratefull if you could give me at least some sort of workaround

Thanks in advance,

Dennis