Show loading while downloading XAP modules in prism.

4 09 2010

Show loading while downloading XAP modules in prism.

Scenario : To show progress bar when modules (xap’s) in prism are downloaded.

Problem : Currently in prism the default ModuleManager class does not expose a progress event that can be tracked to show some progressing of the modules (xap’s) being downloaded.

Solution : Prism’s ModuleManager internally uses instance of XapModuleTypeLoader to load modules (xap’s) asynchronously. Thanks to the community of prism for providing CreateDownloader() as virtual method.

Straight to the point I will jot down the steps to achieve the above mentioned goal.

Step 1 – Create an event and an event argument classes that will be used in custom class below


public class ModuleDownloadProgressEvent : CompositePresentationEvent<ModuleDownloadProgressArgs>
{
}

public class ModuleDownloadProgressArgs
{
    public ModuleDownloadProgressArgs(int BytesReceived, bool IsComplete) { _BytesReceived = BytesReceived; _IsComplete = IsComplete; }
    private int _BytesReceived;
    public int BytesReceived { get { return _BytesReceived; } set { _BytesReceived = value; } }
    private bool _IsComplete;
    public bool IsComplete { get { return _IsComplete; } set { _IsComplete = value; } }
}

Step 2 – Create a class that derives from IFileDownloader.


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

    public CustomFileDownloader()
    {
        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);
    }
}

Step 3 – Create a class that derives from XapModuleTypeLoader and override its virtual method CreateDownloader returning your class object prepared in step 1


public class CustomXapModuleTypeLoader : XapModuleTypeLoader
{
    protected override IFileDownloader CreateDownloader()
    {
        return new CustomFileDownloader();
    }
}

Step 4 – Create a class that derives from ModuleManager and override its virtual method ModuleTypeLoaders replacing default XapModuleTypeLoader with your class object prepared in step 2.


public class CustomModuleManager : ModuleManager
{
    public CustomModuleManager(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 CustomXapModuleTypeLoader()
                                      };
            }

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

Step 5 – Register your custom ModuleManager class prepared in step 4, in overridden method ConfigureContainer() of your Bootstrapper class.


	base.RegisterTypeIfMissing(typeof(IModuleManager), typeof(CustomModuleManager), true);

Step 6 – Create a method in Shell that will respond to the ModuleDownloadProgressEvent event when published


public void ShowHide(ModuleDownloadProgressArgs e)
{
	// Your Dream Goes Here
}

Step 7 – Finally in the Shell subscribe to the ModuleDownloadProgressEvent event prepared in Step 1.


	eventAggregator.GetEvent<ModuleDownloadProgressEvent>().Subscribe(ShowHide, ThreadOption.UIThread);

That’s it!!!

Enjoy Prism.