Wednesday, May 11, 2011

Automating ClickOnce Installations for .NET 4.0 Applications

One sign of a mature development group is the ability and willingness to automate application builds and deployments. While a variety of tools help us build our assemblies (MSBuild, NAnt, etc.), and other tools help us manage those builds (Team Foundation Server, CruiseControl.NET, etc.), a lot of time can be spent implementing the deployment side of that automation. This article is designed to assist those endeavoring to create ClickOnce deployments for their applications, focusing on those require the Microsoft .NET 4.0 Framework.

About ClickOnce

We use the Mage (Manifest Generation and Editing Tool) executable to create our ClickOnce deployment. This program is shipped with the Windows SDK, and can be found under [Program Files]\Microsoft SDKs\Windows\v[X.X]\Bin, where [Program Files] may vary based on your PC or server OS, and [X.X] is the SDK version – Windows 7 shipped with 7.0A, but at the time of this writing, 7.1 is available.

An important point is that if you are deploying applications for .NET 2.0, 3.0, or 3.5, you should use the executable found in the SDK Bin folder. However, if you are deploying a .NET 4.0 application, you need to use Mage.exe located in the NETFX 4.0 Tools sub-folder.

A ClickOnce deployment has 3 critical parts:

  1. The application files, which include all necessary executables, libraries, resource files, and configuration files.
  2. The application manifest, which is an XML file describing the application files. This file will typically be named YourProgram.exe.manifest.
  3. The deployment manifest, which is an XML file describing the application, manifest, version, and update instructions. This file will typically be named YourProgram.application

You will find that if you generate a ClickOnce deployment from the Visual Studio IDE, it creates an HTML landing page, a Setup executable, and all of your application files are appended with the .deploy extension. The landing page and Setup.exe are created as a matter of convenience, but they are unnecessary when creating your ClickOnce deployment. The .deploy extensions lead to a discussion about your specific environment.

Environment Considerations

The users and locations of your application will dictate how your environment is set up, and how your deployment behaves. Remember, a ClickOnce deployment contains your application executable, DLLs, .config files, and many other items which will be available to your users from a website or file share. Typically, these items are prevented by IIS from being downloaded.

If you are deploying to an intranet site, you will probably have more control over the IIS site that serves up the deployment, specifically the MIME types and file mappings. If you are deploying to an Internet site, or if the deployment will be hosted by the same website as another ASP.NET application or web service, you will not want the user to be able to view or download .config files! This scenario is why it can be helpful to rename your deployed assets with the .deploy extension.

ClickOnce Deployment Overview

An important consideration is how your files will be physically situated. If you will be upgrading the application periodically, and you want to take advantage of ClickOnce auto-updates, placement of the manifest files is very important. For clarity and organization, it is helpful to place your application files in a versioned sub-folder of the installation site.

For this example, we will assume the following:

  • The user will access the ClickOnce installer on a public-facing website, www.yoursite.com/installs.
  • The physical directory of the website is C:\Website\Installs\.
  • The build directory is C:\Builds\YourProgram\.
  • The ClickOnce application will auto-upgrade, if a new version is detected.

At first, we are going to create a new ClickOnce deployment for YourProgram version 1.0.0, and publish that installation. The Mage.exe statements can be run at the command line, but you can also use that to create your MSBuild or NAnt script to call the executable.

Preparing the ClickOnce Deployment

After your build process has compiled the necessary components and assemblies, they will need to be copied or moved to a working location. Remember to include the configuration file, resource files, and, if you want, an application icon file. For this example, these files are copied to C:\Builds\YourProgram\1.0.0\.

Your assemblies and executable need to be strong-named, or signed with a strong-name key file, at compile time. You can modify the project file to sign the assembly with the following lines (in the top <PropertyGroup/> section):

    <SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>C:\Artifacts\YourCompany.snk</AssemblyOriginatorKeyFile>



In the executable project file, for WPF or WinForms applications, you may also need to add the following lines to the same <PropertyGroup/> section, which can also be created via the project properties:

    <GenerateManifests>true</GenerateManifests>
<UseApplicationTrust>false</UseApplicationTrust>
<SignManifests>true</SignManifests>
<ManifestCertificateThumbprint>[thumbprint]</ManifestCertificateThumbprint>
<ManifestKeyFile>C:\Artifacts\YourCompany.pfx</ManifestKeyFile>



The manifest key file will also be used to sign your ClickOnce deployment. For development, you can create a temporary self-signed certificate, which will expire in a year from the time of generation.


Create a New Application Manifest


To create the application manifest file YourProgram.exe.manifest, execute the following:



mage.exe -New Application
         -ToFile C:\Builds\YourProgram\1.0.0\YourProgram.exe.manifest
         -Name "YourProgram by YourCompany v1.0.0"
         -Version 1.0.0.0
         -FromDirectory C:\Builds\YourProgram\1.0.0
         -IconFile appicon.ico
         -UseManifestForTrust true
         -TrustLevel FullTrust
         -Publisher "YourCompany, Inc."
         -Processor x86|x64|msil


If the application manifest was built without error, it will be found at C:\Builds\YourProgram\1.0.0\YourProgram.exe.manifest.


Sign the Application Manifest


The generated application manifest must be signed with a certificate, which you will have created yourself, or received from a certificate authority:



mage.exe -Sign C:\Builds\YourProgram\1.0.0\YourProgram.exe.manifest
         -CertFile C:\Artifacts\YourCompany.pfx
         -Password password


Append .deploy Extension to Application Files


Once the application manifest is generated, you can append the .deploy extension to all application files, excluding the manifest file. This will allow the files to be downloaded from an IIS website without modifying MIME types or file mappings.


Create a New Deployment Manifest


The application manifest will now be used to generate the deployment manifest YourProgram.application:



mage.exe -New Deployment
         -ToFile C:\Builds\YourProgram\YourProgram.application
         -Name "YourProgram by YourCompany v1.0.0"
         -Version 1.0.0.0
         -AppManifest C:\Builds\YourProgram\1.0.0\YourProgram.exe.manifest
         -ProviderUrl
http://www.yoursite.com/installs/YourProgram.application
         -Publisher "YourCompany, Inc."
         -AppCodeBase 1.0.0\Coll.UI.WPF.MediNexus.exe.manifest
         -Install true
         -Processor x86|x64|msil


Notice that the deployment manifest file has been created in the directory above the application manifest file’s directory. While the file can be generated to any physical location, when the set of files (application files, application manifest, and deployment manifest) is deployed to the install website, the deployment manifest will reside in the parent directory of the deployed version. This makes upgrades more straightforward (and obvious).


Modifying the Deployment Manifest File


Unfortunately, before we can sign the deployment manifest file, we must content with several short-comings with the Mage executable. First, unlike the MageUI application that also accompanies the Windows SDK, we must modify the deployment manifest to indicate that the application files are aliased with the .deploy extension. Secondly, if we want to force the ClickOnce installation to be upgraded automatically when the application is launched, we must also make that modification within the deployment manifest.


If you edit the YourProgram.application file with a text editor, you will see the <deployment/> element looks like this:

  <deployment install="true" minimumRequiredVersion="1.0.0.0">
<subscription>
<update>
<expiration maximumAge="0" unit="days" />
</update>
</subscription>
<deploymentProvider codebase="http://www.yoursite.com/installs/YourProgram.application" />
</deployment>



So that ClickOnce is alerted to the .deploy aliased files, we will add the mapFileExtensions=”true” attribute to the <deployment/> element. Then, to force ClickOnce to check for and install newer versions prior to launching the application, we replace the <expiration/> element with the <beforeApplicationStartup/> element. The <deployment/> element should look now like this:

  <deployment install="true" mapFileExtensions="true" minimumRequiredVersion="1.0.0.0">
<subscription>
<update>
<beforeApplicationStartup />
</update>
</subscription>
<deploymentProvider codebase="http://www.yoursite.com/installs/YourProgram.application" />
</deployment>



Signing the Deployment Manifest


Just like the application manifest, the deployment manifest must be signed:



mage.exe -Sign C:\Builds\YourProgram\YourProgram.application
         -CertFile C:\Artifacts\YourCompany.pfx
         -Password password


Publishing the ClickOnce Installation


