Boostrapping Catalog from XAML in WPF

Topics: Prism v2 - WPF 3.5
Jun 5, 2009 at 2:09 AM

I'm trying to load a catalog from XAML in WPF.  I've done this successfully in Silverlight (mainly because the example's in the ref docs were for Silverlight) however when I try to do the equivalent in WPF I get the following error when the Boostrapper trys to load a module from the XAML derived catalog:

ModuleTypeLoaderNotFoundException
There is currently no moduleTypeLoader in the ModuleManager that can retrieve the specified module.

 

Here's what I have for the catalog (Catalog.xaml):  

<Modularity:ModuleCatalog 
               xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
               xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:sys="clr-namespace:System;assembly=mscorlib"
               xmlns:Modularity="clr-namespace:Microsoft.Practices.Composite.Modularity;assembly=Microsoft.Practices.Composite">

    <Modularity:ModuleInfo 
        Ref="Modules/MyAssembly.dll"
        ModuleName="ShellModule" 
        ModuleType="MyNamespace.MyModule, MyNamespace" />

</Modularity:ModuleCatalog>

It's loading OK from here (in the Bootstrapper):

 

        protected override IModuleCatalog GetModuleCatalog()
        {
            var uri = new Uri("/Catalogs/Catalog.xaml", UriKind.Relative);
            var catalog = ModuleCatalog.CreateFromXaml(uri);
            return catalog;
        }

A breakpoint shows that the catalog is actually loaded, and contains one item.  The exception is thrown from within 

    public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            new Bootstrapper().Run(); // Exception thrown from here.
        }
    }

Can someone point out what I'm doing wrong here.  Am I not declaring the XAML correctly?  Thanks.

PS: My module works fine when declared within App.config and bootstrapped via the 'ConfigurationModuleCatalog'.  However I want to do this via XAML so I have more control at time of boostrapping.  That is to say I want to decide to load different sets of modules defined in different XAML catalogs based on the startup criteria.  Is that a sensible plan? Or is there a better strategy to deal with this kind of behavior?  Thanks again.

Phil

 

 

 

 

Jun 5, 2009 at 9:40 PM

Hi Phil,

 

I have reproduced your issue and found a possible cause for it. The error you are getting requires two different things to happen:

1.       For some reason the class that implements IModule, is not available for instantiation.  The validation for that is the following:

protected virtual bool ModuleNeedsRetrieval(ModuleInfo moduleInfo)

        {

            if (moduleInfo.State == ModuleState.NotStarted)

            {

                // If we can instantiate the type, that means the module's assembly is already loaded into

                // the AppDomain and we don't need to retrieve it.

                bool isAvailable = Type.GetType(moduleInfo.ModuleType) != null; //This is where the assembly would not be available

                if (isAvailable)

                {

                    moduleInfo.State = ModuleState.ReadyForInitialization;

                }

 

                return !isAvailable;

            }

 

            return false;

        }

2.       If the aforementioned scenario took place the following would also need to happen to get the exception. The Ref attribute in your ModuleInfo does not start with file://. This is because the validation to check if a module can be loaded by the FileModuleTypeLoader is the following.

public bool CanLoadModuleType(ModuleInfo moduleInfo)

        {

            return moduleInfo.Ref != null && moduleInfo.Ref.StartsWith("file://", StringComparison.Ordinal);

        }

 

Please let me know if this helps.

 

Damian Schenkelman

http://blogs.southworks.net/dschenkelman

Jun 15, 2009 at 8:47 PM

Hi Damian,

Thanks a lot for taking the time to look into this and reproduce the error.  I'm still a confused from what you've said however.  By my reading you're saying I need to prepend the 'REF' attribute with a 'file://' to get something like this:

 

<Modularity:ModuleCatalog 
               xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
               xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:sys="clr-namespace:System;assembly=mscorlib"
               xmlns:Modularity="clr-namespace:Microsoft.Practices.Composite.Modularity;assembly=Microsoft.Practices.Composite">

    <Modularity:ModuleInfo 
        Ref="file://Modules/MyAssembly.dll"
        ModuleName="ShellModule" 
        ModuleType="MyNamespace.MyModule, MyNamespace" />
</Modularity:ModuleCatalog>

 

This however is not the absolute path fo the assembly.  I'm not sure how I would do this using relative pathing.  Are there any samples of WPF apps loading catalogs defined within XAML.  It's said to be possible...if I could re-work a code sample I'm sure I'd bypass a lot of confusing questions like these.

Thanks

Phil

 

 

 

