adding a different 'type' to collection , makes GetExportedValueOrDefault fail

Sep 18, 2009 at 11:23 PM
Edited Sep 18, 2009 at 11:27 PM

First of all I am using the excellent sample of Brad Adams:

Silverlight 3 Navigation: Dynamically Loaded Pages… Now MEF Powered!

It really helped me a lot to understand. So I started playing with it

It basically creates a container of all exported <MainPage> in the assemblies.

Which are all exported with a contractname of 'typeof(MainPage)'

in the application start it does the following:

this.RootVisual = container.GetExportedValueOrDefault<MainPage>();

And without my modifications it works fine.

The app.xaml.cs starts like this:

 

namespace SilverlightMEFNavigation

{

public partial class App : Application {

 

And I changed that part in the following, basically adding an extra interfase,

an extra export class and extra import property

 

namespace SilverlightMEFNavigation

{

public interface IMessageSender

{ void Send(string message);

 }

public partial class App : Application {

[Export("IMessageSender.Send")]

public class EmailSender : IMessageSender

{ public void Send(string message)

{ Console.WriteLine(message); } }

 [Import("IMessageSender.Send",AllowRecomposition = true)]

public IMessageSender MessageSender { get; set; }

 

So before the container contained only parts of the type <MainPage> , but after this modification it

also hast an EmailSender in it (that is my understanding)

Evrything still works fine.

 

However now in the actual MainPage I decided that that page is also interested in this

MessageSender so also there I add the following property

 [Import("IMessageSender.Send",AllowRecomposition = true)]

public IMessageSender MessageSender { get; set; }

 

So now the import is at two places, in the program class and in the mainpage class.

Compiles fine, but when it runs it gives an exception, it can no longer find the mainpage in this function

Ii is this function that fails:

this

becuase the Getex.. returns null.

Why, what has really changed ? I tried to add the actual Contractname as a parameter. Same result.

Unfortunately I can not run the diagnostics as I dont have a silverlight assembly for it.

Tnx

