Integrating Prism v4 Region Navigation with Silverlight Frame Navigation with Dynamic Module

Topics: Prism v4 - Silverlight 4
Jan 10, 2011 at 1:13 PM

Hi,

I am trying to modify the sample posted in http://blogs.msdn.com/b/kashiffl/archive/2010/10/05/integrating-prism-v4-region-navigation-with-silverlight-frame-navigation.aspx to support Dynamic Modules. I tried loading the dynamic module in  FrameNavigationWrapperPage.xaml.cs->OnNavigatingFrom function as well as in FrameContentLoader.cs->BeginLoad function. But it is not working as expected.

Can some one guide me on this?

Regards,

Madhan

Developer
Jan 10, 2011 at 6:04 PM

Hi Madhan,

You might find the "Loading Modules on Demand" section of the Modularity Chapter from the Prism MSDN documentation useful, as it explains what's necessary to do in order to load modules on demand. Also, you could check the "Downloading Remote Modules in the Background" if you're downloading the modules in the background, and the "Modules in MEF" section if you're using MEF.

If you continue experiencing problems, it could be helpful if you could provide us with more details on why your approach is not working as expected, so we can help you fulfill your requirements.

I hope you find this helpful.

Guido Leandro Maliandi
http://blogs.southworks.net/gmaliandi

Jan 11, 2011 at 7:19 AM

Hi,

Following are the changes I did on the example implementation provided.

1) I created a new module with XAP name as "ThePhoneCompany.TopMenuRemoteContent.xap". Module class name is "TopMenuRemoteContentModule". I created on Silverlight user control with name "SampleView". Added the module to be copied into ClientBin of "ThePhoneCompany.Web" project

2) Added my module in Bootstrapper.cs->InitializeModules() method

            ModuleInfo m = new ModuleInfo();
            m.Ref = "ThePhoneCompany.TopMenuRemoteContent.xap";
            m.ModuleName = "TopMenuRemoteContentModule";
            m.InitializationMode = InitializationMode.OnDemand;
            this.ModuleCatalog.AddModule(m);

3) Added menu item to navigate to the "SamleView" UI

<Rectangle Style="{StaticResource DividerStyle}"/>
                    <HyperlinkButton x:Name="hlbInventory1" Style="{StaticResource LinkStyle}" NavigateUri="/TopMenuRemoteContent/SampleView" TargetName="ContentFrame" Content="Content"/>

4) In FrameNavigationWrapperPage.xaml.cs->OnNavigatingFrom function, I wrote logic to load the module.

I am getting error in FrameContentLoader.cs->BeginLoad function since the module load is not completed before calling this function.

I am not sure how to make sure that Module is loaded before BeginLoad function is called.

As an alternative, I tried clicking the menu second time so that module will be already loaded in previous call. Event in that scenario, the view is not resolved and BeginLoad call failed.

Let me know in case you need additional details.

My intention here is, my view might be defined in already loaded module or yet to be loaded dynamic module. I will load the module and then initiate the navigation.

Regards,

Madhan

Jan 11, 2011 at 9:33 AM

Hi,

I did one mistake in the first point. I missed add Export attribute for 'SampleView'. So now I am able to load the view in the second click of the menu since the module will be loaded by that time.

I need help in making the dynamic load of module call correctly and make sure that module is loaded before Navigation events are getting fired.

Regards,

Madhan

Developer
Jan 11, 2011 at 5:44 PM
Edited Jan 11, 2011 at 5:48 PM

Hi Madhan,

One possible way of implementing the scenario you're trying to achieve would be to check if the module that contains that view has been loaded in the ConfirmNavigation method. That way, you would call the continuationCallback method only when the module has been downloaded, and proceed with the navigation then.

From Karl Shifflett's post:

"Intercept the navigation request, verify the assembly is loaded then proceed with the navigation request. If the assembly is not loaded or is in the process of loading, you can wait until the assembly loads, and then proceed with the request.

  • MEF Catalogs have a Changed event that you can hook to determine the status of assembly loading."

