Click here to Skip to main content
15,896,063 members
Articles / Web Development / ASP.NET

Sending Files in Chunks with MTOM Web Services and .NET 2.0

Rate me:
Please Sign up or sign in to vote.
4.93/5 (177 votes)
23 Nov 2007CPOL15 min read 3.5M   12.5K   405  
How to send large files across web services in small chunks using MTOM (WSE 3)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Media;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Windows.Forms;

using MTOM_Library;
using System.Web.UI;

namespace UploadWinClient
{
	public partial class Form1 : Form
	{
		public static MTOM_Library.MtomWebService.MTOMWse WebService;

		#region constructor, load, exit methods
		public Form1()
		{
			InitializeComponent();
		}

		private void Form1_Load(object sender, EventArgs e)
		{
			WebService = new MTOM_Library.MtomWebService.MTOMWse();

			// get the list of files to download from the server
			workerGetFileList.RunWorkerAsync();

			// set the default save folder
			this.txtSaveFolder.Text = Application.StartupPath;

			// configure the 'TaskPanel' which is used to dynamically show a progress bar + status message for each file transfer operation
			this.taskPanel1.RemoveItemsWhenFinished = true;
			this.taskPanel1.RemoveItemsOnError = false;
			this.taskPanel1.AutoSizeForm = false;

			// init the ThreadPool MaxThread size to the value in the control
			this.dudMaxThreads_ValueChanged(sender, e);
		}

		private void exitToolStripMenuItem_Click(object sender, EventArgs e)
		{
			this.Close();
		}

		private void Ding()
		{
			try
			{
				SoundPlayer sp = new SoundPlayer();
				sp.Stream = this.GetType().Assembly.GetManifestResourceStream("UploadWinClient.download_complete.wav");
				sp.Play();
			}
			catch { }	// ignore errors playing sound file
		}
		#endregion

		#region upload code
		private void btnBrowse_Click(object sender, EventArgs e)
		{
			this.openFileDialog1.Title = "Select file(s) to upload";
			if(this.openFileDialog1.ShowDialog() == DialogResult.OK)
			{
				if(this.openFileDialog1.FileNames.Length == 0)
					return; // can't upload unless a file is selected

				if(this.chkLoginRequired.Checked && !AuthenticateWebService())
					return;

				foreach(string path in this.openFileDialog1.FileNames)
				{
					string guid = Guid.NewGuid().ToString();
					this.taskPanel1.AddOperation(new TaskPanelOperation(guid, String.Format("Uploading {0}", Path.GetFileName(path)), ProgressBarStyle.Blocks));
					ThreadPool.QueueUserWorkItem(new WaitCallback(this.UploadFile), new Triplet(guid, path, 0));
				}
			}
		}

		/// <summary>
		/// This method is used as a thread start function.
		/// The parameter is a Triplet because the ThreadStart can only take one object parameter
		/// </summary>
		/// <param name="triplet">First=guid; Second=path; Third=offset</param>
		private void UploadFile(object triplet)
		{
			string guid = (triplet as Triplet).First.ToString();
			string path = (triplet as Triplet).Second.ToString();
			long offset = Int64.Parse((triplet as Triplet).Third.ToString());

			FileTransferUpload ftu = new FileTransferUpload();
			ftu.WebService.CookieContainer = WebService.CookieContainer;	// copy the CookieContainer into the transfer object (for auth cookie, if relevant)
			ftu.Guid = guid;
			// set up the chunking options
			if(this.chkAutoChunksize.Checked)
				ftu.AutoSetChunkSize = true;
			else
			{
				ftu.AutoSetChunkSize = false;
				ftu.ChunkSize = (int)this.dudChunkSize.Value * 1024;	// kb
			}
			// set the remote file name and start the background worker
			ftu.LocalFilePath = path;
			ftu.IncludeHashVerification = this.chkHash.Checked;
			ftu.ProgressChanged += new ProgressChangedEventHandler(ft_ProgressChanged);
			ftu.RunWorkerCompleted += new RunWorkerCompletedEventHandler(ft_RunWorkerCompleted);
			ftu.RunWorkerSync(new DoWorkEventArgs(offset));
		}
		
		private void chkUploadAutoChunksize_CheckedChanged(object sender, EventArgs e)
		{
			this.dudChunkSize.Enabled = !this.chkAutoChunksize.Checked;
		}

		#endregion

		#region download code


		/// <summary>
		/// The same button is used to start the download, and cancel it. (the text changes as needed)
		/// Note: the upload and download operations have separate background workers. 
		/// </summary>
		private void btnDownload_Click(object sender, EventArgs e)
		{
			if(this.lstDownloadFiles.SelectedItems.Count == 0)
				return; // can't download unless a file is selected

			if(this.chkLoginRequired.Checked && !AuthenticateWebService())
				return;

			List<string> files = new List<string>(this.lstDownloadFiles.SelectedItems.Count);
			foreach(ListViewItem item in this.lstDownloadFiles.SelectedItems)
			{
				string guid = Guid.NewGuid().ToString();
				this.taskPanel1.AddOperation(new TaskPanelOperation(guid, String.Format("Downloading {0}", Path.GetFileName(item.Text)), ProgressBarStyle.Blocks));
				ThreadPool.QueueUserWorkItem(new WaitCallback(this.DownloadFile), new Triplet(guid, item.Text, 0));
			}
		}