 Ben

.RootVisual = container.GetExportedValueOrDefault<MainPage>();

 

Developer
Sep 20, 2009 at 5:45 AM

Are you sure the imported property in the application class is set? If I remember correctly in Brad's sample the application doesn't actually have it's imports satisfied which is why nothing is wrong.

At any rate I believe the problem is with your EmailSender. Try updating the Export to [Export("IMessageSender.Send", typeof(IMessageSender))]. Since you are using an explicit string contract name it defaults the type to the type that the export attribute is attached to which is EmailSender however the import is looking for the IMessageSender type so you need to explicity provide the type on the Export.

container.GetExportedValueOrDefault<MainPage>() is returning null is because the MainPage export is being rejected because it has that import which cannot be satisfied.

Sep 20, 2009 at 11:40 PM

question:

>>container.GetExportedValueOrDefault<MainPage>() is returning null is because the MainPage export is being rejected because it has that import which cannot be satisfied.

Is it possible to have a 'modifier' on the import such that I allow it to not be satisfied (untill some package is loaded that can satisfy it, If I know it can be 'not satisfied' I could test if it is null before using it.

question:

>>however the import is looking for the IMessageSender type so you need to explicity provide the type on the Export.

OK I can understand that I actually exported a class type of EMailSender instead of a IMessageSender. But...why does it work with only the

the export and import in the app class and starts to fail with an extra import in the main class.

And

1) I import on the contract name only, why does it even bother checking the type

2) And if iy must check shouldn't it see that EmailSender's ancestor class actually is IMessageSender and thus fullfills both the requirements of my import request: contractname and type .

 

OK I changed it:

I added the following to the App class now:

[Export("IMessageSender.Send", typeof(IMessageSender))]

public class EmailSender : IMessageSender

{<font size="2">

 

</font>

public void Send(string message)

{Console.WriteLine(message);}

}

Import("IMessageSender.Send", typeof(IMessageSender), AllowRecomposition = true)]

public IMessageSender MessageSender { get; set; }

 

The application behaves the same as if nothing was added.

 

Then I added in the MainApp class this:

Import("IMessageSender.Send", typeof(IMessageSender), AllowRecomposition = true)]

public IMessageSender MessageSender { get; set; }

 

So exactly the same import as in the app class. And indeed the

main form now appears.

However the application now continues to download a xap and load it  dynamically and the following error occurs on this statement: 

PackageCatalog.AddPackage(p);

 

System.ComponentModel.Composition.ChangeRejectedException was unhandled by user code
  Message="The composition remains unchanged. The changes were rejected because of the following error(s): The composition produced multiple composition errors, with 2 root causes. The root causes are provided below. Review the CompositionException.Errors property for more detailed information.\r\n\r\n1) More than one exports were found that match the constraint '((exportDefinition.ContractName = \"IMessageSender.Send\") && (exportDefinition.Metadata.ContainsKey(\"ExportTypeIdentity\") && \"SilverlightMEFNavigation.IMessageSender\".Equals(exportDefinition.Metadata.get_Item(\"ExportTypeIdentity\"))))'.\r\n\r\nResulting in: Cannot set import 'SilverlightMEFNavigation.MainPage.MessageSender (ContractName=\"IMessageSender.Send\")' on part 'SilverlightMEFNavigation.MainPage'.\r\nElement: SilverlightMEFNavigation.MainPage.MessageSender (ContractName=\"IMessageSender.Send\") --> SilverlightMEFNavigation.MainPage --> AssemblyCatalog (Assembly=\"SilverlightMEFNavigation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\")\r\n\r\n2) More than one exports were found that match the constraint '((exportDefinition.ContractName = \"IMessageSender.Send\") && (exportDefinition.Metadata.ContainsKey(\"ExportTypeIdentity\") && \"SilverlightMEFNavigation.IMessageSender\".Equals(exportDefinition.Metadata.get_Item(\"ExportTypeIdentity\"))))'.\r\n\r\nResulting in: Cannot set import 'SilverlightMEFNavigation.MainPage.MessageSender (ContractName=\"IMessageSender.Send\")' on part 'SilverlightMEFNavigation.MainPage'.\r\nElement: SilverlightMEFNavigation.MainPage.MessageSender (ContractName=\"IMessageSender.Send\") --> SilverlightMEFNavigation.MainPage --> AssemblyCatalog (Assembly=\"SilverlightMEFNavigation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\")\r\n"
  StackTrace:
       at System.ComponentModel.Composition.CompositionResult.ThrowOnErrors(AtomicComposition atomicComposition)
       at System.ComponentModel.Composition.Hosting.ExportProvider.OnExportsChanging(ExportsChangeEventArgs e)
       at System.ComponentModel.Composition.Hosting.CatalogExportProvider.OnCatalogChanging(Object sender, ComposablePartCatalogChangeEventArgs e)
       at System.ComponentModel.Composition.Hosting.ComposablePartCatalogCollection.OnChanging(Object sender, ComposablePartCatalogChangeEventArgs e)
       at System.ComponentModel.Composition.Hosting.AggregateCatalog.OnChanging(ComposablePartCatalogChangeEventArgs e)
       at System.ComponentModel.Composition.Hosting.ComposablePartCatalogCollection.OnContainedCatalogChanging(Object sender, ComposablePartCatalogChangeEventArgs e)
       at System.Windows.PackageCatalog.OnChanging(ComposablePartCatalogChangeEventArgs e)
       at System.Windows.PackageCatalog.AddPackage(Package package)
       at SilverlightMEFNavigation.App.DownloadCompleted(AsyncCompletedEventArgs e, Package p)
       at System.Windows.Package.client_OpenReadCompleted(Object sender, OpenReadCompletedEventArgs e)
       at System.Net.WebClient.OnOpenReadCompleted(OpenReadCompletedEventArgs e)
       at System.Net.WebClient.OpenReadOperationCompleted(Object arg)
  InnerException:

Developer
Sep 21, 2009 at 7:03 PM

>>Is it possible to have a 'modifier' on the import such that I allow it to not be satisfied (untill some package is loaded that can satisfy it, If I know it can be 'not satisfied' I could test if it is null before using it.
[Import(... AllowDefault=true)] This will essentially make the import optional and if one isn't found set it to null, but remember you will still need the AllowRecomposition=true if you expect it to get set later.

>>But...why does it work with only the export and import in the app class and starts to fail with an extra import in the main class.
I would bet that it isn't actually working in the App class, did you ever check to see if it was actually set? I don't believe the App class ever has its imports satisfied.

>>I import on the contract name only, why does it even bother checking the type
In our model we match on both the contract name, which is usually based on the type name but can be any string, but we also match on what refer to as the Type Identity because if the type identity doesn't match then there is a very high chance these are not compatible and if they are today there is a good chance that they will be broken in the future.

>>And if iy must check shouldn't it see that EmailSender's ancestor class actually is IMessageSender and thus fullfills both the requirements of my import request: contractname and type
The type identity check is string based and thus it doesn't have any notion of an ancestry. Our attributed model is about being explicit so there is limited ambiguity. Actually in your particular example the recommended approach would be to not even use a string contract name but instead simply import/export typeof(IMessageSender).

>>Package error
Which Package are you loading dyamically? The default Application XAP should be already loaded. 
From the error message it would appear that whatever package you are loading dynamically contains another IMessageSender implementation and your Imports are single imports and so they cannot import multiple IMessageSenders. Therefore the catalog rejects the changes because it would put the composition into an invalid state. You need to do one of two things, either change your Import to ImportMany and type to IMessageSenders[], or figure out why to IMessageSenders are exist and elminate one of them.

Sep 21, 2009 at 10:21 PM
Edited Sep 21, 2009 at 10:23 PM

>>>>But...why does it work with only the export and import in the app class and starts to fail with an extra import in the main class.
>>I would bet that it isn't actually working in the App class, did you ever check to see if it was actually set? I don't believe the App class ever has its imports satisfied.

Maybe not, haven't tested it if it actually satisfied. But that was not the point. Even if it was not satisfied, at least it was still finding the <MainPage> in the call

container.GetExportedValueOrDefault<MainPage>

But the moment you have the second import of the IMessageSender (which probably is not satisfied either), it starts failing that line of code.

>>>>Package error
>>Which Package are you loading dyamically? The default Application XAP should be already loaded. 
>>From the error message it would appear that whatever package you are loading dynamically contains another IMessageSender implementation and your Imports are single imports and so they cannot import multiple IMessageSenders. Therefore the catalog rejects the changes because it would put the composition into an invalid state. You need to do one of two things, either change your Import to ImportMany >>and type to IMessageSenders[], or figure out why to IMessageSenders are exist and elminate one of them.

 Have a look at Brads example, it loads another package dynamically which exports a <MainPage> as well. But I made no changes to that package, it does not contain any reference to an IMessageSender.

If you want and have a look I can put a zip with code on my site.

Thanks for your time sofar.

 

Developer
Sep 22, 2009 at 4:55 PM

Yeah if you post a zip with your projects I will likely be able to pinpoint the problem.

Sep 22, 2009 at 5:41 PM

http://geerdes.dyndns.org/SilverlightMEFNavigation.zip

As is the application works, I added my additions between lines starting with '//*****'

In the MainPage.xamls.cs there are two lines commented out, uncomment them and the application fails.

And btw, it looks like the import does indeed not get satisfied

 

Tnx

 

 

 

Developer
Sep 22, 2009 at 8:34 PM

Alright I believe I have figured out the problem (although I wasn't able to test it).

There is an issue in the host setup code in Application_Startup, change the code from:

            AggregateCatalog aggregateCatalog = new AggregateCatalog();
            AssemblyCatalog ac = new AssemblyCatalog(Assembly.GetExecutingAssembly());
            aggregateCatalog.Catalogs.Add(ac);
            aggregateCatalog.Catalogs.Add(PackageCatalog);

            // Load any package dynamically
            Package.DownloadPackageAsync(new Uri("AdditionalPages.xap", UriKind.Relative), DownloadCompleted);
            CompositionContainer container = new CompositionContainer(aggregateCatalog);
            this.RootVisual = container.GetExportedValueOrDefault<MainPage>();

To:
            PackageCatalog.AddPackage(Package.Current);

            // Load any package dynamically
            Package.DownloadPackageAsync(new Uri("AdditionalPages.xap", UriKind.Relative), DownloadCompleted);
            CompositionContainer container = new CompositionContainer(PackageCatalog);
            this.RootVisual = container.GetExportedValueOrDefault<MainPage>();

The issue here is that the SilverlightMEFNavigation assembly was getting loaded into the catalog twice, once manually through the AssemblyCatalog and once as an assembly included in the AdditionalPages.xap. Which is how you got two IMessageSenders. The PackageCatalog itself is designed to collapse these duplications and so adding the current package through Package.Current should prevent the duplication.

This is a issue with Brad's sample which I will talk to him about.

Sep 23, 2009 at 5:07 PM

I will try this,

Thanks

  Ben

 

Sep 23, 2009 at 5:44 PM

Indeed it works.

What is the functional difference in creating an AggregateCatalog and an AssemblyCatalog and adding that to the container

compared to just adding the PackageCatalog to the container?

 

In the debugger, when I set a break on the first DownloadCompleted event, the Import of I messageSender is not yest satisfied.

When I have a break later on it is satisfied. Add what moment does the composition take place?

 

Thanks for all your time.

 

 

Sep 23, 2009 at 6:33 PM

PacakgeCatalog is for downloading XAP pacakges dynamically from the server. Package.Current grabs all of the assemblies in the current XAP.

AggregateCatalogs are simply a catalog of catalogs. You would have to manually grab each loaded assembly and add it to an aggregate to get the same functionality. AssemblyCatalog won't help you though with downloading remote XAPs from a URI as PackageCatalog does.

Regards

Glenn

Developer
Sep 23, 2009 at 8:12 PM

Adding to what Glenn mentioned the AggregateCatalog doesn't provide any logic for consolidating duplicate assemblies because it doesn't have direct access to the Assemblies. Where as the PackageCatalog will be smart about adding a given Assembly only once even if it appears in multiple packages. Which is why my fix above eliminated your issue.

As far as when does the composition take place. The composition for MainPage occurs when you call container.GetExportedValueOrDefault<MainPage>(). Now once the container is setup and objects, like MainPage, have already been composed and you download another package, recomposition will happen at the point you call PackageCatalog.AddPackage(...). Recomposition is triggered by the change to the catalog and will only affect objects with imports marked with AllowRecomposition=true that match any newly added exports added to the catalog.

HTH,
Wes