May 5, 2010 at 5:48 PM

Hi Phill / Damian,

I am implementing prism for a GIS -WPF project.

Questions:

1. I am creating the new modules as C# class library projects, with a view (wpf user control), view model and model implenting a MVVM design pattern. Question is in a WPF application

are the MODULES created as CLASS Library Projects? If yes, then I am ok I think. If no, what is the project type.

2. What is the final solution to the problem stated above? Do I copy the assembly, *.dll, of the module into the shell Module folder and then use 'Ref="file://.....*.dll" '  ?

Jeetu

 

 

May 7, 2010 at 5:52 PM

Hi Jeetu,

I will try to answer your questions separately.

  1. There is no problem using Class Library project. Take into account that when you create a project with a template, it does not really matter whether it is a class Library, a WPF Control library or anything else. The differences are for example which references are added to your project by default.
  2. I have created a blog post talking about the question above and how to achieve that functionality. It does not provide any downloadable source, but I believe it can be useful.

Please let me know if this helps.

Damian Schenkelman
http://blogs.southworks.net/dschenkelman

Sep 28, 2010 at 9:40 PM

Damian,

  I'm having the same issue. My module is defined as such:

    [ModuleExport("AR", typeof(AccountsReceivableModule), InitializationMode = InitializationMode.OnDemand)]
    public class AccountsReceivableModule : IModule {

however when I call LoadModule like this (Key is "AR"):

_moduleManager.LoadModule(application.Key);

I'm getting an exception because ModuleManager.GetTypeLoaderForModule fails. My bootstrapper is loading modules like this:

        protected override void ConfigureAggregateCatalog() {
            base.ConfigureAggregateCatalog();

            AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(IUserService).Assembly));
            AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(Bootstrapper).Assembly));
            AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(AccountsReceivableModule).Assembly));
        }
This also leads me to another question. That first Add() is my Infrastructure assembly.  I just picked a service out of it to use typeof() on.  Is this how I'm supposed to do this?

 

Sep 28, 2010 at 9:59 PM

Could you post the full exception message text?  There are a couple of things that can cause the module loading to fail. 

Also, about that first Add call . . .  In the quickstarts and RIs we have used a concrete module type when we add assemblies to the catalog, but any type in the assembly should work.  You can also use a directory catalog or use other mechanisms like a config or xaml file to get types into the container.  The Modularity quickstarts and docs have more info on this.

 

 

Sep 29, 2010 at 4:52 PM
Edited Sep 29, 2010 at 5:07 PM

Here is the full text:

 

There is currently no moduleTypeLoader in the ModuleManager that can retrieve the specified module.

 

This is throw in ModuleManager.GetTypeLoaderForModule(ModuleInfo moduleInfo). I'm following the Modularirty Quickstart for MEF. Looks like my code/structure is the same so not really sure why this is occurring. Also, here is the whole code that loads modules:

        private void Open() {

            IsBusy = true;

            ((MyPrincipal)Thread.CurrentPrincipal).CurrentCompany = SelectedCompanyItem;

            Dispatcher.CurrentDispatcher.Invoke(
                DispatcherPriority.Background, new Action(() => {
                    foreach (var application in from application in SelectedCompanyItem.Applications
                                                where _moduleCatalog.Modules.Any(m => m.ModuleName == application.Key)
                                                select application) {
                        _moduleManager.LoadModule(application.Key);
                    }

                    IsBusy = false;
                })
                );
        }

So the code wouldn't call to load a module that doesn't exist because I'm only looping modules that match.

Sep 29, 2010 at 7:32 PM
Edited Sep 29, 2010 at 7:41 PM

I believe to have identified the problem. The problem lies in MefModuleManager.ModuleNeedsRetrieval(ModuleInfo moduleInfo). This is from that method:

 

                Lazy<IModule, IModuleExport> module =
                    this.ImportedModules.FirstOrDefault(
                        lazyModule => (lazyModule.Metadata.ModuleType.Name == moduleInfo.ModuleName));

 

what that needs to be is:

 

                Lazy<IModule, IModuleExport> module =
                    this.ImportedModules.FirstOrDefault(
                        lazyModule => (lazyModule.Metadata.ModuleName == moduleInfo.ModuleName));

The Prism code was not comparing ModuleNames but instead was comparing the ModuleName in moduleInfo with the name of the module's _type_ name. In my case since I explicitly set the name of my module, this broke while in the Modularity quickstart, it worked because the ModuleName was the same as the ModuleType.Name.

 

Found this as http://compositewpf.codeplex.com/workitem/7274