Another possibility would be to start downloading the module when you click on the menu, and subscribe to an event indicating that it's completed downloading (for example, the event mentioned by Karl Shifflett in case of MEF). Then, you could initiate the navigation request in the handler for that event, so that the navigation is carried out only when that module has been loaded.

I hope you find this helpful.

Guido Leandro Maliandi
http://blogs.southworks.net/gmaliandi

Jan 12, 2011 at 6:51 AM

Hi Guido Leandro Maliandi,

I tired to handle the download in OnNavigatingFrom function. Basically, I had a ModuleStatus type which will keep track of the model load status. I will register that as Single instance with Container. I wrote below code to wait for module to load and then proceed with navigation. But, I was not able to achieve the required functionality. What am I missing here?

protected override void OnNavigatingFrom(NavigatingCancelEventArgs e) {
            ModuleStatus moduleStatus = ServiceLocator.Current.GetInstance<ModuleStatus>();
            this._manager = ServiceLocator.Current.GetInstance<IModuleManager>();
            this._manager.LoadModuleCompleted +=new EventHandler<LoadModuleCompletedEventArgs>(_manager_LoadModuleCompleted);
            this._manager.ModuleDownloadProgressChanged += new EventHandler<ModuleDownloadProgressChangedEventArgs>(_manager_ModuleDownloadProgressChanged);
            _manager.LoadModule(e.Uri.ToString().Split('/')[1] + "Module");
           
            // Do the check in another thread.
            System.Threading.ThreadPool.QueueUserWorkItem(delegate
            {
                int ctr = 0;

                while (!moduleStatus.IsModuleLoaded(e.Uri.ToString().Split('/')[1] + "Module") && ctr < 20)
                {
                    Debug.WriteLine("Waiting for module '" + e.Uri.ToString().Split('/')[1] + "Module' to get loaded");
                    Thread.Sleep(1000);
                    ctr++;
                }

                Debug.WriteLine("module loading is completed and proceeding with navigation logic");

                this.Dispatcher.BeginInvoke(() =>
                {
                    Debug.WriteLine("Run regular logic");
                    Execute<IConfirmNavigationRequest>(
                ina =>
                {
                    Boolean? result = null;
                    Boolean ignore = false;

                    ina.ConfirmNavigationRequest(
                        this.CreateNavigationContext(e.Uri),
                        r =>
                        {
                            if (!ignore)
                            {
                                result = r;
                            }
                        });

                    ignore = true;
                    Debug.WriteLine(result);
                    if (result == false)
                    {
                        e.Cancel = true;
                    }
                    Debug.WriteLine(e.Cancel);
                });
                    Debug.WriteLine(e.Cancel);
                });
            });
            Debug.WriteLine(e.Cancel);
            Debug.WriteLine("OnNavigatingFrom is completed");
        }

 

Regards,

Madhan

Jan 12, 2011 at 2:46 PM

Hi Guido Leandro Maliandi,

Finally, I have reached the solution.

1) Created a ModuleStatus class for tracking the modules loading. I will register object of this class as Single instance with Container. In Module initialize, resolve this object and add the load status information.

2) In FrameRegionSyncBehavior,-> OnFrameNavigating method, check for the module availability for the Navigation URI. If module is not loaded already, cancel the naviation and load the module.

3) In module completion event, reinitiate the navigation with the same URI which call OnFrameNavigating method and navigation proceeds this time.

I tested with different scenarios like, remote module for Silverlight naviation, remote module for prism naviation, refresh scenario. It works fine.

Hope this approach is fine.

Regards,

Madhan

Developer
Jan 12, 2011 at 5:30 PM

Hi Madhan,

Your approach seems a valid possibility for achieving your results. I'm glad that you've found a way to solve it, and I thank you for sharing your findings with the rest of the community, as other users that face similar scenarios could benefit from this.

Thanks,

Guido Leandro Maliandi
http://blogs.southworks.net/gmaliandi

