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;
            }
        }