OnDemand Download and Progressbar

One of the flexibility that ClickOnce provides as a deployment technology is on-demand download of parts of application. This is especially useful in XBAP(Xaml Browser Applications) that are built with Windows Presentation Foundation. This can enable scenarios such as building a small exe that does custom progressbar while app is being downloaded in the background. This Exe can then show message that you want user to see while app is being downloaded. It can also cut down intial download of the application as user wont have to pay up front for the pieces of application that rarely gets used. This sample provided does following things…

  • It creates specifies a dll that is part of the application as on-demand component. This means this dll wont get installed as part of the initial install. When you build application you would also create filegroup as unit of download. For this sample dll is only member of this group but you can create more files to be part of the group
  • Page that has progressbar and two button
  • Button that navigates to page in the on demand dll. This will fail initially (expected) since the dll is not there.
  • Use ApplicationDeployment class to do the on-demand download of dll when user click on the button to download and navigate
  • It also shows the progress bar using threading model for avalon application

Part1: Specify DLL as on demand component

For this after creating either adding a reference directly to your DLL or adding a project reference, you will go in Project Properties/Publish/Application Files dialog. You will change the download group for the dll from “(Required)” to name that you want to give. I use the name “OnDemand Group”.

 Project Properties  

This makes manifest generation task during MSBUILD build process to use this information to generate appropritate manifest entries. Generated manifest can look like this. Part in red is what makes the dll to be not installed at installation time.

<dependency optional=”true”>
        <dependentAssembly dependencyType=”install” allowDelayedBinding=”true” codebase=”OnDemandComponent.dll” size=”20480″ group=”OnDemandGroup”>
            <assemblyIdentity name=”OnDemandComponent” version=”1.0.2377.21815″ language=”neutral” processorArchitecture=”msil” />
            <hash>
                <dsig:Transforms>
                    <dsig:Transform Algorithm=”urn:schemas-microsoft-com:HashTransforms.Identity” />
                </dsig:Transforms>
                <dsig:DigestMethod Algorithm=”
http://www.w3.org/2000/09/xmldsig#sha1” />
                <dsig:DigestValue>Lb1pSEc1s3OAvZi0lbHezfba/B8=</dsig:DigestValue>
            </hash>
        </dependentAssembly>
    </dependency>
    <dependency>
        <dependentAssembly dependencyType=”install” allowDelayedBinding=”true” codebase=”OnDemandProgressBar.exe” size=”24576″>
            <assemblyIdentity name=”OnDemandProgressBar” version=”1.0.2377.22267″ language=”neutral” processorArchitecture=”msil” />
            <hash>
                <dsig:Transforms>
                    <dsig:Transform Algorithm=”urn:schemas-microsoft-com:HashTransforms.Identity” />
                </dsig:Transforms>
                <dsig:DigestMethod Algorithm=”
http://www.w3.org/2000/09/xmldsig#sha1” />
                <dsig:DigestValue>JIzqtjUQjQTZHG8u0vSgtySxxhI=</dsig:DigestValue>
            </hash>
        </dependentAssembly>
    </dependency>

Here is the rest of the important source code

Part 2: Page with Progressbar

This page is simple. Has a progressbar and two buttons. One button navigates to page in dll without doing the download, other one does download and then navigates to the page.

<Page
    xmlns=”
http://schemas.microsoft.com/winfx/2006/xaml/presentation
    xmlns:x=”
http://schemas.microsoft.com/winfx/2006/xaml” Height=”359″ Width=”551″
    x:Class=”OnDemandProgressBar.Page1″>
  <StackPanel>
    <StackPanel  Width=”500″>
      <TextBlock Name=”txtMessage”>On Demand Sample</TextBlock>
    </StackPanel>
    <StackPanel  Width=”500″>
      <ProgressBar Name=”dpProgress” Height=”50″ Width=”500″/>
    </StackPanel>
    <StackPanel  Width=”500″>
      <Button Name=”btnNavigate” Click=”OnNavigateClick” Width=”500″>Navigate Without Download</Button>
      <Button Name=”btnDownload” Click=”OnDownloadClick” Width=”500″>Navigate to Next DLL using Application Deployment</Button>
    </StackPanel>
  </StackPanel>
</Page>

Part 3: Button that navigates to page in on-demand dll

This part of the code navigates to page in on-demand dll. This is expected to fail before the download is done since dll is not there.

        private void OnNavigateClick(object sender, RoutedEventArgs e)
      {
          this.NavigationService.Navigate(new Uri(“/OnDemandComponent;component/ComponentPage.xaml”, UriKind.Relative));
      }

Part 4: Button that navigates to page in on-demand dll

This part of the code uses ApplicationDeployment class from .Net 2.0 to do the on-demand download of the specified group. 

        private void OnDownloadClick(object sender, RoutedEventArgs e)
        {
            try
            {

                ApplicationDeployment d = ApplicationDeployment.CurrentDeployment;

                if (d != null)
                {
                    //this.txtMessage.Text = “Deployment Manager Created”;
                    try
                    {

                        d.DownloadFileGroupProgressChanged += new DeploymentProgressChangedEventHandler(d_ProgressChanged);
                        d.DownloadFileGroupCompleted += new DownloadFileGroupCompletedEventHandler (d_SynchronizeCompleted);
                        d.DownloadFileGroupAsync(“OnDemandGroup”);

                    }
                    catch (Exception deploymenterror)
                    {
                        this.txtMessage.Text = deploymenterror.Message;
                    }
                }
            }
            catch (Exception dmerr)
            {
                this.txtMessage.Text = dmerr.Message;
            }
        } 

