ModuleManager Download Progress

Topics: Prism v2 - Silverlight 2
Feb 20, 2009 at 2:35 AM
I really like the ease of downloading modules on demand that the Module Manager offers, however for Silverlight it is almost necessary to provide some type of visual feedback that the application is downloading something.  This is particularly important when downloading large modules.  The Module Manager does not expose a progress event so my first inclination was to create a custom IModuleTypeLoader.  After looking into this it appears I would have to make changes in more classes than just my custom impementation of IModuleTypeLoader.   Has anyone made similar modification and if so could you offer me a few suggestions? 
Feb 20, 2009 at 7:13 PM
What you need to do are the following steps:

* Create a class that derrives from IFileDownloader. You can use the code in FileDownloader for inspriration.
* Catch the progress events you're interested and publish them to the outside world. Using EventAggregator would be a non-invasive option for that..
* Create a class that derrives from XapModuleTypeLoader and override the CreateDownloader() method. Return your own FileDownloader
* Replace the XapModuleTypeLoader in the ModuleTypeLoader list with your custom XapFileDownloader.

Hope this helps,

_Err

Feb 22, 2009 at 5:17 AM
Thank you that was helpful.  It took a little time to figure out the extensibility points, but with your instructions and a little elbow grease it worked.  Thank you for the reply!
Jul 17, 2009 at 7:26 PM

Hi, I was wondering if you could elaborate on this solution.  I don't know how to inject an EventAggregator into my derived XapModuleTypeLoader.  I am also assuming that I have to derive from ModuleManager to override the ModuleTypeLoader but I don't know how to get a UnityContainer to create my new derived XapModuleTypeLoader.  If you could provide an example of this I would greatly appreciate it.  I am trying to do this in my own library so that I don't recompile the CAL assemblies.

Thanks advance!

Jul 18, 2009 at 4:55 AM

Actually,it has been awhile since I wrote that, but I was able to find it.  I am not sure I included all the relevant classes, but below you will find three I know are involved.  Since you are not able to inject EventAggregator in the constructor, I used the ServiceLocator to get it.  I think another alternative could be to use property injection with a buildup method.   ServiceLocator works great though.  The idea is to publish event using eventagg and then subscribe to that event in one of your view models.  In my case I think I had some progress spinner that was bound to an int Progress property in my ApplicationViewModel where the view is Shell.xaml.  I registered for the event in ApplicationViewModel and set the progress property from the event handler.  I also think I made a IntegerToVisibility Converter that i used for the binding between the progress control and the progress property.  Basically return (value >0 && <100) ? Visibility.Visible : Visibility.Collapsed.

If I remember right, I was uncertain about overriding ModuleTypeLoaders property because I would have preferred to just add a new IModuleTypeLoader to it since it is an IEnumerable, rather than overriding the property.  For some reason I decided to just override the property which works, but there is likely a better way to accomplish this that I missed.  .....Hope this helps! 

public class VerbFileDownloader : IFileDownloader
    {
        private readonly WebClient webClient = new WebClient();
        private readonly IEventAggregator eventAgg;

        public VerbFileDownloader()
        {
            eventAgg = ServiceLocator.Current.GetInstance<IEventAggregator>();
        }
       
        private event EventHandler<DownloadCompletedEventArgs> _downloadCompleted;
        public event EventHandler<DownloadCompletedEventArgs> DownloadCompleted
        {
            add
            {
                if (this._downloadCompleted == null)
                {
                    this.webClient.OpenReadCompleted += this.WebClient_OpenReadCompleted;
                    this.webClient.DownloadProgressChanged += this.DownloadProgressChanged;
                }

                this._downloadCompleted += value;
            }

            remove
            {
                this._downloadCompleted -= value;
                if (this._downloadCompleted == null)
                {
                    this.webClient.OpenReadCompleted -= this.WebClient_OpenReadCompleted;
                    this.webClient.DownloadProgressChanged -= this.DownloadProgressChanged;
                }
            }
        }

        void DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
        {
            RaiseDownloadProgressChanged(e.ProgressPercentage, false);
        }

        private void RaiseDownloadProgressChanged(int val, bool isComplete)
        {
            eventAgg.GetEvent<ModuleDownloadProgressEvent>().Publish(new ModuleDownloadProgressArgs(val, isComplete));
        }

        public void DownloadAsync(Uri uri, object userToken)
        {
            this.webClient.OpenReadAsync(uri, userToken);
        }

        private void WebClient_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
        {
            this._downloadCompleted(this, ConvertArgs(e));
            RaiseDownloadProgressChanged(0, true);
        }

        private static DownloadCompletedEventArgs ConvertArgs(OpenReadCompletedEventArgs args)
        {
            return new DownloadCompletedEventArgs(args.Error == null ? args.Result : null, args.Error, args.Cancelled, args.UserState);
        }
    }

 