Feb 8, 2011 at 6:22 AM
Edited Feb 8, 2011 at 6:29 AM

Hi Madhan,

I am looking for a very similar solution, can you post some sample code? It will save me a lot of time.

Thanks,
Salman

Feb 8, 2011 at 5:36 PM

Hi,

Step 1: Created ModuleStatus class in ThePhoneCompany.Common project

public class ModuleStatus
    {
        List<string> modulesInitialized = new List<string>();

        public void AddModule(Type t)
        {
            modulesInitialized.Add(t.Name);
        }

        public bool IsModuleLoaded(string moduleName)
        {
            bool result = (from name in modulesInitialized
                           where moduleName.Equals(name)
                           select name).Count() > 0;

            return result;
        }

        public int ModulesInitialized
        {
            get { return this.modulesInitialized.Count; }
        }
    }

Step 2: In Bootstrapper.cs in ThePhoneCompany project, added code to create ModuleStatus instance and register as PRISM service

protected override void ConfigureContainer()
        {
            base.ConfigureContainer();
           
            ModuleStatus status = new ModuleStatus();
            this.Container.ComposeExportedValue<ModuleStatus>(status);
        }

Step 3: Added code in all module initialization to resolve the modulestatus instance and use the functions to register the module initialization status as true

public void Initialize()
        {
            ModuleStatus moduleStatus = ServiceLocator.Current.GetInstance<ModuleStatus>();
            moduleStatus.AddModule(this.GetType());
        }

 

Step 4: In FrameRegionSyncBehavior.cs in Prismv4FrameRegionNavigation project, updated the OnFrameNavigating function

void OnFrameNavigating(Object sender, EventArgs e) {
            ModuleStatus moduleStatus = ServiceLocator.Current.GetInstance<ModuleStatus>();

            if (!moduleStatus.IsModuleLoaded(((NavigatingCancelEventArgs)e).Uri.ToString().Split('/')[1] + "Module"))
            {
                foreach (var c in this._catalog.Modules)
                {
                    if (c.ModuleName == ((NavigatingCancelEventArgs) e).Uri.ToString().Split('/')[1] + "Module")
                    {
                        _manager.LoadModule(((NavigatingCancelEventArgs) e).Uri.ToString().Split('/')[1] + "Module");
                        ((NavigatingCancelEventArgs) e).Cancel = true;
                        u = ((NavigatingCancelEventArgs) e).Uri;
                        return;
                    }
                }
            }

            this._isNavigating = true;
        }

Step 5: In FrameRegionSyncBehavior.cs in Prismv4FrameRegionNavigation project, created private Uri variable

private Uri u;

Step 6: In FrameRegionSyncBehavior.cs in Prismv4FrameRegionNavigation project, added code for module load completion

void _manager_LoadModuleCompleted(object sender, LoadModuleCompletedEventArgs e)
        {
            this._hostControl.Navigate(u);
        }

Hope above steps help you to resolve the issue.

 

Regards,

Madhan

 

Feb 9, 2011 at 4:37 AM

Thanks!

Feb 14, 2011 at 12:17 PM

Hi Madhan,

thanks for the example code.  I was looking for pretty much the same thing.

Unfortunately I seem to be missing some info, because in FrameRegionSyncBehavior in OnFrameNavigating the following objects are unknown:

this._catalog  and

_manager

 

Where or rather how do you define them?

 

Greeting,

Michael.

Feb 14, 2011 at 4:15 PM
Edited Feb 14, 2011 at 4:18 PM

Hi Michael,

In FrameRegionSyncBehavior.cs, declare the following variables.

private IModuleManager _manager;
        private IModuleCatalog _catalog;

 

In OnAttach() function, resolve the module manager and module catalog prism services.

 this._manager = ServiceLocator.Current.GetInstance<IModuleManager>();
            this._manager.LoadModuleCompleted += new EventHandler<LoadModuleCompletedEventArgs>(_manager_LoadModuleCompleted);
            this._catalog = ServiceLocator.Current.GetInstance<IModuleCatalog>();

 