Because of how the application files have been assembled, and the manifest files generated, publishing the installation is trivial. Copy the versioned folder C:\Builds\YourProgram\1.0.0 and the deployment manifest file C:\Builds\YourProgram\YourProgram.application to the website physical directory, C:\Website\Installs. In the real world, you will be copying or FTPing to your web servers. Once you direct your users to www.yoursite.com/installs/YourProgram.application, they will be able to install and run the application!


You will likely need to upgrade your deployed application in the future. Luckily, installed copies will check the deployment manifest to see if the version has been changed, and ClickOnce handles the rest.

Tuesday, May 10, 2011

When OO Meets Uh-Oh

This will be an ongoing series on pitfalls and gotchas when developing object-oriented components with the .NET framework.

Working With Nested Objects

When creating business entity classes that represent our enterprise data, we typically encounter a need for nested objects. A good example of this is in the categorization of information, such as Product Category:

  • Tools
    • Hand Tools
      • Hammers
      • Screwdrivers
    • Power Tools
      • Drills
      • Sprayers

At first, we may create a simple class to store such data:

    public class NestedObject
{
public NestedObject()
{
this.Id = 0;
this.Name = string.Empty;
this.ModifiedDate = DateTime.MinValue;
this.ParentObject = new NestedObject();
}

public int Id { get; set; }

public string Name { get; set; }

public DateTime ModifiedDate { get; set; }

public NestedObject ParentObject { get; set; }

}



This class stores the information we need, it compiles without error, and we go on about our business, loading the data from a database or XML feed.

    NestedObject tools = new NestedObject()
{
Id = 1,
Name = "Tools"
};

NestedObject powerTools = new NestedObject()
{
Id = 2,
Name = "Power Tools",
ParentObject = tools
};



However, the problem is exposed when we execute this code, courtesy of the StackOverflowException. Notice in our constructor that we are initializing our Parent Object property. We have inadvertently caused infinite recursion, because as each Nested Object is created, it attempts to create another as its parent, which then also attempts to create its parent, ad infinitum.


Simple Solution


One solution would be to instantiate the Parent Object property as needed, or when the property getter is accessed:

    public class NestedObject
{
private NestedObject _parentObject;

public NestedObject()
{
this.Id = 0;
this.Name = string.Empty;
this.ModifiedDate = DateTime.MinValue;
}

public int Id { get; set; }

public string Name { get; set; }

public DateTime ModifiedDate { get; set; }

public NestedObject ParentObject
{
get
{
if (this._parentObject == null)
this._parentObject = new NestedObject();
return this._parentObject;
}
set { this._parentObject = value; }
}

}



Notice how we defer the object instantiation from the constructor to the property getter. Now, our code still compiles, and when executed, works as expected – unless this object will be XML serialized by a Web or WCF service.


Nested Objects and XML Serialization


When an object, or list of objects, is returned by a method in a Web or WCF service using SOAP, each object is XML serialized for transmission (and then deserialized by the client). Using reflection, the value of each property in the object will be retrieved during serialization, and set when the object is recreated by the client. If a property is another class, composed of members like our Nested Object class, the sub-properties of that class will be discovered, and so forth. 


Let’s create a web service that returns a list of Nested Objects:

        [WebMethod]
public List<NestedObject> GetNestedObjects()
{
List<NestedObject> list = new List<NestedObject>();
NestedObject tools = new NestedObject()
{
Id = 1,
Name = "Tools"
};
list.Add(tools);

NestedObject powerTools = new NestedObject()
{
Id = 2,
Name = "Power Tools",
ParentObject = tools
};
list.Add(powerTools);

return list;
}



When this web method is invoked, we get the same StackOverflowException. The code itself executes without issue, but upon XML serialization, as the properties are discovered, we encounter the infinite recursion issue.


What is the Best Solution?


To answer the question of how best to handle this situation: It depends. Your solution should take this recursion situation into consideration, but the final implementation depends on how you manage your data, code, validation, and everything else. If the data will be displayed in a tree view or similar control, your business entity classes can take the top-down approach (where the children are in a property of the parent). In most cases, however, there is no reason for the entire hierarchy to be constructed and populated in your objects at run-time (but again, this depends on your exact needs).