Introduction
For organizations that run information systems, it's a common problem to keep all instances of a desktop application up to date with the latest version of executables, libraries, configuration and other files. It always costs great deal of resources (human and time). Ideally, administrators and support engineers would like to deploy the updated files once, to a central server, and have the updated files automatically replicated to each desktop installation. The SmartAssembly
is such a solution that uses "pull model" to do so for .NET applications.
.NET framework has provided the support for "Smart Client" applications. You can config <DEPENDENTASSEMBLY>
nodes in the configuration file (see MSDN for details) and let the runtime to download the associated assemblies. However, there are some weaknesses:
- The configuration is too simplex and has a lack of flexibility.
- The download progress is a "blackbox", and not friendly to the end users.
- In the case of late binding, if you load an assembly by its weak name (not specify the version, public key and culture), the runtime will open a HTTP connection to check the remote one every time when you load the same assembly. Of course, it will waste a great deal of time if you need to load some assemblies frequently.
In addition, Microsoft has published an Application Block named Updater which also provides a "pull model" solution to automatically download application updates from a central location. But it does not work on-the-fly. By contraries, it frequently checks the remote manifest file to determine whether there is a need to do update. If so, it starts an Updater process and shuts down the user application to download the changed files. After downloading all the changed files, it then starts the user application again.
SmartAssembly
is designed to meet the following requirements:
- Make the download progress controllable, visible and customizable.
- Support break-and-resume downloading.
- Provide a framework for supporting compressed file.
- Ease the configuration work for deployers.
- Provide deployers with flexibility to configure the system.
- Provide developers with customizability and extensibility.
Working Principle
There is a set of Resolve
events defined in classes AppDomain
and Assembly
. These events provide developers a chance to resolve assemblies, resources and modules by themselves. SmartAssembly
only uses the AssemblyResolve
event. When that event is fired, SmartAssembly
searches the configuration for the associated codebase URI, then downloads the assembly according to the URI and save it to local, at last loads the assembly and returns it back to AppDomain
.
Using SmartAssembly
It's divided into two parts to describe how to use SmartAssembly
. At first, it's required to add a piece of code in your application. Then, write the configuration file and deploy your application.
Coding
By the way, there is a CHM file accompanied with the source package. You can refer it for the details about how to use SmartAssembly
.
1. Create a SmartAssemblyManager Instance
The class SmartAssemblyConfig
is used to create a SmartAssemblyManager
instance from a SmartAssembly
configuration section. There are three ways to do so.
- Load from the application configuration file. By this way, the returned
SmartAssemblyManager
instance is also SmartAssemblyManager.CurrentManager
, and its CurrentDomain
property has already been set to AppDomain.CurrentDomain
. In contrast, the other two ways just create a new SmartAssemblyManager
instance:
SmartAssemblyManager sam =
ConfigurationSettings.GetConfig(Lightning.SmartAssemblyConfig.SectionName)
as SmartAssemblyManager;
- Load from an individual configuration file for
SmartAssembly
:
SmartAssemblyManager sam = SmartAssemblyConfig.Create("c:\test.xml");
- Load from a prepared configuration
XmlNode
:
XmlNode section = ...;
SmartAssemblyManager sam = SmartAssemblyConfig.Create(section);
if( null != sam ) {
sam.CurrentDomain = AppDomain.CurrentDomain;
}
2. Enable SmartAssembly
To enable SmartAssembly
, you need to set the CurrentDomain
property of the SmartAssemblyManager
instance to an AppDomain
.
if( null != sam ) {
sam.CurrentDomain = AppDomain.CurrentDomain;
}
As described above, if you create SmartAssemblyManager
instance from the application configuration file, and just want to handle the AppDomain.CurrentDomain
, then you can skip this step.
3. Customize the Routine to Download SmartAssembly Configuration File
SmartAssembly
configuration file can be an individual file, or even a database record, thus you can download it or get it from the database. SmartAssemblyConfig
provides a static
delegate for you to do so.
Public static DownloadConfigDelegate DownloadConfigCallback;
4. Customize the Routine to Uncompress a File
SmartAssembly
does not provide that function in this version. However, it provides a well defined framework to ease your work. All that you need to do is to develop an ExtractFileInZipDelegate
function and handle the following delegate of the SmartAssemblyManager
instance, then follow the configuration rules for compressed files (see the Deploying section for details).
public ExtractFileInZipDelegate ExtractFileInZipCallback;
5. Customize Other Routines
SmartAssembly
also provides customizability for LoadAssembly
, LoadlocalAssembly
, DownloadFile
and so on.
public event LoadAssemblyEventHandler CustomLoadAssembly;
public event LoadAssemblyEventHandler CustomLoadlocalAssembly;
public event SmartAssemblyEventHandler CustomDownloadFile;
public event SmartAssemblyEventHandler CustomDownloadCompleted;
public event DownloadProgressEventHandler CustomDownloading;
public event SmartAssemblyCancelEventHandler BeforeDownload;
public event SmartAssemblyEventHandler AfterDownload;
Deploying
The deploying work is focused on how to write the configuration file. This section will give you the details to configure SmartAssembly
.
This is the root node of the configuration.
<a href="#node_sac"><smartAssemblyConfiguration></a>
<a href="#node_sc"><smartConfig></a>
<a href="#node_cd"><cacheDirectory></a>
<a href="#node_ns"><netSetting></a>
<a href="#node_ll"><loadLogic></a>
<a href="#node_da"><dependentAssembly></a>
<a href="#node_ai"><assemblyIdentity></a>
<a href="#node_cb"><codeBase></a>
<a href="#node_br"><bindingRedirect></a>
Child Element
| Description
| Required
|
<smartConfig> | Specifies the setting for smart configuration file. | N |
<cacheDirectory> | Specifies the settings for cache directories. | Y |
<netSetting> | Specifies the arguments for network connection. | N |
<loadLogic> | Specifies the logic to load assemblies. | N |
<dependentAssembly> | Encapsulates binding policy and assembly location for each assembly. | N |
Configures the individual SmartAssembly
configuration file. If this node exists, SmartAssembly
will try to load configuration from that setting and the other nodes will be ignored.
<a href="#node_sac"><smartAssemblyConfiguration></a>
<a href="#node_sc"><smartConfig></a>
Attribute
| Description
| Required
| Default
|
href | Specifies the URL where SmartAssembly can find the configuration file. | N | |
filePath | Specifies the full file path where SmartAssembly saves the configuration file to. | N | |
tryLocal | Tries the local file if failed to download the remote one. | N | true |
Settings for local cache directory.
<a href="#node_sac"><smartAssemblyConfiguration></a>
<a href="#node_cd"><cacheDirectory></a>
Attribute
| Description
| Required
| Default
|
path | Specifies the cache path for SmartAssembly to store assemblies. | Y | |
type | Specifies the type of the cache path. (Enum : KeepNewestOnly , KeepAllVersions , KeepAllByHashCode ) | N | KeepNewestOnly |
packageFilePath | Specifies the cache path where SmartAssembly stores compressed files. | N | |
shadowCopyCachePath | Specifies the cache directory for ShadowCopy . | N | |
shadowCopyExtraDirs | Specifies the extra directories for ShadowCopy . | N | |
Settings for network arguments.
<a href="#node_sac"><smartAssemblyConfiguration></a>
<a href="#node_ns"><netSetting></a>
Attribute
| Description
| Required
| Default
|
agentName | Specifies the agent name for a HttpRequest . | N | |
bufferSize | Specifies the size of the download buffer. | N | 4096 |
timeout | Specifies the the time-out value for a HttpRequest . | N | -1 |
The logics to load assemblies.
<smartAssemblyConfiguration>
<loadLogic>
Attribute
| Description
| Required
| Default
|
notCheckRemoteWhileMatched | Specifies whether to check the remote file (assembly) even when the local one matched with the request completely. | N | true |
downloadNewerOnly | Specifies whether to do download only when the remote file is newer than local one. | N | false |
tryLatestVersionWhileNotMatch | Specifies whether to use the highest version that is configured in the configuration file automatically. | N | true |
smartMatch | Specifies whether to use "Smart Match" technique. | N | true |
loadSymbols | Specifies whether to try downloading the symbol file(s) associated the assemblies that are to be resolved. | N | false |
symbolsFileExtName | Specifies the extension of symbol file(s) that will be used to calculate the URI. | N | pdb |
loadMethod | Specifies the method to load Assembly. (Enum : Directly , ViaMemoryStream , ShadowCopy ) | N | Directly |
identifiedByName | Specifies whether to use the weak name to identify an assembly. | N | false |
workOffline | Specifies whether to work in offline mode. | N | false |
baseHref | Specifies the base URI, which will be used to calculate the URI of the assemblies. | N | |
defaultZipFile | Specifies the default compressed file name, which will be used to calculate the URI of the assemblies that have not been configured. | N | |
Encapsulates binding policy and assembly location for each assembly. Use one <dependentAssembly>
element for each assembly.
<a href="#node_sac"><smartAssemblyConfiguration></a>
<a href="#node_da"><dependentAssembly></a>
<a href="#node_ai"><assemblyIdentity></a>
<a href="#node_cb"><codeBase></a>
<a href="#node_br"><bindingRedirect></a>
Child Element
| Description
| Required
|
<assemblyIdentity> | Contains identifying information about the assembly. This element must be included in each <dependentAssembly> element. | Y |
<codeBase> | Specifies where the runtime can find a shared assembly if it is not installed on the computer. | N |
<bindingRedirect> | Redirects one assembly version to another or even to another assembly. | N |
Contains identifying information about the assembly.
<a href="#node_sac"><smartAssemblyConfiguration></a>
<a href="#node_da"><dependentAssembly></a>
<a href="#node_ai"><assemblyIdentity></a>
Attribute
| Description
| Required
| Default
|
name | The name of the assembly. | Y | |
publicKeyToken | A hexadecimal value that specifies the strong name of the assembly. | N | |
culture | A string that specifies the language and country/region of the assembly. | N | |
notCheckRemoteWhileMatched | | N | true |
Specifies where the common language runtime can find an assembly.
<a href="#node_sac"><smartAssemblyConfiguration></a>
<a href="#node_da"><dependentAssembly></a>
<a href="#node_cb"><codeBase></a>
Attribute
| Description
| Required
| Default
|
version | Specifies the version of the assembly the codebase applies to. The format of an assembly version number is major.minor.build.revision . Valid values for each part of the version number are 0 to 65535 | N | |
href | Specifies the URL where the runtime can find the specified version of the assembly. | N | |
Redirects one assembly version to another or even to another assembly.
<a href="#node_sac"><smartAssemblyConfiguration></a>
<a href="#node_da"><dependentAssembly></a>
<a href="#node_br"><bindingRedirect></a>
Attribute
| Description
| Required
| Default
|
oldVersion | Specifies the version of the assembly that was originally requested. There are three possible formats:
Single : n.n.n.n Range : n.n.n.n - n.n.n.n or n.n.* (same as n.n.0.0 - n.n.65535.65535) Collection : n.n.n.n; n.n.n.n; n.n.n.n; ...
| N | |
newVersion | Specifies the version of the assembly or a new assembly to use instead of the originally requested version. The format of a new assembly should like this:
NewAsm, Version=1.0.0.0, Culture=en-us, PublicKeyToken=
| N | |
A Completed Example
<configuration>
<configSections>
<section name="smartAssemblyConfiguration"
type="Lightning.SmartAssemblyConfig, SmartAssembly"/>
</configSections>
<smartAssemblyConfiguration>
<cacheDirectory path="%ComAppData%\Assembly\Origin"
type="KeepAllVersions"
packageFilePath="%ComAppData%\Assembly\ZipFiles" />
<netSetting agentName="Smart Assemlbly Manager"
bufferSize="8182" timeout="-1"
proxyMode="" />
<loadLogic notCheckRemoteWhileMatched="true"
downloadNewerOnly="true"
tryLatestVersionWhileNotMatch="true"
smartMatch="true"
loadSymbols="true"
loadMethod="ShadowCopy"
identifiedByName="true"
workOffline="false"
baseHref="http://localhost/Assembly/" />
<dependentAssembly>
<assemblyIdentity name="MainControl" publicKeyToken="" culture="" />
<codeBase version="" href="old/MainControl.dll"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="SubControl1"
publicKeyToken="a9061ec0e85bbecf"
culture="" />
<codeBase version="0.0.1.0" href="old/SubControl1.dll"/>
<codeBase version="2.1.0.0" href="new/SubControl1.dll"/>
<bindingRedirect oldVersion="0.0.0.1; 0.0.0.2" newVersion="0.0.1.0" />
<bindingRedirect oldVersion="1.0.*" newVersion="2.1.0.0" />
<bindingRedirect oldVersion="1.1.0.0 - 2.0.0.0" newVersion="2.1.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="SubControl2" publicKeyToken="" culture="" />
<codeBase version="" href="old/SubControl2.dll"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="MainControl.resource" publicKeyToken="" culture="" />
<bindingRedirect oldVersion="*"
newVersion="MainControl.resource, Version=, Culture=, PublicKeyToken=" />
</dependentAssembly>
</smartAssemblyConfiguration>
</configuration>
About the Demo
There are three projects in the demo solution:
SmartAssembly | SmartAssembly library. |
Test | The demo application which can be executed stand alone or be loaded by IE (see the readme.txt for details.) |
TestControl | The demo assembly which will be "SmartAssemblied". |
Points of Interest
There are some tips for deploying, for example:
- Deciding where to set the cache directory to, to
AppBaseDir
, to AppData
or to ComAppData
. - How to package the assemblies if you use compressed file(s), as an application, as a package or some other strategies?
License
This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.
A list of licenses authors might use can be found here.