		/// <summary>
		/// This method is used as a thread start function.
		/// The parameter is a Triplet because the ThreadStart can only take one object parameter
		/// </summary>
		/// <param name="triplet">First=guid; Second=path; Third=offset</param>
		private void DownloadFile(object triplet)
		{
			string guid = (triplet as Triplet).First.ToString();
			string path = (triplet as Triplet).Second.ToString();
			long offset = Int64.Parse((triplet as Triplet).Third.ToString());
			
			FileTransferDownload ftd = new FileTransferDownload();
			ftd.WebService.CookieContainer = WebService.CookieContainer;	// copy the CookieContainer into the transfer object (for auth cookie, if relevant)
			ftd.Guid = guid;
			// set up the chunking options
			if(this.chkAutoChunksize.Checked)
				ftd.AutoSetChunkSize = true;
			else
			{
				ftd.AutoSetChunkSize = false;
				ftd.ChunkSize = (int)this.dudChunkSize.Value * 1024;	// kb
			}
			// set the remote file name and start the background worker
			ftd.RemoteFileName = Path.GetFileName(path);

			if(String.IsNullOrEmpty(this.txtSaveFolder.Text.Trim()))
				ftd.LocalSaveFolder = Application.StartupPath;	// by default
			else
				ftd.LocalSaveFolder = this.txtSaveFolder.Text;

			ftd.IncludeHashVerification = this.chkHash.Checked;
			ftd.ProgressChanged += new ProgressChangedEventHandler(ft_ProgressChanged);
			ftd.RunWorkerCompleted += new RunWorkerCompletedEventHandler(ft_RunWorkerCompleted);
			ftd.RunWorkerSync(new DoWorkEventArgs(0));
		}

		void ft_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
		{
			string guid = (sender as FileTransferBase).Guid;
			if(e.Error != null)
			{
				this.taskPanel1.EndOperation(guid, e.Error);
				return;
			}
			else if(e.Cancelled)
			{
				this.taskPanel1.EndOperation(guid, new Exception("Cancelled"));
				return;
			}
			else
			{
				this.taskPanel1.EndOperation(guid, null);
				if(this.taskPanel1.List.Count == 0)	// play a ding on last item
					Ding();
			}
		}

		void ft_ProgressChanged(object sender, ProgressChangedEventArgs e)
		{
			this.taskPanel1.ProgressChanged((sender as FileTransferBase).Guid, e.ProgressPercentage, e.UserState.ToString());
		}

	

		#endregion

		#region Get Server File List code


		private void btnRefreshFiles_Click(object sender, EventArgs e)
		{
			if(this.workerGetFileList.IsBusy)
			{
				this.workerGetFileList.CancelAsync();
				Thread.Sleep(1000);
				if(this.workerGetFileList.IsBusy)
				{
					MessageBox.Show("Worker thread is busy and did not respond to a cancel, try again in a few seconds.", "Busy");
					return;
				}
			}

			// refresh the list of files from the Upload folder on the server. 
			this.workerGetFileList.RunWorkerAsync();
		}
		/// <summary>
		/// This method fetches a string[] of filenames from the Upload folder on the server.
		/// </summary>
		private void workerGetFileList_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
		{
			if(e.Error != null)
			{
				if(e.Error.Message.Contains("ReturnUrl=") || e.Error.Message.Contains("forcibly closed"))
				{
					this.lblErrors.Text = "Forms Authentication Required";
					this.chkLoginRequired.Checked = true;
					this.txtUsername.Focus();
					return;
				}
				else
					this.lblErrors.Text = "Could not list files on server: " + e.Error.Message;
				return;
			}
			this.lblErrors.Text = "";
			this.lstDownloadFiles.Items.Clear();
			// copy the list of filenames into the listbox
			foreach(string s in e.Result as string[])
				this.lstDownloadFiles.Items.Add(s);
		}


		private void workerGetFileList_DoWork(object sender, DoWorkEventArgs e)
		{
			// fetch the list of filenames from the web service
			string[] files = WebService.GetFilesList();
			// set the result with the return value (available to workerGetFileList_RunWorkerCompleted method as e.Result)
			e.Result = files;			
		}
		#endregion

		#region manual resume code, manual MD5 hash

		private void manualResumeDownloadToolStripMenuItem_Click(object sender, EventArgs e)
		{
			this.openFileDialog1.Title = "Select partially complete download file";
			if(this.openFileDialog1.ShowDialog() == DialogResult.Cancel)
				return;

			long offset = new FileInfo(this.openFileDialog1.FileName).Length;
			this.txtSaveFolder.Text = Path.GetDirectoryName(this.openFileDialog1.FileName);
			string guid = Guid.NewGuid().ToString();
			this.taskPanel1.AddOperation(new TaskPanelOperation(guid, String.Format("Downloading {0}", Path.GetFileName(this.openFileDialog1.FileName)), ProgressBarStyle.Blocks));
			ThreadPool.QueueUserWorkItem(new WaitCallback(this.DownloadFile), new Triplet(guid, this.openFileDialog1.FileName, offset));					
		}

