What if you’re implementing an application in .NET (C#) that is supposed to automatically update itself once a new version comes out? Well, you have multiple options, some of which cost a lot of money, while others are free but come with certain restrictions, like being only compatible with an ISS or solely running in user space like ClickOnce apps.

After searching for a solution for days, I stumbled upon AppUpdater, which is a free .NET component that “was written using the .NET Framework and enables you to make your application auto-updatable simply by dropping the component into your existing .NET applications and setting a few properties (ex. where to get updates from)“. And indeed, it’s as easy as it sounds. Drag-and-drop the component on your application’s main form, configure the AppStart.config file and start your application by using the provided AppStart.exe. Then put an updated version into the according folder on your server and the AppUpdater component will take care of the rest. (if you cannot follow, read the AppUpdater article first :-)

Although step 5 of the walkthrough says that any Web-DAV enabled web server should suffice, I ran into trouble using an apache web server with the dav and dav-fs modules loaded. Apparently, you need an IIS, since it interprets DAV verbs a little different than its apache counterpart. While both the CheckForFileUpdate() and the UpdateFile() functions from the AppUpdater’s class “WebFileLoader.cs” work as expected, the client throws an exception in GetDirectoryContents(). It seems that the apache dav module has some problems with the iscollection and displayname tags of the request string since it returns the following response (e.g. for the file appupdater.dll):


<D:response xmlns:lp1=\"DAV:\" xmlns:lp2=\"http://apache.org/dav/props/\" xmlns:g0=\"DAV:\">
<D:href>/update/1.0.0.1/appupdater.dll</D:href>
<D:propstat>
<D:prop><lp1:getlastmodified>Sat, 12 Apr 2008 00:30:16 GMT</lp1:getlastmodified></D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
<D:propstat>
<D:prop>
<g0:displayname />
<g0:iscollection />
</D:prop>
<D:status>HTTP/1.1 404 Not Found</D:status>
</D:propstat>
</D:response>

getlastmodified returns the right timestamp, but both displayname and iscollection return a “404 Not Found” error. Now, I could’ve gotten into the DAV protocol, put my web server into debugging mode, TCP-dumped a couple of DAV requests and tried to build a proper request string, but then again there is actually no need to employ DAV for simple tasks like this. The simple HTTP protocol totally suffices in this situation. You just need to go a slightly different way:-) As initially implemented, the AppUpdater connects to the web server in the GetDirectoryContents() function and browses the directory to find and copy all the contained files to the client. “Directory Browsing” is a DAV feature, so we’ll have to improvise here.

Let’s say I just released version 2.0.0.0 of my application (Former version was e.g. 1.0.0.0). According to step 5 of the article, I simply need to create a new folder named “2.0.0.0″ in my server’s root update directory and edit the UpdateVersion.xml file to point to this newly created dir. Instead of uploading all files from our release folder, we’ll zip them up and upload the resulting zip-file into the server’s “2.0.0.0″ folder. That way our client only needs to download one file (via HTTP) and isn’t dependent upon the “Directory Browsing” feature. After downloading the zip-file we’ll just unpack it into the temp directory that the regular AppUpdater would have stored the downloaded files in and let it do its magic from there on. It’s as simple as that, plus you only need to zip up and upload those files, which have changed between releases. The AppUpdater will copy all files, which it finds in the 1.0.0.0 but cannot find in the 2.0.0.0 folder to the latter folder. Unfortunately, this little hack doesn’t allow for auto-downloading of missing assemblies anymore (setting the AutoFileLoad option of the AppUpdater component to true), but then again users can just download the installer and re-install the app, if they accidently deleted any of the files in the app’s root folder.

Now to get going and to enable the AppUpdater to work with your apache web server (without Web-DAV) you actually only need to modify one file. First download the the .NET Application Updater Component package, which includes the AppUpdater’s source code. Afterwards edit the file “WebFileLoader.cs”. Since the GetDirectoryContents() function caused us so many headaches and we won’t need it anymore you can delete it entirely. Afterwards, replace the code of the function CopyDirectory with the following (most of which is copied from the UpdateFile function):


Resource currentResource;
int FileCount = 0;
string newFilePath;
if (!Directory.Exists(filePath))
Directory.CreateDirectory(filePath);
HttpWebResponse Response;
HttpWebRequest Request = (HttpWebRequest)HttpWebRequest.Create(url);
Request.Credentials = CredentialCache.DefaultCredentials;
try
{
Response = (HttpWebResponse)Request.GetResponse();
}
catch (WebException e)
{
if (e.Response == null)
{
Debug.WriteLine("Error accessing Url " + url);
throw;
}
HttpWebResponse errorResponse = (HttpWebResponse)e.Response;
if (errorResponse.StatusCode == HttpStatusCode.NotModified)
{
e.Response.Close();
return 0;
}
else
{
e.Response.Close();
Debug.WriteLine("Error accessing Url " + url);
throw;
}
}
Stream respStream = null;
string newPath = "";
try
{
respStream = Response.GetResponseStream();
newPath = filePath + Path.GetFileName((new Uri(url)).LocalPath);
CopyStreamToDisk(respStream, newPath);
DateTime d = System.Convert.ToDateTime(Response.GetResponseHeader("Last-Modified"));
File.SetLastWriteTime(newPath, d);
}
catch (Exception)
{
Debug.WriteLine("APPMANAGER: Error writing to: " + filePath);
throw;
}
finally
{
if (respStream != null)
respStream.Close();
if (Response != null)
Response.Close();
}
// unzip files
Shell32.ShellClass sc = new Shell32.ShellClass();
Shell32.Folder srcFlder = sc.NameSpace(newPath);
Shell32.Folder destFlder = sc.NameSpace(filePath);
Shell32.FolderItems items = srcFlder.Items();
destFlder.CopyHere(items, 20);
File.Delete(newPath);
return 1;

