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”.
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;
}
}
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?
Rolf
July 15, 2006 at 11:51 pm
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
Bombayboy
July 17, 2006 at 9:42 am
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
russland
October 4, 2006 at 8:14 am
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);
}
russland
October 4, 2006 at 8:21 am
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.
bombayboy
October 5, 2006 at 10:48 am
I appreciate you taking the time to create this post. It has long been really useful to me in fact. Appreciate it.
Bernardine Galarneau
March 22, 2012 at 12:46 pm