Web Service file transfer, how to add retries? - c#

I have a bit of code that uses a Task to transfer a file over a web service. However if the network drops or a timeout occurs it results in an incomplete file. I'm really trying to make this service as reliable as possible, but I'm not really sure where to even really start to add code that will trap when a chunk was not sent, and then attempt to resend it multiple times but also not send the next chunk until that is done. And maybe if it cannot resend that chunk over X times, fail completely and log an event.
Can anyone suggest anything?
Action<Guid, string> action = (smGuid, pubAttFullPath) =>
{
try
{
//Set filename from object
string FileName;
FileName = System.IO.Path.GetFileName(pubAttFullPath.ToString());
//Declare Web Service
TransferFile.TransferFileSoapClient ws_TransferFile = new TransferFile.TransferFileSoapClient();
//
bool transfercompleted = false;
using (FileStream fs = new FileStream(
pubAttFullPath.ToString(),
FileMode.Open,
FileAccess.Read,
FileShare.Read))
{
//Declare Buffers and Counts
byte[] buffer = new byte[49152];
long fileSize = fs.Length;
long totalReadCount = 0;
int readCount;
float percentageComplete = 0;
//Loop and copy file until it changes to not exactly the same byte count as the buffer
//which means the file is about to complete.
while ((readCount = fs.Read(buffer, 0, buffer.Length)) > 0)
{
if (!transfercompleted)
{
totalReadCount += readCount;
byte[] bytesToTransfer;
if (readCount == buffer.Length)
{
//Copy bytes until buffer is different
bytesToTransfer = buffer;
ws_TransferFile.WriteBinaryFile("ABCD", bytesToTransfer, FileName);
percentageComplete = (totalReadCount / (float)fileSize * 100);
percentageComplete = (float)Math.Round(percentageComplete, 2, MidpointRounding.ToEven);
//Update progress to DB
InsertProgress.InsertProgressSoapClient ws_InsertProgress = new InsertProgress.InsertProgressSoapClient();
if (percentageComplete == 10.00)
{
ws_InsertProgress.InsertProgressService(smGuid.ToString(), 10.0);
}
if (percentageComplete == 20.00)
{
ws_InsertProgress.InsertProgressService(smGuid.ToString(), 20.0);
}
if (percentageComplete == 30.00)
{
ws_InsertProgress.InsertProgressService(smGuid.ToString(), 30.0);
}
if (percentageComplete == 40.00)
{
ws_InsertProgress.InsertProgressService(smGuid.ToString(), 40.0);
}
if (percentageComplete == 50.00)
{
ws_InsertProgress.InsertProgressService(smGuid.ToString(), 50.0);
}
if (percentageComplete == 60.00)
{
ws_InsertProgress.InsertProgressService(smGuid.ToString(), 60.0);
}
if (percentageComplete == 70.00)
{
ws_InsertProgress.InsertProgressService(smGuid.ToString(), 70.0);
}
if (percentageComplete == 80.00)
{
ws_InsertProgress.InsertProgressService(smGuid.ToString(), 80.0);
}
if (percentageComplete == 90.00)
{
ws_InsertProgress.InsertProgressService(smGuid.ToString(), 90.0);
}
}
else
{
// Only a part is requred to upload,
// copy that part.
List<byte> b = new List<byte>(buffer);
bytesToTransfer = b.GetRange(0, readCount).ToArray();
ws_TransferFile.WriteBinaryFile("ABCD", bytesToTransfer, FileName);
percentageComplete = 100;
//Insert Progress as complete
InsertProgress.InsertProgressSoapClient ws_InsertProgress = new InsertProgress.InsertProgressSoapClient();
ws_InsertProgress.InsertProgressService(smGuid.ToString(), 100);
transfercompleted = true;
fs.Close();
break;
}
}
}
}
}
catch (Exception ex)
{
EventLog.WriteEntry("Application", ex.Message.ToString(), EventLogEntryType.Error);
}

Web service is bad idea for file transfer. I used it few times, but amount of additional data transfered is making size of transfered data 1,5-2 times bigger then sending file using simple handler. Handler will allow you to the same without issues. It causes al lot of problem with proper progress handling and resume. You realy should reconsider using httphandler. If you like to use webservice, here is good example:
http://msdn.microsoft.com/en-us/library/ms172362%28v=vs.85%29.aspx
If you decide to use IHttpHandler see:
http://msdn.microsoft.com/en-us/library/ms228090%28v=vs.100%29.aspx
And then you can use following piece of code to proper handle retry/resume:
using (Stream stream = new FileStream(
pubAttFullPath.ToString(),
FileMode.Open,
FileAccess.Read,
FileShare.Read))
{
context.Response.AddHeader("Accept-Ranges", "bytes");
context.Response.Buffer = false;
if (context.Request.Headers["Range"] != null)
{
context.Response.StatusCode = 206;
string[] range = context.Request.Headers["Range"].Split(new[] { '=', '-' });
startBytes = Convert.ToInt32(range[1]);
}
int dataToRead = size - startBytes;
context.Response.ContentType = "application/octet-stream";
context.Response.AddHeader("Content-Length", dataToRead.ToString());
context.Response.AddHeader("Connection", "Keep-Alive");
context.Response.AddHeader("Content-Disposition", "attachment; filename=" + HttpUtility.UrlEncode(fileName, Encoding.UTF8));
if (startBytes > 0)
{
context.Response.AddHeader("Content-Range", string.Format(" bytes {0}-{1}/{2}", startBytes, size - 1, size));
stream.Seek(startBytes, SeekOrigin.Begin);
}
while (dataToRead > 0)
{
// Verify that the client is connected.
if (context.Response.IsClientConnected)
{
// Read the data in buffer.
int length = stream.Read(buffer, 0, buffer.Length);
// Write the data to the current output stream.
context.Response.OutputStream.Write(buffer, 0, length);
// Flush the data to the HTML output.
context.Response.Flush();
dataToRead = dataToRead - length;
}
else
{
// prevent infinite loop if user disconnects
dataToRead = -1;
}
}
}

Related

IHttpHandler over Https on IE

I'm looking for your expertise/help figuring out why my http handler works perfectly over http and doesn't over https (only IE problem, other browsers work fine either way). I'm using the handler to display images and when https is used on IE, it shows some images and fails to show others images.
again, the problem is only happening on https and IE, all other mutations with other browsers work just fine. below is my code:
public void ProcessRequest(HttpContext context)
{
Stream iStream = null;
try
{
if (!context.Response.IsClientConnected || HttpContext.Current == null || HttpContext.Current.Session["GoodUser"] == null)
return;
var url = context.Request.QueryString[#"ID"];
if (string.IsNullOrWhiteSpace(url))
return;
var fileUrl = EncryptionManager.DecryptUrl(url);
if (!File.Exists(fileUrl))
return;
var buffer = new byte[4096];
// Open the file.
iStream = new FileStream(fileUrl, FileMode.Open, FileAccess.Read, FileShare.Read);
// Total bytes to read:
var dataToRead = iStream.Length;
context.Response.AddHeader(#"Accept-Ranges", #"bytes");
context.Response.AddHeader(#"Content-Length", dataToRead.ToString());
context.Response.StatusCode= (int)HttpStatusCode.OK;
var file = new FileInfo(fileUrl);
context.Response.ContentType = MimeTypeMap.GetMimeType(file.Extension);
context.Response.Cache.SetCacheability(HttpCacheability.NoCache);
context.Response.Cache.SetNoStore();
context.Response.Cache.SetExpires(DateTime.MinValue);
if (!string.IsNullOrEmpty(context.Request.Headers[#"Range"]))
{
var range = context.Request.Headers[#"Range"].Split('=', '-');
var startbyte = int.Parse(range[1]);
iStream.Seek(startbyte, SeekOrigin.Begin);
context.Response.StatusCode = 206;
context.Response.AddHeader(#"Content-Range", $#" bytes {startbyte}-{dataToRead - 1}/{dataToRead}");
}
while (dataToRead > 0)
{
iStream.Read(buffer, 0, buffer.Length);
if (context.Response.IsClientConnected)
{
context.Response.OutputStream.Write(buffer, 0, buffer.Length);
context.Response.Flush();
buffer = new byte[buffer.Length];
dataToRead = dataToRead - buffer.Length;
}
else
{
dataToRead = -1;
}
}
}
catch (Exception exception)
{
}
finally
{
iStream?.Close();
context.ApplicationInstance.CompleteRequest();
}
}

How can i download a file faster?

I have made a download program in C#. It is a queue downloader.
You can see how it works here: Click Express Downloader
Is there any faster method to download?
Here is the method i use, it must support resume support.
private void Download(object startPoint)
{
try
{
try
{
//int startPointInt = Convert.ToInt32(startPoint);
Int64 startPointInt = Convert.ToInt64(startPoint);
webRequest = (HttpWebRequest)WebRequest.Create(url);
webRequest.AddRange(startPointInt);
webRequest.Credentials = CredentialCache.DefaultCredentials;
webResponse = (HttpWebResponse)webRequest.GetResponse();
Int64 fileSize = webResponse.ContentLength;
strResponse = webResponse.GetResponseStream();
if (startPointInt == 0)
{
strLocal = new FileStream(txtPath.Text + "\\" + filename, FileMode.Create, FileAccess.Write, FileShare.None);
}
else
{
strLocal = new FileStream(txtPath.Text + "\\" + filename, FileMode.Append, FileAccess.Write, FileShare.None);
}
int bytesSize = 0;
byte[] downBuffer = new byte[4096];
while ((bytesSize = strResponse.Read(downBuffer, 0, downBuffer.Length)) > 0)
{
strLocal.Write(downBuffer, 0, bytesSize);
this.Invoke(new UpdateProgessCallback(this.UpdateProgress), new object[] { strLocal.Length, fileSize + startPointInt });
if (goPause == true)
{
break;
}
}
}
catch { }
}
finally
{
strResponse.Close();
strLocal.Close();
}
}
You have to do two things,
Get faster bandwidth
Change block size from 4096 to something like 8192 or any number which can be hold in memory easily.

Download a file with .net compact and C#

I am re-developing an app for a scanner used for stocktakes to allow it to work while offline. In order to do so, I need to be able to download a file from a laptop which is acting as a server. I got to a point at which it works, but only downloads that are of size 9.53mb max. How can I tweak the code to allow for larger files. I would need to allow for a maximum size of around 30mb.
Here is my code:
try
{
string full_url = App.prouductUrl + App.stStocktakeId + ".db";
HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(full_url);
httpRequest.Credentials = CredentialCache.DefaultCredentials;
HttpWebResponse httpResponse = (HttpWebResponse)httpRequest.GetResponse();
System.IO.Stream dataStream = httpResponse.GetResponseStream();
// Dim str As Stream = cdsMobileLibrary2.http_download.getfile(filename)
//50 meg
byte[] inBuf = new byte[10000001];
int bytesToRead = Convert.ToInt32(inBuf.Length);
int bytesRead = 0;
while (bytesToRead > 0)
{
int n = dataStream.Read(inBuf, bytesRead, bytesToRead);
if (n == 0)
{
break; // TODO: might not be correct. Was : Exit While
}
bytesRead += n;
bytesToRead -= n;
}
FileStream fstr = new FileStream(#"\productdb\" + App.stStocktakeId + ".db", FileMode.OpenOrCreate, FileAccess.Write);
fstr.Write(inBuf, 0, bytesRead);
dataStream.Close();
fstr.Close();
string size = loginRes.getFromJSON("size");
FileInfo fi = new FileInfo(#"\productdb\" + App.stStocktakeId + ".db");
MessageBox.Show("File Size is:" + fi.Length + "Compared to:" + size);
}
catch { }

Download not working once the size less than buffer length c#

today while doing a logic for downloading large size files, i am facing an error. For downloading what my logic is am spltting the file into 10KB chunks and then integrating it and is downloading.While downloading what happening is like each time am reducing the total size by 10KB , but once the remaining length is less than 10KB my download is getting interuppted. Please find the below code of mine and let me know if any change needed in my logic.
protected void btnDownload_Click(object sender, EventArgs e)
{
SPSecurity.RunWithElevatedPrivileges(delegate()
{
System.IO.Stream iStream = null;
// Buffer to read 10K bytes in chunk:
byte[] buffer = new Byte[10000];
// Length of the file:
int length;
// Total bytes to read:
long dataToRead;
// Identify the file to download including its path.
string filepath = "C:\\Users\\GZT00000000000001020\\Desktop\\123.zip";
// Identify the file name.
string filename = System.IO.Path.GetFileName(filepath);
try
{
iStream = new System.IO.FileStream(filepath, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.Read);
dataToRead = iStream.Length;
Page.Response.ContentType = "application/octet-stream";
Page.Response.AddHeader("Content-Disposition", "attachment; filename=" + filename);
// Read the bytes.
while (dataToRead > 0)
{
// Verify that the client is connected.
if (Page.Response.IsClientConnected)
{
if( (dataToRead < 10000) && (dataToRead!=-1))
{
length = (int)dataToRead;
buffer = new Byte[length];
dataToRead = -1;
}
else
{
// Read the data in buffer.
length = iStream.Read(buffer, 0, 10000);
}
// Write the data to the current output stream.
Page.Response.OutputStream.Write(buffer, 0, length);
// Flush the data to the HTML output.
Page.Response.Flush();
if (dataToRead > 10000)
{
buffer = new Byte[10000];
dataToRead = dataToRead - length;
}
else if(dataToRead!=-1)
{
length =(int)dataToRead ;
buffer = new Byte[length];
}
}
else
{
//prevent infinite loop if user disconnects
dataToRead = -1;
}
}
}
catch (Exception ex)
{
// Trap the error, if any.
Page.Response.Write("Error : " + ex.Message);
}
finally
{
if (iStream != null)
{
//Close the file.
iStream.Close();
}
Page.Response.Close();
}
});
}
You don't call iStream.Read in the branch where dataToRead < 10000

using WebClient to read and save large files in WP7

I am developing a music player in wp7 using silverlight.My end users don't have good internet connection when they are roaming. I want to give them an option to download music in their phone.
For that i have written a download manager which they can use to download music when they have wifi connection.
I ussue i am facing is that,My music is stored on my server and i am downloading the music on the phone using WebCLient class.
if size of file goes beyond 68 MB , my application gets OutOfMemory exception.
here is the code:
public void DownloadKirtan(KirtanViewModel kirtanVm,bool QueueItem=true)
{
{
if (kirtanVm.LocationPath != null)
{
WebClient webClient = new WebClient();
//webClient.AllowWriteStreamBuffering = false;
// webClient.AllowReadStreamBuffering = false;
if (QueueItem == false)
{
//App.ViewModel.ActiveInstancesOfWebClientForKirtanDownload[kirtanVm] = webClient;
// App.ViewModel.ActiveInstancesOfWebClientForKirtanDownload.Add(kirtanVm//
webClient = App.ViewModel.ActiveInstancesOfWebClientForKirtanDownload[kirtanVm];
kirtanVm.IsDownloadedForOfflineViewing = "Started";
webClient.OpenReadCompleted += new OpenReadCompletedEventHandler(webClient_OpenReadCompleted);
webClient.OpenReadAsync(kirtanVm.LocationPath, kirtanVm);
}
else if (!App.ViewModel.ActiveInstancesOfWebClientForKirtanDownload.ContainsKey(kirtanVm))
{
App.ViewModel.ActiveInstancesOfWebClientForKirtanDownload.Add(kirtanVm, webClient);
kirtanVm.IsDownloadedForOfflineViewing = "Started";
webClient.OpenReadCompleted += new OpenReadCompletedEventHandler(webClient_OpenReadCompleted);
webClient.OpenReadAsync(kirtanVm.LocationPath, kirtanVm);
}
// webClient.DownloadProgressChanged += new DownloadProgressChangedEventHandler(webClient_DownloadProgressChanged);
}
}
}
void webClient_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
KirtanViewModel kirtanVm = e.UserState as KirtanViewModel;
try
{
if (e.Cancelled == false)
{
if (e.Result != null)
{
((WebClient)sender).OpenReadCompleted -= webClient_OpenReadCompleted;
#region Isolated Storage Copy Code
IsolatedStorageFile isolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication();
bool checkQuotaIncrease = IncreaseIsolatedStorageSpace(e.Result.Length);
if (checkQuotaIncrease)
{
string VideoFile = "";
VideoFile = GetUrlOfOfflineContent(kirtanVm);
using (IsolatedStorageFileStream isolatedStorageFileStream = new IsolatedStorageFileStream(VideoFile, FileMode.Create, isolatedStorageFile))
{
long VideoFileLength = (long)e.Result.Length;
byte[] byteImage = new byte[VideoFileLength];
e.Result.Read(byteImage, 0, byteImage.Length);
isolatedStorageFileStream.Write(byteImage, 0, byteImage.Length);
kirtanVm.IsDownloadedForOfflineViewing = "True";
kirtanVm.DownloadSize = "Size=" + (VideoFileLength / 1000000).ToString() + " MB";
kirtanVm.DownloadProgress = "100%";
Settings.OfflineKirtanContents.Value.Add(kirtanVm);
AddRemoveKirtanInOfflineModels(kirtanVm, true);
}
#endregion
}
else
{
kirtanVm.IsDownloadedForOfflineViewing = "False";
App.ViewModel.ActiveInstancesOfWebClientForKirtanDownload.Remove(kirtanVm);
MessageBox.Show("There is not enough space in your phone to store this media. You need " + e.Result.Length + " bytes of storage.Please free some offline contents you have downloaded by going to Offline content managemnt screen in Settings Section and try again or increase the storage on your phone.");
}
// mediaFile.SetSource(isolatedStorageFileStream);
// mediaFile.Play();
// progressMedia.Visibility = Visibility.Collapsed;
}
}
}
catch (Exception ex)
{
kirtanVm.IsDownloadedForOfflineViewing = "Failed";
//App.ViewModel.ActiveInstancesOfWebClientForKirtanDownload.Remove(kirtanVm);
MessageBox.Show(ex.ToString());
}
}
The problem i am having is When i get e.result which is a stream object. To write to isolatedStoreFileStream, i have to read it again to byte array and then save it to isolatedstoragefile. This is inefficient, it consumes double memory. Memory of 60 MB (for 60MB file) by WebClient e.result stream object and then 60 MB to convert to array. So memory consumtion is 128 MB . Is there is better way to download big files and store it to IsolatedStorage>
UPDATE : I am now using following code using the chunck size , instead of reading all in memory but i still get out of memory error on large files 100 MB
void webClient_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
KirtanViewModel kirtanVm = e.UserState as KirtanViewModel;
try
{
if (e.Cancelled == false)
{
if (e.Result != null)
{
((WebClient)sender).OpenReadCompleted -= webClient_OpenReadCompleted;
#region Isolated Storage Copy Code
bool checkQuotaIncrease = IncreaseIsolatedStorageSpace(e.Result.Length);
if (checkQuotaIncrease)
{
string VideoFile = "";
VideoFile = GetUrlOfOfflineContent(kirtanVm);
ThreadPool.QueueUserWorkItem( k =>{
using (IsolatedStorageFile isolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication())
{
using (IsolatedStorageFileStream isolatedStorageFileStream = new IsolatedStorageFileStream(VideoFile, FileMode.Create, isolatedStorageFile))
{
long VideoFileLength = (long)e.Result.Length;
using (BinaryWriter writer = new BinaryWriter(isolatedStorageFileStream))
{
Stream resourceStream = e.Result;//streamResourceInfo.Stream;
long length = resourceStream.Length;
byte[] buffer = new byte[32];
int readCount = 0;
using (BinaryReader reader = new BinaryReader(resourceStream))
{ // read file in chunks in order to reduce memory consumption and increase performance
while (readCount < length)
{
int actual = reader.Read(buffer, 0, buffer.Length);
readCount += actual;
writer.Write(buffer, 0, actual);
}
}
}
kirtanVm.IsDownloadedForOfflineViewing = "True";
kirtanVm.DownloadSize = "Size=" + (VideoFileLength / 1000000).ToString() + " MB";
kirtanVm.DownloadProgress = "100%";
Settings.OfflineKirtanContents.Value.Add(kirtanVm);
AddRemoveKirtanInOfflineModels(kirtanVm, true);
}
}
});
//byte[] byteImage = new byte[VideoFileLength];
//e.Result.Read(byteImage, 0, byteImage.Length);
// e.re
//ThreadPool.QueueUserWorkItem( k =>{
// isolatedStorageFileStream.Write(byteImage, 0, byteImage.Length);
//isolatedStorageFileStream.Close();
//Application.Current.RootVisual.Dispatcher.BeginInvoke( ()=>
// {
// kirtanVm.IsDownloadedForOfflineViewing = "True";
// kirtanVm.DownloadSize = "Size=" + (VideoFileLength / 1000000).ToString() + " MB";
// kirtanVm.DownloadProgress = "100%";
// Settings.OfflineKirtanContents.Value.Add(kirtanVm);
// AddRemoveKirtanInOfflineModels(kirtanVm, true);
// });
//});
//StreamWriter writer=new StreamWriter(isolatedStorageFileStream);
// writer.Write(e.Result);
// writer.Close();
// isolatedStorageFileStream.Write(
// e.Result.Write(
// isolatedStorageFileStream.Write(byteImage, 0, byteImage.Length);
// isolatedStorageFileStream.Close();
}
#endregion
else
{
kirtanVm.IsDownloadedForOfflineViewing = "False";
App.ViewModel.ActiveInstancesOfWebClientForKirtanDownload.Remove(kirtanVm);
MessageBox.Show("There is not enough space in your phone to store this media. You need " + e.Result.Length + " bytes of storage.Please free some offline contents you have downloaded by going to Offline content managemnt screen in Settings Section and try again or increase the storage on your phone.");
}
}
}
else
{
lock (App.ViewModel.LockForCancelForCurrentOfflineDownload)
{
if (App.ViewModel.cancelInitiatedByUserForCurrentOfflineDownload)
{
kirtanVm.IsDownloadedForOfflineViewing = "False";
// kirtanVm.IsOfflineDownloadCancelled = false;
App.ViewModel.cancelInitiatedByUserForCurrentOfflineDownload = false;
}
else
{
if (kirtanVm.IsDownloadedForOfflineViewing == "Started")// don't queue things again
{
kirtanVm.IsDownloadedForOfflineViewing = "Failed";
// bool ItemExist = App.ViewModel.ActiveInstancesOfWebClientForKirtanDownload.Any(k => k.Key.Kirtan.KirtanId == kirtanVm.Kirtan.KirtanId);
DownloadKirtan(kirtanVm, false);
}
}
}
}
}
catch (Exception ex)
{
kirtanVm.IsDownloadedForOfflineViewing = "Failed";
//App.ViewModel.ActiveInstancesOfWebClientForKirtanDownload.Remove(kirtanVm);
MessageBox.Show(ex.ToString());
}
}
Thanks
Verinder
Finally i found the solution
turn on webClient.AllowReadStreamBuffering = false; in the above code which i shared and then in
void webClient_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e) {} method.. Remove all refrences to e.Result.Length. When using no buffering solution you cannot know the stream size in advance
long length = resourceStream.Length;
byte[] buffer = new byte[1024];
int readCount = 0;
using (BinaryReader reader = new BinaryReader(resourceStream)) { // read file in chunks in order to reduce memory consumption and increase performance
while (true)
{
int actual = reader.Read(buffer, 0, 1024);
if(actual==0)
{
break;
}
else
{
readCount += actual;
writer.Write(buffer, 0, actual);
}
}
}
Keep on reading the stream with 1024 bytes at a time unless you are done reading. This way WebClient will not buffer whole file in memory and max mem usage on your phone will be 1KB at a time. You can download Gigs of data like this. INcrease the buffer size to 1MB or 3 MB , if you want to read faster.
Make sure you run this code in ThreadPool instead of user thread
You need to write it one chunk at a time by reading into a 4KB (or so) array and writing that to isolated storage until you run out of data.

Categories