(Sorry for the messy code. I couldn’t get WordPress to indent it properly:-( In the last 7 lines we employ the Shell32 COM library to extract our zip file. To make this work, you need to add the correct reference. In your project, click on “Add Reference”, then on the “COM” tab in the opening dialog and then search for “Microsoft Shell Controls And Automation”. That’s the lib you want to add. Finally compile it and add the resulting .dlls to your project, although I would generally recommend adding the entire AppUpdater project to your solution and referencing it.

Before we can test our new application, we need to create a new directory on our server (let’s assume the example from above and name it 2.0.0.0). Then go through the article’s steps 1-4 and do everything as told, build your application, zip up the new files (to e.g. app.zip) and upload the zip file to the 2.0.0.0 directory on the server. Afterwards, edit the UpdateVersion.xml file as follows:

<VersionConfig>
<AvailableVersion>2.0.0.0</AvailableVersion>
<ApplicationUrl>http://your.web.server/your_update_dir/2.0.0.0/app.zip</ApplicationUrl>
</VersionConfig>

It’s very important to mention here that with the modifications you made, you now need to give the FULL PATH to your zip file within the ApplicationUrl tags, not just the directory as before.

Last but not least, fire up your client and let it automatically update itself!

That’s it! Hope it works for you.

Tags: , , , , , , , , ,

8 Responses to “AppUpdater with Apache Server (without Web-DAV)”

Hi!

I have made modifications as specified in this article..
Still having problem to start newer version after updating is completed.

1) I have committed changes suggested in “WebFileLoader.cs”,
2) upload a zip file of newer version of my application in 2.0.0.0 folder on web server.
3) And change in UpdateVersion.xml

As a result , i got success to check and download newer version from Apache web server,

But it “fails to Start newer version”. I can see a folder created as “1.0.0.0_1″ and app/ contents are downloaded to 1.0.0.0\ in my target machine. Actually it should download files in folder 2.0.0.0. which is not happening in my case..

Please suggest where i am wrong!!!

Hi Cinni,

sorry to hear you have problems. Have you tried debugging the “UpdateFile” function? This might help finding out what’s going wrong in your case. Also, can you post your UpdateVersion.xml file? What files are copied to the 1.0.0.0 folder? What’s in the folder 1.0.0.0_1?

HI admin!
Thank you for a quick reply

Here is my “UpdateVersion.xml”

2.0.0.0
http://www.myapplication/2.0.0.0/app.zip

Before update
1.0.0.0 folder contains, exe and dlls for my application

After update
1.0.0.0 folder contains app\ contents downloaded from Server
app\
app\myapplication.exe
app\mydll.dll

1.0.0.0_1 contains
old files (myapplication.exe , mydll.dll)
app\
app\myapplication.exe
app\mydll.dll

“UpdateFile” function is NOT getting called!!!

Hope this can give u idea to find where i m wrong

Hi Admin!
Above issue is resolved…

it was app.zip which created this issue for me. I have created a zip of a folder named “app” and files inside it, rather than zipping all files togather directly “app.zip”

Your Solution Rocks!!!!

A Trillion Thank to You.

Hi Cinni,

I was wondering why your 1.0.0.0 folder contained a folder named “app” after the update and was gonna suggest that you look into this :-)

I’m glad it works for you!!

A Wed-DAV-less solution is exactly what I was looking for, thanks for publishing it.

I did however run into a few problems. The zip file extraction would throw an exception saying that the “file was not found”. I thought it was the spaces in the filename that were playing tricks, but it turns out that the shell32 method relies (behind the scenes) on the presence of “unzip.exe” to do the extraction and for whatever reason that utility is not available on a few systems here.

So instead of bundling unzip.exe with the application, I simply added a reference to an open source zip lib (http://www.codeplex.com/DotNetZip) and extracted the files in the archive by replacing the Shell32 lines in CopyDirectory by the following:
using (Ionic.Utils.Zip.ZipFile zip = Ionic.Utils.Zip.ZipFile.Read(newPath))
{
foreach (Ionic.Utils.Zip.ZipEntry e in zip)
{
e.Extract(filePath);
}
}

Hopefully this will help someone else with a similar situation.

After much searching for why Apache won’t work with AppUpdater, I stumbled upon this solution. It was a huge help. Thank you for taking the time to provide such great details.

Thank you SO much for this awesome post! I have been killing myself to get this working and was about to give up on it. Distributing by zip files is neater anyway!

Below is the output of my log messages upon the failure that this fixes. Hopefully this will help others with this problem find this solution through search engines:

UPDATE FAILED with the following error message:
System.FormatException: Input string was not in a correct format.
at Microsoft.Samples.AppUpdater.AppDownloader.Download()
at Microsoft.Samples.AppUpdater.AppDownloader.RunThread()

 Feed for this Entry

Something to say?