Creating Self-Updating Applications With the .NET Compact Framework

 
Contents

Introduction
Infrastructure
Setting Up an Update Server
Building a web Service
Creating an Updater Applet
Initializing and Reading the Configuration
Checking for Updates
Downloading Updates
Installing Update
More Information

Introduction

Every application life cycle involves periodic updates. Bug fixes, new feature and changes that allow supporting new hardware cause software manufacturers to provide the user with new builds of their software. Unlike the desktop where the user can be simply directed to the web site, on mobile devices it is nice to have some level of control over the update process.

In this paper I am going to show how an application can be "automagically" updated from the mobile device.

This paper assumes a certain level of familiarity with both the .NET Compact Framework and Web services.

Note:    A build error has been forced in the sample to ensure that the url of your Web service is updated properly. You must set the host of the Web service in the Updater project's XML file "updatecfg.xml," as well as in the Properties menu of the "UpdateSvc" Web Service in the same project. To build the code, follow the instructions following the #error directive and then remove them.

Infrastructure

The self-updating application infrastructure consists of two major parts - an updater applet and a web service that provides information about update availability and location.

Setting Up an Update Server

We choose IIS and ASP.NET as a platform for our update server for a simple reason. We would like to use Web service and ASP.NET sounds like a perfect tool to build just that.

There are a couple of things that the update server should be able to do. First, it should provide the information to the client as to the update availability. To do this it should maintain a local database of the available updates. We will use an XML document to store this information, since it is easy to load, edit and save.

The second task is to serve the cab with the updated version of the software. This can be done using pretty much any web server environment. To keep our options open we will serve to the client a complete download url in step 1, but for the purpose of this exercise the download will be hosted on the same server.

Building a Web Service

Let's build the Web service that will provide the update information. First, we will define the XML storage format. One of our goals is to be able to provide downloads that target a particular CPU architecture, since a typical application is built for several different CPUs. Another thing we'd like to do is to provide downloads for several components using the same Web service. Finally we would like to keep it reasonably simple – a real life scenario will probably involve other considerations, but for now this should be enough.

<?xml version="1.0" encoding="utf-8" ?> 
<updateinfo>
    <downloadmodule plat="PPC" arch="ARM" name="TESTAPP" action="cab">
        <version maj="1" min="0" bld="1363"/>
        download/TestApp_PPC.ARM.CAB
    </downloadmodule>
    <downloadmodule plat="PPC" arch="ARMV4" name="TESTAPP"
     action="cab">
        <version maj="1" min="0" bld="1363"/>
        download/TestApp_PPC.ARMV4.CAB
    </downloadmodule>
</updateinfo>

The root element is called updateinfo. It contains multiple downloadmodule elements, each defining a download for a particular combination component/CPU architecture. The CPU architecture designation (attribute "ARCH") is borrowed from the naming schema used by Visual Studio for generated CAB files. In this example <downloadmodule arch="ARM" name="MYAPP"><version maj="1" min ="0" bld="1363"/> designates a CAB file with MYAPP application version 1.0.1363 built for ARM architecture.

The Web service will return the following data structure:

public class UpdateInfo
{
    public string Url; 
    public bool IsAvailable; 
    public string newVersion; 
}

[WebMethod] 
public UpdateInfo GetUpdateInfo(string name, string arch,
  int maj, int min, int bld) 
{
    UpdateInfo ui = new UpdateInfo (); 
    Version ver = new Version(maj, min, bld); 
    XmlDocument xmlUpdateCfg = new XmlDocument(); 
    //Attempt to load xml configuration 
    try 
    { 
        xmlUpdateCfg.Load(Context.Request.MapPath("updatecfg.xml")); 
    } 
    catch(Exception ex) 
    { 
        ui.IsAvailable = false; 
        return ui; 
    } 

    string xq = string.Format(
        "//downloadmodule[@arch=/"{0}/" and @name=/"{4}/" " + 
        "and ( version/@maj>{1} or version/@maj={1} " + 
        " and (version/@min > {2} or version/@min = {2}" +
        "and version/@bld > {3}))]",
        arch, maj, min, bld, name);
    XmlElement nodeUpdate =
       (XmlElement)xmlUpdateCfg["updateinfo"].SelectSingleNode(xq); 
    if ( nodeUpdate == null ) 
    { 
        ui.IsAvailable = false; 
        return ui; 
    } 

    // Build UpdateInfo structure
    ui.IsAvailable = true; 
    ui.newVersion = new Version(string.Format("{0}.{1}.{2}", 
        nodeUpdate["version"].Attributes["maj"].Value, 
        int.Parse(nodeUpdate["version"].Attributes["min"].Value), 
        int.Parse(nodeUpdate["version"].Attributes["bld"].Value))).ToString(); 
    string srv = Context.Request.ServerVariables["SERVER_NAME"]; 
    string path = Context.Request.ServerVariables["SCRIPT_NAME"]; 
    ui.Url = string.Format("http://{0}{1}", srv, path); 
    ui.Url = ui.Url.Substring(0, ui.Url.LastIndexOf("/")); 
    ui.Url += "/" + nodeUpdate.InnerText.Trim(); 
    return ui; 
}

Creating an Updater Applet

To make life simpler let us assume that a copy of updater applet will ship with the application and deploy into the same directory as the main application assembly. Let us also assume that every update will increment at least the main assembly revision. As we know, every assembly holds a version attribute, that looks like X.Y.ZZZZ.TTTT where X is the major build number, Y is the minor build number, ZZZZ is the build number and TTTT is the revision number.

