Recommended

Skip Navigation LinksHome / Articles / View Article

Navigating between Pages in Different Xaps (by using MEF)

+ Add to SilverlightShow Favorites
6 comments   /   posted by Pencho Popadiyn on Aug 02, 2010
(4 votes)

1. The Problem

In this article I’ll show you a simple solution for navigation between pages in different Xaps, by using Managed Extensibility Framework (MEF). Recently I hit the following issue, while playing with MEF. Suppose that you have a Silverlight Navigation Application. Your application is partitioned in different modules (plugins, extensions, add-ons or whatever). Let's imagine that your application has three plugins – Orders plugin, Products plugin and Suppliers plugin, as shown on the snapshot below.

 

 

 

Each one of your plugins contains an entry page (this is the so called Entry Point for the plugin). I’ll show you some technical details about the implementation later in the article. For now let’s stick to the main problem. Your main project (the host project) has static references to two of the plugins, for example OrdersPlugin and ProductsPlugin.

 

 

Since both of the plugins are referenced by the main project, during compile time, they are included in the .Xap file! And once you start the application they are automatically loaded without any problems. However, the third plugin – the SuppliersPlugin is not included in the main .XAP file. It is downloaded runtime, by using the mighty DeploymentPackageCatalog (the DeploymentPackageCatalog is not part of MEF, but its implementation is widely spread in Google).

 

 

If you try to navigate to the Orders page or Products page, then you won’t have any problems (it’s just like creating a regular Silverlight navigation application). However, when you try to navigate to the Suppliers page, you will see the following exception.

 

 

What a shame, isn’t it? :). So where is the problem? By default the Frame class from the Navigation Framework uses the PageResourceContentLoader class to load the pages. The PageResourceContentLoader class has the following limitation: it is designed to load pages from the application package (the .xap file) that correspond to a given URI. However, our Suppliers page is not in the main application package, it is part of another package, downloaded runtime. That’s why the PageResourceContentLoader won’t know about the Suppliers page, and respectively won’t be able to load it.

2. The Solution

Fortunately the System.Windows.Controls.Navigation assembly provides us with the INavigationContentLoader interface. So, as you can guess, we need a custom implementation of the INavigationContentLoader.

public class MefContentLoader : INavigationContentLoader
{
    private PageResourceContentLoader pageResourceContentLoader = new PageResourceContentLoader();
    private Uri targetUri;
 
    public IAsyncResult BeginLoad( Uri targetUri, Uri currentUri, AsyncCallback userCallback, object asyncState )
    {
        throw new NotImplementedException();
    }
 
    public bool CanLoad( Uri targetUri, Uri currentUri )
    {
        throw new NotImplementedException();
    }
 
    public void CancelLoad( IAsyncResult asyncResult )
    {
        throw new NotImplementedException();
    }
 
    public LoadResult EndLoad( IAsyncResult asyncResult )
    {
        throw new NotImplementedException();
    }
}

The trick here is that our custom content loader should know about the entry points of each plugin.

public class MefContentLoader : INavigationContentLoader
{
    [ImportMany( AllowRecomposition = true )]
    public UIProviderBase[] Plugins
    {
        get;
        set;
    }
 
    //....Rest of the code
}

For our purposes we need to implement only the BeginLoad, CanLoad and EndLoad methods, like demonstrated on the code snippet below:

public class MefContentLoader : INavigationContentLoader
{
    private PageResourceContentLoader pageResourceContentLoader = new PageResourceContentLoader();
    private Uri targetUri;
 
    public MefContentLoader()
    {
        CompositionInitializer.SatisfyImports( this );
    }
 