public class VerbModuleManager : ModuleManager
    {
        public VerbModuleManager(IModuleInitializer moduleInitializer, IModuleCatalog moduleCatalog, ILoggerFacade loggerFacade)
            : base(moduleInitializer, moduleCatalog, loggerFacade) { }
        private System.Collections.Generic.IEnumerable<IModuleTypeLoader> typeLoaders;
        public override System.Collections.Generic.IEnumerable<IModuleTypeLoader> ModuleTypeLoaders
        {
            get
            {
                if (this.typeLoaders == null)
                {
                    this.typeLoaders = new List<IModuleTypeLoader>()
                                          {
                                              new VerbXapModuleTypeLoader()
                                          };
                }

                return this.typeLoaders;
            }
            set
            {
                this.typeLoaders = value;
            }
        }
    }

 public class VerbXapModuleTypeLoader : XapModuleTypeLoader
    {
        protected override IFileDownloader CreateDownloader()
        {
            return new VerbFileDownloader();
           
        }
    }

 

Jul 18, 2009 at 9:43 PM
Edited Jul 18, 2009 at 10:06 PM

Thanks so much for your response.  I have pretty much setup what you have described above but I am not sure how to get one of my modules to use the new ModuleManager that I have created.  I was using Constructor injection of the IModuleManager but it is never creating my manager?  Is there some place in the bootstrapper that needs to have this registered?

I also noticed that when the completed event fires, it is still too early.  I am trying to know when the file has been downloaded and initialized so that I can publish an event that the new downloaded module can handle?  I was trying to kill two birds with one stone:  1.  Download progress notification.  2.  Publish an event so that the new module would respond correctly.

Does this make sense or should I separate the two?

Thanks again for helping.

Jul 27, 2009 at 4:47 PM

Hi mkduffi2272,

Did you figure out your issue?  I too am trying to figure out how to inform my main application when a module has completed initialization.  I was thinking along the lines of extending the ModuleInitializer to dispatch an event folllowing the call to module.Initialise(), but the problem is how to subscribe to such an event.

Jul 27, 2009 at 5:35 PM

Sorry for late response...was on vacation :).   Regarding the IModuleManager, you override ConfigureContainer() in the bootstrapper similar to below.

protected override void ConfigureContainer()
        {
            this.UpdateMessage("Configuring Injection Container");
           
            //this.RegisterTypeIfMissing<IModuleLoader, SplashModuleLoader>(true);
            base.RegisterTypeIfMissing(typeof(IModuleInitializer), typeof(NotifyingModuleInitializer), true);
            base.RegisterTypeIfMissing(typeof(IModuleManager), typeof(VerbModuleManager), true);
            currentDispatcher.BeginInvoke(delegate { CreateCatalog(); });
           
            while (catalog == null)
            {
                Thread.CurrentThread.Join(50);
            }
            base.ConfigureContainer();
        }

Regarding the module initializing, one way is to just raise an event using event aggregator in the Initialize() of IModule.  Similar to the way you said above.  Maybe pass in the module name as a parameter to your event args class and wherever you subscribe to that event you just say eventAgg.GetEvent<EventType>().Subscribe(handler, UIThread, something, ()=> x.Name == "ModuleName");

When this event is raised you will know the module has been registered and ready for access.  If the eventAgg instructions are too vague I can send you an example, I am just running out the door.

Jul 28, 2009 at 9:45 AM
Edited Jul 28, 2009 at 10:26 AM

Thanks reddoglaw,

The problem i having is this:

I already have default classes registered with the container, but also need to load modules on demand which will override the defaults by registering the classes within the modules.  My model holds an xml config string which maps classes' keys against modules, so can be used to decide whether the relevant module has yet been downloaded.

I'm currently looking at overriding UnityContainer.Resolve(Type,string), which will check whether the relevant module is downloaded yet.  If not, I would like it to call moduleManager.LoadModule, and only return with the resolution once the module in question has finished initialising.  I have been able to get the event aggregator to publish the event at the end of module initialisation, what i can't do is figure out a way to handle that event in my extended unity container to make  the Resolve() call block until the initialisation is completed.

Thanks

Mark

Jul 28, 2009 at 2:20 PM

Thanks reddoglaw for your response.  It answered both my questions and I now have progress notification as well as initialization notification using event aggregators.

Regards,

Matt

Oct 23, 2009 at 11:51 AM

Hey - what would you guys think of this alternative strategy: (Note: this strategy relies on populating the ModuleInfo entries in the module catalog in code)

Create a module info replacement class that supports INotifyPropertyChanged and hook the changed event, listening for Status Changes?

That way you could update the UI progress and do whatever is needed once the module is done loading?

Mar 3, 2010 at 1:47 AM

I have implemented the above solution with an SL3 Toolkit "BusyIndicator" control like so:

_eventAggregator.GetEvent<ModuleDownloadProgressEvent>().Subscribe(args =>
{
        if (!args.IsFinishedDownloading)
                BusyWindow.IsBusy = true;    <== this line executes but progress window doesn't show up
        else
                BusyWindow.IsBusy = false;    <== this line executes correctly
}, true);

_verbmoduleManager.LoadModule("xyz");

 

If I just set BusyWindow.IsBusy = true before the call to LoadModule, it will not work because the download won't happen when the module was already cached, and the BusyWindow will be stuck open (yes it displays if set to busy like that).