I’m working on implementing dynamic loading of Pages into a existing Silverlight application. So far, so good. Both importing Pages from external .xap’s work and exporting data from the host application to the plugins is working.
The host application consumes a Webservice that exposes a number of types relating to the backend system. My question is how do i export objects of types defined in the Webservice?
An example i Export a List of the SMS_SupportedPlatforms, which is defined in the WS client. This is from the hosting application. The property lives in the App.xaml.cs of the main Silverlight application. It is filled by an async call to the Webservice.
[Export(ExportContracts.SMS_SupportedPlatforms)]
public static List<Client.SMS_SupportedPlatforms> SupportedPlatforms = new List<Client.SMS_SupportedPlatforms>();
Edited:
I’ve moved the Imports to a Seperate class in the external assemblies. They now recide in this class:
public class NeoServiceManagerImports
{
[Import(NeoSMExportContracts.DepartmentExportAttribute, AllowRecomposition = true)]
public string Department { get; set; }
[Import(NeoSMExportContracts.RolesExportAttribute, AllowRecomposition = true)]
public List<string> Roles { get; set; }
[Import(NeoSMExportContracts.SMS_SupportedPlatforms, AllowRecomposition = true)]
public List<Client.SMS_SupportedPlatforms> SupportedPlatforms
{
get;
set;
}
public NeoServiceManagerImports()
{
CompositionInitializer.SatisfyImports(this);
}
}
This class called by the constructor of the Page to be exported (for testing purposes).
The page is then exported with a MetadataAttribute into a UIProviderbase class i’ve bade for exporting plugins(ignore the very abstract naming ;-)) The class has a couple of props for a logo, title, and a list of pages.
[Export(typeof(UIProviderBase))]
public class ExternalMainMenuExternalSubMenuUIProvider: UIProviderBase
{
public override string Title
{
get { return "Submenu"; }
}
public override string ImageUri
{
get { return "uriuri"; }
}
[ImportMany("ExternalSubMenuForExternalMainMenuContract")]
public override List<System.ComponentModel.Composition.ExportFactory<FrameworkElement, IPageMetadata>> EntryPage
{
get;
set;
}
}
I sure the issue relates to MEF being unable to resolve the type as the same, when being referenced by two different assemblies. Is there any way to solve this, without refactoring the hosting application to take a list of ISMS_SupportedPlatforms? Currently it seems that the hosting application is Exporting correctly, but it is never discovered in the plugin.
If i have AllowDefault = true, the page is loaded but SupportedPlatforms stays null. If false, it doesn’t export the page and fails silently.
I’ve changed the way i load in the pages a bit, and i’m trying to get some more info for you. This is the error i see now
The composition produced a single composition error. The root cause is provided below. Review the CompositionException.Errors property for more detailed information.
1) No valid exports were found that match the constraint '((exportDefinition.ContractName == "SMS_SupportPlatformsExport") AndAlso (exportDefinition.Metadata.ContainsKey("ExportTypeIdentity") AndAlso "System.Collections.Generic.List(HelloWorld.MEF.Client.SMS_SupportedPlatforms)".Equals(exportDefinition.Metadata.get_Item("ExportTypeIdentity"))))', invalid exports may have been rejected.
Resulting in: Cannot set import 'HelloWorld.MEF.NeoServiceManagerImports.SupportedPlatforms (ContractName="SMS_SupportPlatformsExport")' on part 'HelloWorld.MEF.NeoServiceManagerImports'.
Element: HelloWorld.MEF.NeoServiceManagerImports.SupportedPlatforms (ContractName="SMS_SupportPlatformsExport") --> HelloWorld.MEF.NeoServiceManagerImports
Resulting in: An exception occurred while trying to create an instance of type 'HelloWorld.MEF.ExternalMainMenuExternalSubMenu'.
Resulting in: Cannot activate part 'HelloWorld.MEF.ExternalMainMenuExternalSubMenu'.
Element: HelloWorld.MEF.ExternalMainMenuExternalSubMenu --> HelloWorld.MEF.ExternalMainMenuExternalSubMenu --> AssemblyCatalog (Assembly="HelloWorld.MEF, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")
Resulting in: Cannot get export 'HelloWorld.MEF.ExternalMainMenuExternalSubMenu (ContractName="ExternalSubMenuForExternalMainMenuContract")' from part 'HelloWorld.MEF.ExternalMainMenuExternalSubMenu'.
Element: HelloWorld.MEF.ExternalMainMenuExternalSubMenu (ContractName="ExternalSubMenuForExternalMainMenuContract") --> HelloWorld.MEF.ExternalMainMenuExternalSubMenu --> AssemblyCatalog (Assembly="HelloWorld.MEF, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")
This is the code where the exception appears
var page = (from p in this.Plugins
from e in p.EntryPage
where e.Metadata.NavigateUri == this.targetUri.ToString()
select e).Single().CreateExport().Value;
Residing in this class (Loading the pages dynamically)
public class MefContentLoader : INavigationContentLoader
{
private PageResourceContentLoader pageResourceContentLoader = new PageResourceContentLoader();
private Uri targetUri;
[ImportMany(AllowRecomposition = true)]
public UIProviderBase[] Plugins
{
get;
set;
}
public MefContentLoader()
{
CompositionInitializer.SatisfyImports(this);
}
#region INavigationContentLoader Members
public IAsyncResult BeginLoad(Uri targetUri, Uri currentUri, AsyncCallback userCallback, object asyncState)
{
this.targetUri = targetUri;
return pageResourceContentLoader.BeginLoad(targetUri, currentUri, userCallback, asyncState);
}
public bool CanLoad(Uri targetUri, Uri currentUri)
{
// TODO: Handle this properly
return true;
}
public void CancelLoad(IAsyncResult asyncResult)
{
// TODO: Handle this properly
pageResourceContentLoader.CancelLoad(asyncResult);
}
public LoadResult EndLoad(IAsyncResult asyncResult)
{
if (this.Plugins.Length == 0 ||
this.Plugins.Count(p => p.EntryPage != null && p.EntryPage.Any(u => u.Metadata.NavigateUri == targetUri.ToString())) == 0)
{
return pageResourceContentLoader.EndLoad(asyncResult);
}
var page = (from p in this.Plugins
from e in p.EntryPage
where e.Metadata.NavigateUri == this.targetUri.ToString()
select e).Single().CreateExport().Value;
return new LoadResult(page);
}
#endregion
}
To sum up:
In Assembly B (Plugin) i’m exporting a number of pages to an UIProviderbase in Assembly B. The pages need to consume data from Assembly A(Main Silverlight app). Assembly A then needs to import the UIProviderbase as plugins, and from them get a collection of menu pages adding them to the application.
This works with all but the type’s defined in the webservice, that both Assembly A and B have a reference to.
Turns out, it was some form of either type matching or type incompatability. In the plugin (Assembly B), i added a reference to Assembly A and used the type from there:
I have not decorated the export or import with a ContractName, since it’s not needed in this case and the type is unambiguous.