Hope this helps.

Regards,

Madhan

Feb 14, 2011 at 6:20 PM

Wanted to say that I just worked through this and it all works perfectly!

Also, Your implementation here also supports loading modules from silver-light class libraries that are referenced directly by the shell.  

Thank you for taking the time to post as I am sure it will help many people out... like me!

ACanadian

Feb 14, 2011 at 6:30 PM

whoops, forgot to mention on thing.

I plan to try and display a Module loading page while a module is being loaded dynamically.   Should be a matter of navigating the host control to a page in the shell, then when LoadModuleComplete is called the host control will navigate appropriately....  

Feb 14, 2011 at 6:50 PM
Edited Feb 14, 2011 at 6:52 PM

 

Well that was easy... unless I am doing something that I shouldn't :)

1) Created a new view called: ModuleLoadingView in the shell.

2) Ensured the view was exported, kept it as Shared so that it would be reused

 

    /// <summary>
    /// The Export attribute exports the ModuleLoadingView using the full type name as the key
    ///    
    /// </summary>
    [Export(typeof(ModuleLoadingView))]
    [PartCreationPolicy(CreationPolicy.Shared)]

 

3) Modified the OnFrameNavigating to: (Change in bold)

    ModuleStatus moduleStatus = ServiceLocator.Current.GetInstance<ModuleStatus>();

    if (!moduleStatus.IsModuleLoaded(((NavigatingCancelEventArgs)e).Uri.ToString().Split('/')[1] + "Module"))
    {
       foreach (var c in this._catalog.Modules)
       {
          if (c.ModuleName == ((NavigatingCancelEventArgs)e).Uri.ToString().Split('/')[1] + "Module")
          {
             //Navigate to loading page
             this._hostControl.Navigate(new Uri("/ModuleLoadingView",UriKind.Relative));

             _manager.LoadModule(((NavigatingCancelEventArgs)e).Uri.ToString().Split('/')[1] + "Module");
             ((NavigatingCancelEventArgs)e).Cancel = true;
             u = ((NavigatingCancelEventArgs)e).Uri;

             return;
          }
       }
    }

    this._isNavigating = true;
Feb 19, 2011 at 6:29 PM

Running into a situation where the dynamically loaded modules MEF exports are not getting into the compostibale parts catalogs?.    Essentially I am attempting to build my own module that loads dynamically using the inventory module as a template.

The problem I am having is I'm trying to use the region manager request navigate function from a dynamically loaded module and it fails right away.

_regionManager.RequestNavigate(RegionNames.MainContentRegion, Constants.AdministrationHomeView, PostNavigationCallback);

 void PostNavigationCallback(NavigationResult navigationResult)
        {
            if (navigationResult.Result == true)
                MessageBox.Show("Navigation Successful");
            else
                MessageBox.Show("Navigation Failed");
        }


In the post navigation call back I noticed that the NavigationResult objects context property does not have a reference to a navigation service. 
Wondering what I am missing?

The constant value is relative URI path: /Administration/AdministrationView

I have confirmend that I am successfully importing the _regionManager in the ctor... which means MEF must be working on imports..?

[ImportingConstructor]
public UserViewModel(
      IRegionManager regionManager
      )
{

 _regionManager = regionManager;

}

My view model is implementing the following interfaces: 
INavigationAwareIPageTitleIRegionMemberLifetime

 

Jan 17, 2012 at 12:13 AM

Hi anyone, please read this, Karl Shifflett has made great work on this:

http://blogs.msdn.com/b/kashiffl/archive/2010/10/05/integrating-prism-v4-region-navigation-with-silverlight-frame-navigation.aspx

Mar 18, 2012 at 1:59 PM

Created a framework for creating Navigation based Silverlight application using prism easily.  It is partly based on Karl Shifflett's solution.  Thought I might share it.

http://ultimateframework.codeplex.com/