![]() |
Languages »
C / C++ Language »
General
Intermediate
Dime Buffered UploadBy Tim_MackeyThis article outlines a technique to overcome the problem of sending large files via web services. |
C#.NET 1.1, Win2K, WinXP, Win2003, Vista, ASP.NET, IIS, VS.NET2003, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||

This article uses version 2 of the Web Service Enhancements (WSE) and .NET version 1.1. I have put together a new article with WSE 3 and .NET 2.0, using MTOM instead of DIME, which is faster and simpler to use. This article is being left for people who need a .NET 1.1 / WSE 2 solution.
This article documents a solution to upload large files over an HTTP web service, overcoming IIS request timeouts and IIS/ASP.NET request size limits. As well as providing a reliable transport system for large files, it enables a Windows Forms application to provide regular feedback on the status of the transfer, using a progress bar.
If you have ever developed a Windows Forms application that sends files (possibly large) to a web server with a HTTP web service, you will probably already know how poorly the .NET framework handles this task. Web services were designed to send single messages, but us developers being the creatures we are, started using them to send arrays of bytes, i.e. the binary contents of a file. This approach is not scalable for large files because IIS has a request time out, and a maximum request size, which puts constraints on the size of any web service request we might send to the server. You are also leaving your user to wait possibly a long time until the success or failure of the operation is returned. You may also encounter OutOfMemoryExceptions in your client application, or on the server (since the array of bytes must be stored all at once in memory).
This article originally outlined a solution to split a file into X number of file fragments, and then send them all to the server, where they would be combined to reproduce the original file on the server. This was a fast way of achieving 'chunking' over HTTP and providing regular feedback to the user (provided the chunks were small enough). The problem with this is obviously the risk of orphaned files scattered on the client and server computer, if any unexpected errors occurred.
This article has now been updated based on some code that Tulio Rodriguez kindly gave me permission to use and post here. His approach was not to split the file into fragments, but to send byte arrays one at a time to the server, which simply appended each chunk to a file on the server. This is a much more elegant solution, and he gets all the credit for the idea to use this approach. I made the following changes to his code:
Before you go implementing this into your application, check out other options that may save you time and effort:
In the demo application provided with this article, there is a Windows Forms application, which connects to a web service on a Web application. Files are sent from the client application and saved on to the web server, via the web service.
AppendChunk() method invoked by the client application.
In my tests in a local network environment, I uploaded a 2.5 gigabyte DVD ISO image, split into 16 MB chunks, in just under 8 minutes. (I had to change some data types from int to long to accommodate the massive file size!) 16 MB chunks are obviously very large, but probably appropriate in an intranet environment. In my CMS app that has clients connecting over dial-up connections, I have the chunk size set to 32 Kb, which provides quick feedback to the user all the way through the upload.
During some performance analysis, I noted that for fast networks the chunk size did not have a noticeable effect on the time taken for each AppendChunk() operation. It is therefore more efficient to go for the biggest chunk size your network can accommodate, while still providing a reasonable frequency of updates to the progress bar.
If you want to edit the source and recompile the application, you must download WSE (Web Service Enhancements) for Visual Studio from Microsoft. Some users have had trouble updating the web service reference, because without WSE, Visual Studio won't create a WSE version of the proxy class. If you get any of the compile errors below, then not having WSE installed for Visual Studio is likely the problem.
If you have problems with WSE, please visit the MSDN site for WSE.
First of all we create a 'BufferedUpload' object. This is a helper class for the WinForms application, to send the file chunks to the web service. As you will see from the code below, the chunk size is set to the value in the "Chunk Size" Domain-Up-Down control. Two event handlers are added, one for updating the progress bar, and the other for responding to changes in the state of the upload, i.e. Completed, Failed, etc. The 'BeginUpload()' method is asynchronous so the application remains responsive during the upload.
ws = new BufferedUpload();
ws.ChunkSize = Int32.Parse(this.dudChunkSize.Text)*1000; // kilobytes
ws.ProgressChanged += new ProgressChangedEventHandler(Upload_ProgressChanged);
ws.StateChanged += new EventHandler(Upload_StateChanged);
ws.BeginUploadChunks(this.textBox1.Text);
The code below outlines a simplified version of the upload routine. It is essentially a while loop that keeps reading bytes into a buffer, attaching the bytes as a Dime attachment (via MemoryStream) and sending the attachment to the web service. Further explanation of the code can be found in the comments in the source code.
FileStream fs = new FileStream(Filename, FileMode.Open, FileAccess.Read);
int bytesRead = 0; // used to store the number of bytes written each time
byte[] buffer = new byte;
if (this.sentBytes == 0)
{
// initialise an upload Instance to manage the file I/O for this upload
this.instanceId = this.Initialize(Path.GetFileName(Filename),
this.overwrite);
}
bytesRead = fs.Read(buffer, 0, bufferSize);
while (bytesRead > 0 && this.state != UploadState.Aborting)
{
// copy the byte buffer into a memory stream
// to send in a dime attachment
MemoryStream ms = new MemoryStream(buffer, 0, buffer.Length);
DimeAttachment dimeAttach = new DimeAttachment("image/gif",
TypeFormat.MediaType, ms);
this.RequestSoapContext.Attachments.Add(dimeAttach);
this.AppendChunk(this.instanceId, sentBytes, bytesRead);
this.sentBytes += bytesRead;
bytesRead = fs.Read(buffer, 0, bufferSize); // read the next chunk (if it
}
The AppendChunk() web method simply reads the Dime attachment back into a byte[] and calls the Instance.AppendChunk() method, as shown below:
public void AppendChunk(byte[] buffer, long offset, int bufferSize)
{
// make sure that the file exists
if (!System.IO.File.Exists(this.tempFilename))
CustomSoapException("Cannot Find Temp File", this.tempFilename);
// if the temp file size is not the same
// as the offset then something went wrong....
if (tempFilesize != offset)
CustomSoapException("Transfer Corrupted",
String.Format("The file size is {0}, expected {1} bytes",
tempFilesize, offset));
else
{
// offset matches the filesize, so the chunk
// is to be inserted at the end of the file.
FileStream FileStream = new FileStream(this.tempFilename,
FileMode.Append);
FileStream.Write(buffer, 0, bufferSize);
FileStream.Close();
}
}In your Win-Forms app, add the 'BufferUpload.cs' file, and follow the logic used in the demo app. You don't need WSE2 installed on your PC to make this work, because the DLL has been bundled with the demo app. Simply reference this DLL in your WinForms and WebForms projects.
In your web app, add the 'Instance.cs' and 'upload.asmx' files (with code-behind for the .asmx), and make sure the Win app is referencing the correct URL for your web service. Also make sure to include the WSE sections in your web.config (copy them from the one in the demo app).
You'll notice in the demo app there is a wait cursor icon displayed to the left of the status bar. This conveys a further sense of 'busy' to the user as well as the progress bar. I borrowed this trick from the FotoVision .NET sample application. Also, in case you're wondering, the progress bar isn't actually part of the status bar, it is just placed on top of it and anchored to the corner. I put these two features together into a 'FormBar' control which is supposed to be some kind of general purpose progress tool for Win-Forms apps. It provides methods to update the status bar message and the progress bar, which can be safely called from the UI thread, or from worker threads.
I also threw in a simple 'Retry' feature. It is a form that pops up if the upload fails for any reason, and picks up from the last good position if a chunk failed to arrive. To test this out, pause your web server mid-transfer and you'll see the Retry window pop up. It will keep failing until you resume the web server.
I've spent considerable effort over the last two years on and off trying to find a suitable solution to uploading large files over HTTP, and I think this is the simplest and most efficient approach so far. It's very robust and the fact that it supports resume functionality is an added bonus. Also, being able to reliably upload a DVD ISO image is not bad in my opinion!
If you have any problems / suggestions, post them below.
Enjoy!
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 29 Dec 2005 Editor: Smitha Vijayan |
Copyright 2005 by Tim_Mackey Everything else Copyright © CodeProject, 1999-2009 Web10 | Advertise on the Code Project |