Part 5: Updating Progress bar using dispatcher

This part uses dispatcher class to so that progress UI can be updated from the thread that is executing downloadfilegroup call.

        private void d_ProgressChanged(object sender, DeploymentProgressChangedEventArgs e)
        {
            this.Dispatcher.BeginInvoke(DispatcherPriority.Send, new DispatcherOperationCallback(UpdateProgressUI), e);
        }

        private void d_SynchronizeCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
        {
            this.Dispatcher.BeginInvoke(DispatcherPriority.Send, new DispatcherOperationCallback(FinishProgressUI), e);
        }

        private object UpdateProgressUI(object obj)
        {
            DeploymentProgressChangedEventArgs d1 = obj as DeploymentProgressChangedEventArgs;
            this.txtMessage.Text = “Percentage Complete: ” + d1.ProgressPercentage.ToString() + ”   Total Number Of Bytes: ” + d1.BytesTotal.ToString() + ”   Total Number Of Bytes Downloaded: ” + d1.BytesCompleted.ToString(); ;
            double FinalValue = d1.ProgressPercentage * 10;
            this.dpProgress.Value = FinalValue;
            return obj;
        }

        private object FinishProgressUI(object obj)
        {
            this.NavigationService.Navigate(new Uri(“pack://application:,,,/OnDemandComponent;component/ComponentPage.xaml”, UriKind.Absolute));
            return obj;
        }


        private void OnTestClick(object sender, RoutedEventArgs e)
        {
            try
            {
                this.txtMessage.Text = AppDomain.CurrentDomain.ActivationContext.Identity.CodeBase;
            }
            catch (Exception err)
            {
                this.txtMessage.Text = err.Message;
            }
        }

Advertisements

5 Responses to “OnDemand Download and Progressbar”

  1. Rolf Says:

    Great sample here!

    One question: I understand that the Internet security settings enforce some kind of 512KB limit. Will you get an exception if your OnDemandComponent DLL exceeds that?

  2. Bombayboy Says:

    512KB limit applies for isolated storage and not for the app cache. App cache does have a limit of 100MB that means only 100MB worth of online application can be stored at any given time in ClickOnce cache and if cache goes above it, scavanging will kick off. OnDemandComponent Dll since it is part of application (by that i mean, it is described in the manifest), it wont count again 512 KB limit but it will count against 100MB limit. I hope this answers your question

  3. russland Says:

    hi, do you think you could expose the files or make the available for download. I cannot attacht an event to the DownloadFileGroupAsync that would update the progressbar?

    I’d appreciate big time.

    thanks

  4. russland Says:

    uhhh… too tired to write gramatically correct. sorry. I actually wanted to ask how you do that with a SmartClient.

    I succeed in downloading the assembly but fail in displaying the progress of the download. The eventhandler DownloadFileGroupProgressChanged obviously doesn’t work in my example.

    //DllMapping is a Dictionary object // since I have more than one assembly to download

    DllMapping[“AdminModule”] = “AdminModule”;
    AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(AssemblyResolve_AdminModule);

    Any clue what’s wrong with that?

    Assembly AssemblyResolve_AdminModule(object sender, ResolveEventArgs args)
    {
    Assembly newAssembly = null;
    if (ApplicationDeployment.IsNetworkDeployed)
    {
    ApplicationDeployment da = ApplicationDeployment.CurrentDeployment;
    // Get the DLL name from the Name argument.
    string[] nameParts = args.Name.Split(‘,’);
    string dllName = nameParts[0];
    string downloadGroupName = DllMapping[dllName];

    try
    {
    da.DownloadFileGroupAsync(downloadGroupName);
    da.DownloadFileGroupProgressChanged += new DeploymentProgressChangedEventHandler(d_DownloadFileGroupProgressChanged);
    da.DownloadFileGroupCompleted += new DownloadFileGroupCompletedEventHandler(d_DownloadFileGroupCompleted);
    da.DownloadFileGroupAsync(“AdminModule”);
    }
    catch (DeploymentException de)
    {
    MessageBox.Show(“Downloading file group failed. Group name: ” + downloadGroupName + “; DLL name: ” + args.Name);
    throw (de);
    }
    // Load the assembly.
    // Assembly.Load() doesn’t work here, as the previous failure to load the assembly
    // is cached by the CLR. LoadFrom() is not recommended. Use LoadFile() instead.
    try
    {
    newAssembly = Assembly.LoadFile(Application.StartupPath + @”\” + dllName + “.dll”);
    }
    catch (Exception e)
    {
    throw (e);
    }
    }
    else
    {
    //Major error – not running under ClickOnce, but missing assembly. Don’t know how to recover.
    throw (new Exception(“Cannot load assemblies dynamically – application is not deployed using ClickOnce.”));
    }
    return (newAssembly);
    }

  5. bombayboy Says:

    I dont think your code will work because DownloadFileGroupAsync is Async call once you call into it your assembly resolve event handler will continue executing. so your code to load assembly will fail if the file is not already downloaded. You could use DownloadFileGroup instead of DownloadfileGroupAsync but that means you wont get progress events since the files will be downloaded as a synchornous call.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: