fter four years of trying out every iteration of Web server application deployment that Microsoft created for .NET, ClickOnce has finally allowed me to succeed in deploying one particularly complex smart client application.
But I still had to tear a few more hairs out before I got it working and came to love ClickOnce. I'm writing this article to share some of the not-so-obvious ways (including a hack or two) to use ClickOnce for application deployment.
The January/February 2006 issue of CoDe Magazine has an excellent overview article of ClickOnce by Patrick Darragh, a Program Manager at Microsoft who had a lot of responsibility for ClickOnce. Patrick's article also digs under the covers a little to explore the mechanics of ClickOnce. What I will do in this article is talk about some of the specific deployment issues I ran into and how I solved them.
Deploying Applications with Dynamically Loaded Forms
My application's architecture is a little more complex than average, though it is certainly not a major enterprise application. It is nearly a typical MDI application that hosts child forms in a window. The atypical feature is that the child forms are loaded dynamically based on user selection. I designed the application so I can add new forms over time by merely dropping a new DLL into the application folder; the application itself has no built-in knowledge of these forms or their assemblies. This is a critical point of departure for the ClickOnce model (and its predecessors) which uses System.Reflection to analyze the application and determine what goes into the deployment package.
|It took a number of times through this manual process of building the manifest before it became second nature to me and I no longer dread providing updates.|
For ClickOnce to discover a particular DLL, my application would need to reference that DLL. But the DLLs containing my child forms are not referenced. When the user chooses her form, the application reads the text of the selection, (for example, "Test42") then uses File.IO to ensure that a file named Test42.dll
exists in the application folder, and finally uses Reflection to dynamically load the Test42
Because of this architecture, ClickOnce will never discover the assembly on its own and thus won't include it in the package.
You have to use a combination of the ClickOnce Publish tool in Visual Studio 2005's IDE and the external MAGEUI application to deploy the application. It took a number of times through this manual process of building the manifest before it became second nature to me and I no longer dread providing updates. The list may look detailed, but it's actually quite simple. Here's how I do it:
|Figure 1: A Deployment Folder after first publishing with ClickOnce showing the deployment manifest (MyApp.application), the versioned manifest for server-side rollbacks (MyApp_1_0_0_0.application) and the manifest folder containing the deployment files (MyApp_1_0_0_0).|
- Use the ClickOnce Publish tool in Visual Studio and let it build the default deployment manifest from the files that it does discover.
- In Windows Explorer, browse to the directory that contains the new Web application. You should see an unversioned deployment manifest file (MyApp.application), another one with your version number, and a folder tagged with that same version number, for example My App 1_0_0_0. Figure 1 shows what a simple deployment folder contains.
- Make a copy of the version folder and rename it with your next version number, My App 1_0_0_1.
- Here's the secret ingredient! Copy all of the extra assemblies from your project files right into this new folder.
- In the new folder, locate the Application Manifest file (e.g. MyApp.exe.application). Open this up in the MAGEUI tool, which should be available by right-clicking on the file.
- On the Name form, change the Version to your new version number ("188.8.131.52"). See Figure 2.
|Figure 2: MAGEUI with the Name page of an Application Manifest.||
|Figure 3: MAGEUI with the Files page of the Application Manifest after the new files have been added in.||
- On the Files form, click "Populate" to re-populate the file list. When asked, I allow it to add the .deploy extension and override existing files. It may ask about individual files, as well. Notice in the lower left corner of the windows, you will see it counting through all of the files it is adding or re-adding into the list. Frequently, when I do the population, the program shuts itself down. No worries. Just open it up, remember to fix the Version, and then run the Populate function again. It always works the 2nd time. See Figure 3.
- Now it's time to save the file. Hopefully, you have elected to sign your manifests. If so, be sure to continue to sign them with the same certificate. Otherwise, the updates will fail on the client side and you will be very confused.
|Figure 4: After the Application Manifest has been updated. Open up the Deployment Manifest in MAGEUI and update the name and the reference to point to your new Application Manifest.|
That was the Application Manifest. Now we need to update the Deployment Manifest!
- In the main folder, open up the Deployment Manifest into the MAGEUI tool. This is the non-versioned manifest file, for example, MyApp.application. Note that MAGEUI may not be the default application for this file type, so don't just double-click to open it. That will most likely fire up the installation! Right-click and choose the MAGE tool from the shortcut menu.
- On the Name page, change the Version to match the new application version. For example, 184.108.40.206.
- On the Application Reference page, click Select Manifest and browse to the Application Manifest in the new folder. See Figure 4.
- Save and sign the Deployment manifest.
You may wonder what to do with those versioned deployment manifests in the main folder. The purpose of these is so that you can do a server-side rollback. For example, if you are up to version 220.127.116.11
and you want everyone to go back to 18.104.22.168
, you can copy the MyApp_1_0_0_3.application
back over to MyApp.application
and (assuming that the matching folder is still there) the next time the users hit the update, they will install the older version. It is up to you to choose whether or not to retain the manifest version files. If you do, simply make a copy of the manifest file after it is created and give it the appropriate name.