		private void manualResumeUploadToolStripMenuItem_Click(object sender, EventArgs e)
		{
			this.openFileDialog1.Title = "Select the original local file to upload";			
			if(this.openFileDialog1.ShowDialog() == DialogResult.Cancel)
				return;
			
			// get the offset from the server (which has the partial file)
			long offset = WebService.GetFileSize(Path.GetFileName(this.openFileDialog1.FileName));
			string guid = Guid.NewGuid().ToString();
			this.taskPanel1.AddOperation(new TaskPanelOperation(guid, String.Format("Uploading {0}", Path.GetFileName(this.openFileDialog1.FileName)), ProgressBarStyle.Blocks));
			ThreadPool.QueueUserWorkItem(new WaitCallback(this.UploadFile), new Triplet(guid, this.openFileDialog1.FileName, offset));			
		}

		private void computeMD5HashToolStripMenuItem_Click(object sender, EventArgs e)
		{
			this.openFileDialog1.Title = "Select local file to hash";
			if(this.openFileDialog1.ShowDialog() == DialogResult.Cancel)
				return;

			this.lblStatusBarText.Text = "Calculating file hash...";
			this.Cursor = Cursors.WaitCursor;
			this.workerFileHash.RunWorkerAsync(this.openFileDialog1.FileName);
		}

		private void workerFileHash_DoWork(object sender, DoWorkEventArgs e)
		{
			e.Result = FileTransferBase.CalcFileHash(e.Argument.ToString());
		}

		private void workerFileHash_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
		{
			this.lblErrors.Text = "[Hash copied to clipboard]: " + e.Result.ToString();
			Clipboard.SetData(DataFormats.StringFormat, e.Result.ToString());
			this.lblStatusBarText.Text = "Ready";
			this.Cursor = Cursors.Default;
		}
		#endregion

		#region forms auth code

		private void chkLoginRequired_CheckedChanged(object sender, EventArgs e)
		{
			// enable or disable the username/password boxes
			this.lblUsername.Enabled = this.lblPassword.Enabled = this.txtUsername.Enabled = this.txtPassword.Enabled = this.chkLoginRequired.Checked;
		}

		private void btnLogin_Click(object sender, EventArgs e)
		{
			try
			{
				if(AuthenticateWebService())
				{
					this.lblErrors.Text = "";
					this.lblStatusBarText.Text = "Login OK";
					this.btnRefreshFiles.PerformClick();
				}
				else
					this.lblErrors.Text = "Login failed";
			}
			catch(Exception ex)
			{
				this.lblErrors.Text = "Login failed: " + ex.Message;
			}
		}

		private bool AuthenticateWebService()
		{
			if(WebService.CookieContainer != null && WebService.CookieContainer.Count > 0)
				return true;	// already authenticated.

			// send a HTTP web request to the login.aspx page, using the querystring to pass in username and password
			string postData = String.Format("?Username={0}&Password={1}", this.txtUsername.Text, this.txtPassword.Text);
			string url = WebService.Url.Replace("MTOM.asmx", "") + "Login.aspx" + postData;	// get the path of the login page, assuming it is in the same folder as the web service
			HttpWebRequest req = HttpWebRequest.Create(url) as HttpWebRequest;
			req.CookieContainer = new CookieContainer();
			HttpWebResponse response = (HttpWebResponse)req.GetResponse();

			// copy the cookie container to the web servicreqes
			WebService.CookieContainer = req.CookieContainer;

			return (response.Cookies.Count > 0);	// true if the server sent an auth cookie, i.e. authenticated successfully
		}
		#endregion

		private void dudMaxThreads_ValueChanged(object sender, EventArgs e)
		{
			ThreadPool.SetMaxThreads((int)this.dudMaxThreads.Value, (int)this.dudMaxThreads.Value);
		}

		private void chkAutoChunkSize_CheckedChanged(object sender, EventArgs e)
		{
			this.dudChunkSize.Enabled = !this.chkAutoChunksize.Checked;
		}

		private void lstDownloadFiles_SelectedIndexChanged(object sender, EventArgs e)
		{
			int numItems = this.lstDownloadFiles.SelectedItems.Count;
			this.btnDownload.Enabled = numItems > 0;
			if(numItems == 1)
				this.lblFileSizeDownload.Text = String.Format("File size: {0}", FileTransferBase.CalcFileSize(WebService.GetFileSize(this.lstDownloadFiles.SelectedItems[0].Text)));
			else
				this.lblFileSizeDownload.Text = "";
		}

		private void btnBrowseSaveFolder_Click(object sender, EventArgs e)
		{
			if(this.folderBrowserDialog1.ShowDialog() != DialogResult.Cancel)
				this.txtSaveFolder.Text = this.folderBrowserDialog1.SelectedPath;
		}
	}	
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
Ireland Ireland
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions