Walkthrough: Downloading Assemblies On Demand with the ClickOnce Deployment API
By default, all of the assemblies included in a ClickOnce application are downloaded when the application is first run. You may, however, have parts of your application that are used by a small set of your users. In this case, you want to download an assembly only when you create one of its types. The following walkthrough demonstrates how to mark certain assemblies in your application as "optional", and how to download them using classes in the System.Deployment.Application namespace when the common language runtime (CLR) demands them.
Note |
---|
Your application will need to run in full trust to use this procedure. |
Creating the Projects
To create a project using an on-demand assembly
Open the .NET Framework SDK Command Prompt.
Using Notepad or another text editor, define a class named
DynamicClass
with a single property namedMessage
.Public Class DynamicClass Sub New() End Sub Public ReadOnly Property Message() As String Get Message = "Hello, world!" End Get End Property End Class
using System; using System.Collections.Generic; using System.Text; namespace Microsoft.Samples.ClickOnceOnDemand { public class DynamicClass { public DynamicClass() {} public string Message { get { return ("Hello, world!"); } } } }
Save the text as a file named ClickOnceOnLibrary.cs or ClickOnceLibraryvb, depending on the language you use.
Compile the file into an assembly.
csc /target:library ClickOnceLibrary.cs vbc /target:library ClickOnceLibrary.vb
Create a new file using your text editor and enter the following code, which creates a Windows Forms application that downloads the ClickOnceLibrary assembly when it is required.
Imports System Imports System.Windows.Forms Imports System.Deployment.Application Imports System.Drawing Imports System.Reflection Imports Microsoft.Samples.ClickOnceOnDemand Namespace Microsoft.Samples.ClickOnceOnDemand <System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand, Unrestricted:=true)> _ Class Form1 Inherits Form Public Sub New() ' Add button to form. Dim GetAssemblyButton As New Button() GetAssemblyButton.Location = New Point(100, 100) GetAssemblyButton.Text = "Get assembly on demand" AddHandler GetAssemblyButton.Click, AddressOf GetAssemblyButton_Click Me.Controls.Add(GetAssemblyButton) AddHandler AppDomain.CurrentDomain.AssemblyResolve, AddressOf CurrentDomain_AssemblyResolve End Sub <STAThread()> _ Shared Sub Main() Application.EnableVisualStyles() Application.Run(New Form1()) End Sub Private Function CurrentDomain_AssemblyResolve(ByVal sender As Object, ByVal e As ResolveEventArgs) As Assembly If ApplicationDeployment.IsNetworkDeployed Then Dim deploy As ApplicationDeployment = ApplicationDeployment.CurrentDeployment Try deploy.DownloadFileGroup(e.Name) Catch de As DeploymentException End Try ' Load the assembly. Dim newAssembly As Assembly = Nothing Try newAssembly = Assembly.Load(e.Name) Catch ex As Exception MessageBox.Show("Could not download assembly on demand.") End Try CurrentDomain_AssemblyResolve = newAssembly Else CurrentDomain_AssemblyResolve = Nothing End If End Function Private Sub GetAssemblyButton_Click(ByVal sender As Object, ByVal e As EventArgs) Dim ourClass As New DynamicClass() MessageBox.Show("DynamicClass string is: " + ourClass.Message) End Sub End Class End Namespace
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Reflection; using System.Deployment.Application; using Microsoft.Samples.ClickOnceOnDemand; namespace ClickOnceOnDemand { [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand, Unrestricted=true)] public class Form1 : Form { // Maintain a dictionary mapping DLL names to download file groups. This is trivial for this sample, // but will be important in real-world applications where a feature is spread across multiple DLLs, // and you want to download all DLLs for that feature in one shot. Dictionary<String, String> DllMapping = new Dictionary<String, String>(); public static void Main() { Form1 NewForm = new Form1(); Application.Run(NewForm); } public Form1() { // Configure form. this.Size = new Size(500, 200); Button getAssemblyButton = new Button(); getAssemblyButton.Size = new Size(130, getAssemblyButton.Size.Height); getAssemblyButton.Text = "Test Assembly"; getAssemblyButton.Location = new Point(50, 50); this.Controls.Add(getAssemblyButton); getAssemblyButton.Click += new EventHandler(getAssemblyButton_Click); DllMapping["ClickOnceLibrary"] = "ClickOnceLibrary"; AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve); } /* * Use ClickOnce APIs to download the assembly on demand. */ Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) { Assembly newAssembly = null; if (ApplicationDeployment.IsNetworkDeployed) { ApplicationDeployment deploy = ApplicationDeployment.CurrentDeployment; // Get the DLL name from the Name argument. string[] nameParts = args.Name.Split(','); string dllName = nameParts[0]; string downloadGroupName = DllMapping[dllName]; try { deploy.DownloadFileGroup(downloadGroupName); } 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); } private void getAssemblyButton_Click(object sender, EventArgs e) { DynamicClass dc = new DynamicClass(); MessageBox.Show("Message: " + dc.Message); } } }
Save the file as either Form1.cs or Form1.vb and compile it into an executable.
csc /target:exe /reference:ClickOnceLibrary.dll Form1.cs vbc /target:exe /reference:ClickOnceLibrary.dll Form1.vb
Marking Assemblies as Optional
To mark assemblies as optional in your ClickOnce application using the Manifest Generation and Editing Tool - Graphical Client (MageUI.exe)
Create your ClickOnce manifests as described in Walkthrough: Deploying a ClickOnce Application Manually. Name your application ClickOnceOnDemand.
Before closing MageUI.exe, select the tab containing your deployment's application manifest, and within that tab select the Files tab.
On the Files tab, find OnDemandAssembly.dll in the list of application files and set its File Type column to None. For the Group column, type ClickOnceOnDemand.dll.
Testing the New Assembly
To test your on-demand assembly
Upload your ClickOnce deployment to a Web server.
Start your application deployed with ClickOnce from a Web browser by entering the URL to the deployment manifest. If you call your ClickOnce application ClickOnceOnDemand, and you upload it to the root directory of adatum.com, your URL would look like this:
http://www.adatum.com/ClickOnceOnDemand/ClickOnceOnDemand.application
When your main form appears, press the Button. You should see a string in a message box window that reads "Hello, World!"