您的位置:首页 > 移动开发

Creating Self-Updating Applications With the .NET Compact Framework

2007-05-21 14:18 597 查看

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.



Copy Code


<?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:



Copy Code


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.



Copy Code


<?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



Copy Code


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.



Copy Code


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


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



Copy Code


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.



Copy Code


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:



Copy Code


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.



Copy Code


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.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