    [ImportMany( AllowRecomposition = true )]
    public UIProviderBase[] Plugins
    {
        get;
        set;
    }
 
    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 )
    {
        return true;
    }
 
    public void CancelLoad( IAsyncResult asyncResult )
    {
        throw new NotImplementedException();
    }
 
    public LoadResult EndLoad( IAsyncResult asyncResult )
    {
        if ( this.Plugins.Length == 0 ||
            this.Plugins.Count( p => p.EntryPage != null && p.EntryPage.Metadata.NavigateUri == targetUri.ToString() ) == 0 )
        {
            return pageResourceContentLoader.EndLoad( asyncResult );
        }
 
        IView page = this.Plugins.First( p => p.EntryPage != null && p.EntryPage.Metadata.NavigateUri == this.targetUri.ToString() ).EntryPage.CreateExport().Value;
        return new LoadResult( page );
    }
}

 

The tricky part comes into the EndLoad method. Once we know the entry points for each plugin, and we know the target uri, we can extract the requested page and pass it to the LoadResult.

Finally, you need to set the Frame’s ContentLoader property in XAML.

<navigation:Frame x:Name="ContentFrame"
                  Style="{StaticResource ContentFrameStyle}"
                  Source="/HomeView"
                  NavigationFailed="ContentFrame_NavigationFailed">
    <navigation:Frame.ContentLoader>
        <local:MefContentLoader />
    </navigation:Frame.ContentLoader>
    <navigation:Frame.UriMapper>
        <uriMapper:UriMapper>
            <uriMapper:UriMapping Uri=""
                                  MappedUri="/Views/HomeView.xaml" />
            <uriMapper:UriMapping Uri="/{assemblyName};component/{path}"
                                  MappedUri="/{assemblyName};component/{path}" />
            <uriMapper:UriMapping Uri="/{pageName}"
                                  MappedUri="/Views/{pageName}.xaml" />
        </uriMapper:UriMapper>
    </navigation:Frame.UriMapper>
</navigation:Frame>

 

Check out the link at the end of the article, in case you want to download the full source code.

3. Designing Plugin Applications

Since there isn’t a lot of information in the web, I would like to say a few words about how to design (create) plugin applications. The strong definition about the plugin is a set of software components that adds specific capabilities to a large software application. We are surrounded with such kind of applications – the most famous are Office Word, Excel, Outlook, Visual Studio. Basically, each plugin has an entry point (plugin interface). The host application deals only with the plugin interfaces (not directly with the plugin). The host application operates independently of the plugins, making it possible for the end-users to add and update plugins dynamically without the need to make changes to the host application.

Prior to .NET Framework 4, we didn't have any concrete technology for creating plugin applications. Fortunately, in .NET Framework 4, Microsoft provides us with the Managed Extensibility Framework (MEF) which is available for Silverlight, too.

So let’s come back to our demo. I am using an abstract class named UIProviderBase and this is the “plugin interface” (the entry point) for the demo. The class provides a Title (the name of the plugin), an Image (associated with the plugin) and an EntryPage – this is the initial view (the main view) of the plugin. This class serves something like a contract, saying: “Hey, if you want to plug in your module in my application, you have to implement me”.

public abstract class UIProviderBase
{
    public abstract string Title
    {
        get;
    }
    public abstract string ImageUri
    {
        get;
    }
    public abstract ExportFactory<IView, IPageMetadata> EntryPage
    {
        get;
        set;
    }
}

 

Each plugin should provide a UIProviderBase implementation. Additionally, that implementation should be marked with the [ExportAttribute]. Below you can see the entry point for the Suppliers plugin.

[Export( typeof( UIProviderBase ) )]
public class SuppliersUIProvider : UIProviderBase
{
    public override string Title
    {
        get
        {
            return "Suppliers";
        }
    }
    public override string ImageUri
    {
        get
        {
            return "/SuppliersPlugin;component/Images/SuppliersImage.png";
        }
    }
    [Import( ViewNames.SuppliersViewName )]
    public override ExportFactory<IView, IPageMetadata> EntryPage
    {
        get;
        set;
    }
}

 

On the other side, the host application is expecting an array of UIProviderBase implementations. Note the [ImportManyAttribute].

[Export( ViewModelNames.HomeViewModelName, typeof( object ) )]
public class HomeViewModel : MyViewModelBase, IPartImportsSatisfiedNotification
{
    [ImportMany( AllowRecomposition = true )]
    public UIProviderBase[] Plugins
    {
        get;
        set;
    }
 
    public void OnImportsSatisfied()
    {
        this.Plugins = this.Plugins.OrderBy( p => p.Title ).ToArray();
        this.RaisePropertyChanged( "Plugins" );
    }
}

 

The other goodies:

  • If you download the source code (the link is at the end of the article), you will find an implementation of the DeploymentServiceCatalog, which is downloading a XAP file by a given relative uri.
  • Note the EntryPage property in the UIProviderBase class. An instance of the page is created on demand. What I am using here is a Metadata. For more information, you could read here.
  • In the HomeView.xaml I am using a PathListBox to arrange the UI.
  • One last question I should answer is how the different plugins communicate with each other. Well, in this case you need to use the so called “global events (messages)". For example, I prefer the Messanger class from the MVVM Light Toolkit, but you can use the EventAggregator with the same success.

Share


Comments

Comments RSS RSS
  • RE: Navigating between Pages in Different Xaps (by using MEF)  

    posted by DonovanWoodside on Aug 02, 2010 17:47

    Source Code's link is not correct. (http://www.silverlightshow.net/admin/)

  • RE: Navigating between Pages in Different Xaps (by using MEF)  

    posted by ppopadiyn on Aug 02, 2010 17:56

    Hi DonovanWoodside,

    Sorry for the inconvenience, it is fixed now. Thanks for the note.

  • RE: Navigating between Pages in Different Xaps (by using MEF)  

    posted by middle on Aug 12, 2010 11:18

    It's great article. Did you try implement this with WCF RIA service app?

    Thanks.

    Middle

  • RE: Navigating between Pages in Different Xaps (by using MEF)  

    posted by ppopadiyn on Aug 12, 2010 11:51

    Hi Middle,

    Yes, I tried it with WCF RIA service app. Furthermore, I have a completed demo, demonstrating WCF RIA, navigation framework and mef. There is nothing special in this case - each xap has a link to the service and communicate with it. However I decided not to upload it, since Brian Noyes has pretty nice series of articles about WCF RIA. In case you are interested, you could check it here.

  • RE: Navigating between Pages in Different Xaps (by using MEF)  

    posted by Anton on Aug 13, 2010 18:08

    Hi Middle,

    Great article and it has helped me plenty to find my feet with MEF.

    But for the life of me I just can't get mine working. The Link loads perfectly and from debugging the Plugins and also the DownloadCompleted in the DeploymentCatalogServices.cs all seems fine. When I click on the link is where things just falls over, I get the error The type '', specified in the x:Class of '' could not be found in any loaded assembly. I've even used the dissambler to make sure that my export tag is correct.

    Is there anything that you can think of that I might be missing. I see that you used a Silverlight Class library and then included it in a Silverlight Application for the SupplierPackage, is there any reason for doing that? If I just use a Silverlight Application will that have an effect?

     

    Thanks in advance. 

  • RE: Navigating between Pages in Different Xaps (by using MEF)  

    posted by Anton on Aug 13, 2010 18:22

    Hi Middle,

    Just realized that I can eliminate what the cause is by adding the plugin as a reference. Once I did that it worked and the error was gone, so the download of the xap doesn't seem to work.

    Not your problem though, will try to figure it out.

    Thanks again, this article was a great help.

Add Comment

 
 

   
  
  
   
Please add 1 and 2 and type the answer here:

Watch a recording of Gill Cleeren's recent SilverlightShow webinar 'Data Binding in Action'.
Sign up for the next upcoming free SilverlightShow webinar event 'Sketchflow in Real Scenarios' with presenter Braulio Diez. (hide this)