When you build your project, the assembly version is controlled by AssemblyVersionAttribute, which by default is defined in AssemblyInfo.cs (or AssemblyInfo.vb for VB projects). Of course this attribute can be defined in any of the project files. When the wizard creates the project, assembly version is set to 1.0.*. This means that the compiler will increment the build number automatically. The actual build number generated by the compiler just happens to be an amount of days since midnight January 1, 2000, while the revision number is an amount of seconds since midnight (all UTC).

We are going to build our updater applet as a .NET Compact Framework Form-based project. The applet will read the XML configuration file, connect to the Web service, specified in that file, load the application main assembly and retrieve its version number, pass it to the Web service and if an update is available, offer a user an opportunity to download and install the update. If the user chooses so, the CAB file will be requested, downloaded locally and launched to install a new version of the controlled application.

Initializing and Reading the Configuration

The update applet sample configuration looks like this.

<?xml version="1.0" encoding="utf-8" ?> 
<updateinfo>
      <checkassembly name="MyApp.EXE"/>
      <remoteapp name="MyApp"/>
      <service url="http://updates.mycompany.com/UpdateAgent/Agent.asmx"/>
</updateinfo>

The following elements must be defined:

  • checkassembly: name of the application main assembly – will be used to retrieve version information
  • remoteapp: the symbolic name of the application, for which the update is sought.
  • service: url of the web service to use with this application

To read the xml file and retrieve configuration details we simply load the XML document

XmlDocument config = new XmlDocument();
config.Load(configDocumentPath);

Now configuration elements can be simply referred to as config["configuration"]["remoteapp"].GetAttribute("name")

Checking for Updates

The update service is implemented as a Web service. There are numerous examples on the web demonstrating how to access a Web service from the .NET Compact Framework. The only other thing worth mentioning is that the URL that the Web service proxy uses, is set dynamically.

Agent agent = new Agent();
agent.Url = xmlConfig["updateinfo"]["service"].GetAttribute("url");

We invoke update service method GetUpdateInfo to receive an instance of UpdateInfo class.

string platform = Utils.GetPlatformType();
string arch = Utils.GetInstructionSet();
string appName = xmlConfig["updateinfo"]["remoteapp"].GetAttribute("name").ToUpper();
UpdateInfo info = agent.GetUpdateInfo(appName, platform, arch, 
    name.Version.Major, name.Version.Minor, name.Version.Build);

If the call to the Web service has succeeded, we will know whether an update is available from the UpdateInfo.IsAvailable property. Notice that one of the parameters, passed to the Web service is a string defining the CPU architecture. We know on what kind of hardware we are running and at this point we are not interested in downloading other CABs.

Figure 1.

Downloading Updates

One of the bits of information received from the update service is the URL for the CAB to be downloaded. In order to transfer this CAB file to the device, we use the HttpWebRequest class. We request http data asynchronously in order for the request to be non-blocking.

private HttpWebRequest m_rec;

private void btnUpdate_Click(object sender, System.EventArgs e)
{
    m_req = (HttpWebRequest)HttpWebRequest.Create(UpdateUrl);
    m_req.BeginGetResponse(new AsyncCallback(ResponseReceived), null);
    btnCheck.Enabled = false;
    Cursor.Current = Cursors.WaitCursor;
}

Asynchronous data transfer also allows us to present a nice progress bar:

void ResponseReceived(IAsyncResult res)
{
    try
    {
        m_resp = (HttpWebResponse)m_req.EndGetResponse(res);
    }
    catch(WebException ex)
    {
        MessageBox.Show(ex.ToString(), "Error");
        return;
    }
    // Allocate data buffer
    dataBuffer = new byte[ DataBlockSize ];
    // Set up progrees bar
    maxVal = (int)m_resp.ContentLength;
    pbProgress.Invoke(new EventHandler(SetProgressMax));
    // Open file stream to save received data
    m_fs = new FileStream(GetCurrentDirectory() +  @"/download.cab",
      FileMode.Create);
    // Request the first chunk
    m_resp.GetResponseStream().BeginRead(dataBuffer, 0, DataBlockSize,
      new AsyncCallback(OnDataRead), this);
}

Notice the use of Progressbar.Invoke(). The callback is invoked on a secondary (worker) thread and any attempt to update a control directly from inside this callback will fail.

When another chunk of data is successfully received, another callback function is invoked. We save the received data buffer, update UI and prepare to receive the next one.

void OnDataRead(IAsyncResult res)
{
    // How many bytes did we get this time
    int nBytes = m_resp.GetResponseStream().EndRead(res);
    // Write buffer
    m_fs.Write(dataBuffer, 0, nBytes);
    // Update progress bar using Invoke()
    pbVal += nBytes;
    pbProgress.Invoke(new EventHandler(UpdateProgressValue));
    // Are we done yet?
    if ( nBytes > 0 )
    {
        // No, keep reading
        m_resp.GetResponseStream().BeginRead(dataBuffer, 0,
          DataBlockSize, new AsyncCallback(OnDataRead), this);
    }
    else
    {
        // Yes, perform cleanup and update UI.
        m_fs.Close();
        m_fs = null;
        this.Invoke(new EventHandler(this.AllDone));
    }
}

Installing Update

Now that we have successfully downloaded a CAB file, it is time to install it. Fortunately, the CAB file can be installed by simply launching it. To launch a CAB file we P/Invoke ShellExecute function.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值