Detect if file was downloaded successfully in ASP.NET MVC 5 - c#

my ActionResult provides a StreamContent file in the HttpResponseMessage result.Content. Now I would like to track the state of the download, to delete the file immediately after it was downloaded.
I found solutions that use the ByteStream, which split the file into chunks but that lacks the possibilities to provide a HttpStatusCode and other information in case some authorization tests deny the request.
This is my current controller:
[HttpGet]
public HttpResponseMessage GetZipDownload(string token, string guid)
{
if (!dOps.ValidateToken(token))
{
return Request.CreateResponse(HttpStatusCode.Unauthorized,
new HttpError("Unauthorized"));
}
Device device = dOps.GetDeviceFromToken(auth);
AssetOperations assetOps = new AssetOperations();
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
FileStream file = assetOps.GetZip(guid);
var content = new StreamContent(file);
result.Content = content;
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/zip");
result.Content.Headers.Add("Connection", "Keep-Alive");
result.Content.Headers.Add("Content-Length", (file.Length).ToString());
return result;
}
Before I dig deeper into the ByteStream solution, I would like to ask if anybody probably knows about a reliable solution for ASP.NET MVC 5.

You can register EndRequest event handler to achieve it.
Override Init() method inside MvcApplication class, and register to EndRequest event handler.
Save into HttpContext.Current.Items the file path with a constant key.
Global.asax file
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
// some startup code, removed for clearity
}
public override void Init()
{
base.Init();
EndRequest += MvcApplication_EndRequest;
}
private void MvcApplication_EndRequest(object sender, EventArgs e)
{
if (
HttpContext.Current.Request.Url.AbsolutePath == "/DownloadFileEndedWebsite/Home/Contact" // This should be adjusted.
&& HttpContext.Current.Items.Contains("DownloadFilePath"))
{
var filePath = HttpContext.Current.Items["DownloadFilePath"];
// Delete file here..
}
}
}
HomeController.cs
public ActionResult Contact()
{
HttpContext.Items.Add("DownloadFilePath", "DownloadFilePathValue");
return View();
}

You should create a method like: startDownload() from the UI thread. The idea of WebClient.DownloadFileAsync() is that it will spawn a worker thread for you automatically without blocking the calling thread.
As this thread explains, the Trhead will help you to have control of the download,
private void startDownload()
{
Thread thread = new Thread(() => {
WebClient client = new WebClient();
client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(client_DownloadProgressChanged);
client.DownloadFileCompleted += new AsyncCompletedEventHandler(client_DownloadFileCompleted);
client.DownloadFileAsync(new Uri("http://joshua-ferrara.com/luahelper/lua.syn"), #"C:\LUAHelper\Syntax Files\lua.syn");
});
thread.Start();
}
void client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
this.BeginInvoke((MethodInvoker) delegate {
var bytesReceived = e.BytesReceived;
//double bytesIn = double.Parse(e.BytesReceived.ToString());
//double totalBytes = double.Parse(e.TotalBytesToReceive.ToString());
//double percentage = bytesIn / totalBytes * 100;
//label2.Text = "Downloaded " + e.BytesReceived + " of " + e.TotalBytesToReceive;
//progressBar1.Value = int.Parse(Math.Truncate(percentage).ToString()); // YOU CAN HAVE CONTROL OF THE PERCENTAGE OF THE DOWNLOAD, BUT, FOR MVC MAYBE IS BETTER NOT USE IT
});
}
void client_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
this.BeginInvoke((MethodInvoker) delegate {
//TODO: EXECUTE AN EVENT HERE, **THE DOWNLOAD ALREADY IS COMPLETED**!!!
});
}

Related

Wait for download to complete

I'm trying to create a simple program to download few files. I've tried some ready-made solutions I found on the web but I can't manage to make it work the way I want it to. I'm using this:
private void startDownload(string toDownload, string saveLocation)
{
Thread thread = new Thread(() =>
{
WebClient client = new WebClient();
client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(client_DownloadProgressChanged);
client.DownloadFileCompleted += new AsyncCompletedEventHandler(client_DownloadFileCompleted);
client.DownloadFileAsync(new Uri(toDownload), saveLocation);
});
thread.Start();
}
void client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
this.BeginInvoke((MethodInvoker)delegate
{
double bytesIn = double.Parse(e.BytesReceived.ToString());
double totalBytes = double.Parse(e.TotalBytesToReceive.ToString());
double percentage = bytesIn / totalBytes * 100;
labelPercentage.Text = "Downloading " + Convert.ToInt32(percentage) + "% - " + Convert.ToInt32(bytesIn / 1024) + " / " + Convert.ToInt32(totalBytes / 1024) + " kB";
progressBar1.Value = int.Parse(Math.Truncate(percentage).ToString());
});
}
void client_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
this.BeginInvoke((MethodInvoker)delegate
{
textBoxLog.AppendText("OK");
});
}
I'd like to make the program continue (download next file / show the "OK" message / do whatever is in the next line of code) AFTER the download has finished.
In the current form, if I'd put eg.
private void button1_Click(object sender, EventArgs e)
{
startDownload(url, localpath + #"\file.zip");
textBoxLog.AppendText("the cake is a lie");
}
it's showing me this text first and "OK" later.
I'm beginning with c#/.net and I've never learned object-oriented programming before so it's kind of double challenge for me and I can't figure it out by myself. I would be really grateful for relatively easy explanation.
You can have startDownload wait for the asynchronous file download through Application.DoEvents() like this:
private bool downloadComplete = false;
private void startDownload(Uri toDownload, string saveLocation)
{
string outputFile = Path.Combine(saveLocation, Path.GetFileName(toDownload.AbsolutePath));
WebClient client = new WebClient();
client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(client_DownloadProgressChanged);
client.DownloadFileCompleted += new AsyncCompletedEventHandler(client_DownloadFileCompleted);
client.DownloadFileAsync(toDownload, outputFile);
while (!downloadComplete)
{
Application.DoEvents();
}
downloadComplete = false;
}
void client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
// No changes in this method...
}
void client_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
this.BeginInvoke((MethodInvoker)delegate
{
textBoxLog.AppendText("OK");
downloadComplete = true;
});
}
And the download queue...
private void button1_Click(object sender, EventArgs e)
{
FireDownloadQueue(urls, localpath);
textBoxLog.AppendText("the cake is a lie");
}
private async void FireDownloadQueue(Uri[] toDownload, string saveLocation)
{
foreach (var url in urls)
{
await Task.Run(() => startDownload(url, localpath));
}
}
However, I think you're better off reading about HttpWebRequest and writing your own downloader class with proper checks and events...
Here's a pretty good example by Hemanshu Bhojak (Source) that you can expand upon:
public class Downloader
{
public async Task Download(string url, string saveAs)
{
var httpClient = new HttpClient();
var response = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, url));
var parallelDownloadSuported = response.Headers.AcceptRanges.Contains("bytes");
var contentLength = response.Content.Headers.ContentLength ?? 0;
if (parallelDownloadSuported)
{
const double numberOfParts = 5.0;
var tasks = new List<Task>();
var partSize = (long)Math.Ceiling(contentLength / numberOfParts);
File.Create(saveAs).Dispose();
for (var i = 0; i < numberOfParts; i++)
{
var start = i*partSize + Math.Min(1, i);
var end = Math.Min((i + 1)*partSize, contentLength);
tasks.Add(
Task.Run(() => DownloadPart(url, saveAs, start, end))
);
}
await Task.WhenAll(tasks);
}
}
private async void DownloadPart(string url, string saveAs, long start, long end)
{
using (var httpClient = new HttpClient())
using (var fileStream = new FileStream(saveAs, FileMode.Open, FileAccess.Write, FileShare.Write))
{
var message = new HttpRequestMessage(HttpMethod.Get, url);
message.Headers.Add("Range", string.Format("bytes={0}-{1}", start, end));
fileStream.Position = start;
await httpClient.SendAsync(message).Result.Content.CopyToAsync(fileStream);
}
}
}
Example usage:
Task.Run(() => new Downloader().Download(downloadString, saveToString)).Wait();
With something along the lines of:
public class Downloader
{
public event EventHandler DownloadProgress;
DownloaderEventArgs downloaderEventArgs;
public void DownloadStarted(DownloaderEventArgs e)
{
EventHandler downloadProgress = DownloadProgress;
if (downloadProgress != null)
downloadProgress(this, e);
}
// ...
}
class DownloaderEventArgs : EventArgs
{
public string Filename { get; private set; }
public int Progress { get; private set; }
public DownloaderEventArgs(int progress, string filename)
{
Progress = progress;
Filename = filename;
}
public DownloaderEventArgs(int progress) : this(progress, String.Empty)
{
Progress = progress;
}
}
startDownload initiates the download on a new thread, so when you invoke startDownload it starts the thread and the rest of the code after that continues immediately because it is on a separate thread. That is why you are seeing "the cake is a lie" before the "OK".

Why doesn't delegate call the function?

1: Can someone explain the last line of the first function to me?
2: The second function doesn't work. Please tell me why.The PHP script is fetching the data.
I edited the code to get this, but the app now crashes with a System nullreferenceexception.
Please help.
private void checkbutton_Click(object sender, RoutedEventArgs e)
{
statustext.Text = "Checking for new score";
var webclient = new WebClient();
webclient.OpenReadCompleted += new OpenReadCompletedEventHandler(getscores_OpenReadCompleted);
webclient.OpenReadAsync(new Uri("http://example.com/get.php?"+DateTime.Now));
}
void getscores_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
StreamReader s = null;
Stream reply=null;
try
{
reply = (Stream)e.Result;
s = new StreamReader(reply);
}
catch
{
statustext.Text = "ERROR IN FETCHING";
}
scorebox.Text = s.ReadToEnd();
statustext.Text = "DoNE";
}
The last line of the first method is attaching a handler to an event. It is saying that when the OpenReadCompleted event fires, that is to say when the read completes, the getscores_OpenReadCompleted method should be called.
The getscores_OpenReadCompleted doesn't work because it's trying to access a UI element from a non-UI thread.
You're also adding the handler after starting the asynchronous operation, so while it's unlikely, it's certainly possible that the operation completes very quickly and the event is fired before you add the handler. While this situation would be very unusual, it's fixed very quickly and easily by simply adding the handler before you start the asynchronous operation.
There are a couple of issues here:
Register the delegate before the call to OpenReadAsync
Read the stream from the event arguments and close the stream when you're done.
private void checkbutton_Click(object sender, RoutedEventArgs e)
{
statustext.Text = "Checking for new score";
var webclient = new WebClient();
webclient.OpenReadCompleted += new OpenReadCompletedEventHandler(getscores_OpenReadCompleted);
webclient.OpenReadAsync(new Uri("http://example.com/get.php?"+DateTime.Now));
}
void getscores_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
Stream reply = null;
StreamReader s = null;
string outputText = string.Empty;
try
{
reply = (Stream)e.Result;
s = new StreamReader(reply);
outputText = s.ReadToEnd();
}
finally
{
if (s != null)
{
s.Close();
}
if (reply != null)
{
reply.Close();
}
}
statustext.Text = outputText;
}
See the usage of the OpenReadAsync method here:
http://msdn.microsoft.com/en-us/library/system.net.openreadcompletedeventhandler(v=vs.110).aspx
and here
http://msdn.microsoft.com/en-us/library/ms144211(v=vs.110).aspx

C# Multiple progress bars for multiple file downloads

I am writing a C# application for uploading and downloading file. The download uses WebClient object and its DownloadAsycDownload method. The download works fine for multiple files. It downloads as much files as I want.
My problem is I am not able to show the progress of all file in different progress bars which are dynamically added to the form's flowlayout control.
Here is my code:
public ProgressBar[] bar;
public int countBar=0;
...
bar[countBar] = new ProgressBar();
flowLayoutPanel1.Controls.Add(bar[countBar]);
countBar++;
request.DownloadProgressChanged += new DownloadProgressChangedEventHandler(DownoadInProgress);
request.DownloadFileCompleted += new AsyncCompletedEventHandler(DownloadFileCompleted);
request.DownloadFileAsync(new Uri(this.uri), localPath);
byte[] fileData = request.DownloadData(this.uri);
FileStream file = File.Create(localPath);
file.Write(fileData, 0, fileData.Length);
file.Close();
}
public void DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
flowLayoutPanel1.Controls.Remove(bar[countBar]);
countBar--;
MessageBox.Show("Download Completed");
}
public void DownoadInProgress(object sender, DownloadProgressChangedEventArgs e)
{
bar[countBar].Maximum = total_bytes;
bar[countBar].Value = (int)e.BytesReceived;
}
You're using a count to index into progress bars, but once one is complete - you remove the last one, where you really should remove the one associated with the file.
I suggest, in this case, using a Dictionary<WebClient, ProgressBar> (might not be WebCliet - should be the type of sender in the events).
...
var progBar = new ProgressBar();
progBar.Maximum = 100;
flowLayoutPanel1.Controls.Add(progBar);
request.DownloadProgressChanged += DownoadInProgress;
request.DownloadFileCompleted += DownloadFileCompleted;
request.DownloadFileAsync(new Uri(this.uri), localPath);
dic.Add(request, progBar);
// You shouldn't download the file synchronously as well!
// You're already downloading it asynchronously.
// byte[] fileData = request.DownloadData(this.uri);
// FileStream file = File.Create(localPath);
// file.Write(fileData, 0, fileData.Length);
// file.Close();
Then, you can remove countBar altogether, and have the new methods:
public void DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
// Can remove (WebClient) cast, if dictionary is <object, ProgressBar>
var request = (WebClient)sender;
flowLayoutPanel1.Controls.Remove(dic[request]);
dic.Remove(request);
MessageBox.Show("Download Completed");
}
public void DownoadInProgress(object sender, DownloadProgressChangedEventArgs e)
{
var progBar = dic[(WebClient)sender];
progBar.Value = e.ProgressPercentage;
}
I would use something like this with lambdas, a bit more concise, no need in dictionary:
var webClient = new WebClient();
var pb = new ProgressBar();
pb.Maximum = 100;
flowLayoutPanel1.Controls.Add(pb);
webClient.DownloadProgressChanged += (o, args) =>
{
pb.Value = args.ProgressPercentage;
};
webClient.DownloadFileCompleted += (o, args) =>
{
flowLayoutPanel1.Controls.Remove(pb);
};
webClient.DownloadFileAsync(new Uri(this.uri), localPath);

Creating a multi-thread webdownload via WebClient / BeginInvoke Method

I have actually the following code:
private Stopwatch _sw;
public void DownloadFile(string url, string fileName)
{
string path = #"C:\DL\";
Thread bgThread = new Thread(() =>
{
_sw = new Stopwatch();
_sw.Start();
labelDownloadAudioStatusText.Visibility = Visibility.Visible;
using (WebClient webClient = new WebClient())
{
webClient.DownloadFileCompleted +=
new AsyncCompletedEventHandler(DownloadCompleted);
webClient.DownloadProgressChanged +=
new DownloadProgressChangedEventHandler(DownloadStatusChanged);
webClient.DownloadFileAsync(new Uri(url), path + fileName);
}
});
bgThread.Start();
}
void DownloadStatusChanged(object sender, DownloadProgressChangedEventArgs e)
{
this.BeginInvoke((MethodInvoker) delegate
{
int percent = 0;
if (e.ProgressPercentage != percent)
{
percent = e.ProgressPercentage;
progressBarDownloadAudio.Value = percent;
labelDownloadAudioProgress.Content = percent + "%";
labelDownloadAudioDlRate.Content =
(Convert.ToDouble(e.BytesReceived)/1024/
_sw.Elapsed.TotalSeconds).ToString("0.00") + " kb/s";
Thread.Sleep(50);
}
});
}
private void DownloadCompleted(object sender, AsyncCompletedEventArgs e)
{
this.BeginInvoke((MethodInvoker) delegate
{
labelDownloadAudioDlRate.Content = "0 kb/s";
labelDownloadAudioStatusText.Visibility = Visibility.Hidden;
});
}
My problem is that in a previous version without the outer thread, the whole GUI freezes sporadically and the GUI is liquid when the download is finished. So I googled around and found this: https://stackoverflow.com/a/9459441/2288470
An answer was to pack everything into a separate thread which performs the interaction with DownloadFileAsync, but I got the fault, that the BeginInvoke method can not be found.
When using WPF, the BeginInvoke method is not exposed by the Window class, like it is for Form in WinForms. Instead you should use Dispatcher.BeginInvoke.
Working code:
private Stopwatch _sw;
public void DownloadFile(string url, string fileName)
{
string path = #"C:\DL\";
Thread bgThread = new Thread(() =>
{
_sw = new Stopwatch();
_sw.Start();
labelDownloadAudioStatusText.Visibility = Visibility.Visible;
using (WebClient webClient = new WebClient())
{
webClient.DownloadFileCompleted +=
new AsyncCompletedEventHandler(DownloadCompleted);
webClient.DownloadProgressChanged +=
new DownloadProgressChangedEventHandler(DownloadStatusChanged);
webClient.DownloadFileAsync(new Uri(url), path + fileName);
}
});
bgThread.Start();
}
void DownloadStatusChanged(object sender, DownloadProgressChangedEventArgs e)
{
Dispatcher.BeginInvoke((MethodInvoker) delegate
{
int percent = 0;
if (e.ProgressPercentage != percent)
{
percent = e.ProgressPercentage;
progressBarDownloadAudio.Value = percent;
labelDownloadAudioProgress.Content = percent + "%";
labelDownloadAudioDlRate.Content =
(Convert.ToDouble(e.BytesReceived)/1024/
_sw.Elapsed.TotalSeconds).ToString("0.00") + " kb/s";
Thread.Sleep(50);
}
});
}
private void DownloadCompleted(object sender, AsyncCompletedEventArgs e)
{
Dispatcher.BeginInvoke((MethodInvoker) delegate
{
labelDownloadAudioDlRate.Content = "0 kb/s";
labelDownloadAudioStatusText.Visibility = Visibility.Hidden;
});
}
Invoke method and BeginInvoke method is implemented on System.Windows.Forms.Control Class. If you are not writing code in such as Form Class, you cannot use this method. To resolve this problem, inherit your Job Class from System.Windows.Forms.Control Class, then you could use BeginInvoke method. Please note that you have to create instance on main thread.
public class JobClass : System.Windows.Forms.Control {
.....
}

How do I Async download multiple files using webclient, but one at a time?

It has been surprisingly hard to find a code example of downloading multiple files using the webclient class asynchronous method, but downloading one at a time.
How can I initiate a async download, but wait until the first is finished until the second, etc. Basically a que.
(note I do not want to use the sync method, because of the increased functionality of the async method.)
The below code starts all my downloads at once. (the progress bar is all over the place)
private void downloadFile(string url)
{
WebClient client = new WebClient();
client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(client_DownloadProgressChanged);
client.DownloadFileCompleted += new AsyncCompletedEventHandler(client_DownloadFileCompleted);
// Starts the download
btnGetDownload.Text = "Downloading...";
btnGetDownload.Enabled = false;
progressBar1.Visible = true;
lblFileName.Text = url;
lblFileName.Visible = true;
string FileName = url.Substring(url.LastIndexOf("/") + 1,
(url.Length - url.LastIndexOf("/") - 1));
client.DownloadFileAsync(new Uri(url), "C:\\Test4\\" + FileName);
}
void client_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
}
void client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
double bytesIn = double.Parse(e.BytesReceived.ToString());
double totalBytes = double.Parse(e.TotalBytesToReceive.ToString());
double percentage = bytesIn / totalBytes * 100;
progressBar1.Value = int.Parse(Math.Truncate(percentage).ToString());
}
What I have done is populate a Queue containing all my urls, then I download each item in the queue. When there are no items left, I can then process all the items. I've mocked some code up below. Keep in mind, the code below is for downloading strings and not files. It shouldn't be too difficult to modify the below code.
private Queue<string> _items = new Queue<string>();
private List<string> _results = new List<string>();
private void PopulateItemsQueue()
{
_items.Enqueue("some_url_here");
_items.Enqueue("perhaps_another_here");
_items.Enqueue("and_a_third_item_as_well");
DownloadItem();
}
private void DownloadItem()
{
if (_items.Any())
{
var nextItem = _items.Dequeue();
var webClient = new WebClient();
webClient.DownloadStringCompleted += OnGetDownloadedStringCompleted;
webClient.DownloadStringAsync(new Uri(nextItem));
return;
}
ProcessResults(_results);
}
private void OnGetDownloadedStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error == null && !string.IsNullOrEmpty(e.Result))
{
// do something with e.Result string.
_results.Add(e.Result);
}
DownloadItem();
}
Edit:
I've modified your code to use a Queue. Not entirely sure how you wanted progress to work. I'm sure if you wanted the progress to cater for all downloads, then you could store the item count in the 'PopulateItemsQueue()' method and use that field in the progress changed method.
private Queue<string> _downloadUrls = new Queue<string>();
private void downloadFile(IEnumerable<string> urls)
{
foreach (var url in urls)
{
_downloadUrls.Enqueue(url);
}
// Starts the download
btnGetDownload.Text = "Downloading...";
btnGetDownload.Enabled = false;
progressBar1.Visible = true;
lblFileName.Visible = true;
DownloadFile();
}
private void DownloadFile()
{
if (_downloadUrls.Any())
{
WebClient client = new WebClient();
client.DownloadProgressChanged += client_DownloadProgressChanged;
client.DownloadFileCompleted += client_DownloadFileCompleted;
var url = _downloadUrls.Dequeue();
string FileName = url.Substring(url.LastIndexOf("/") + 1,
(url.Length - url.LastIndexOf("/") - 1));
client.DownloadFileAsync(new Uri(url), "C:\\Test4\\" + FileName);
lblFileName.Text = url;
return;
}
// End of the download
btnGetDownload.Text = "Download Complete";
}
private void client_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
if (e.Error != null)
{
// handle error scenario
throw e.Error;
}
if (e.Cancelled)
{
// handle cancelled scenario
}
DownloadFile();
}
void client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
double bytesIn = double.Parse(e.BytesReceived.ToString());
double totalBytes = double.Parse(e.TotalBytesToReceive.ToString());
double percentage = bytesIn / totalBytes * 100;
progressBar1.Value = int.Parse(Math.Truncate(percentage).ToString());
}
I am struggling to understand where the issue is. If you only call the async method for the first file, won't it only download that file? Why not use the client_downlaodFileCompleted event to initiate the next file download based on some value passed by AsyncCompletedEvents, or maintain a list of downloaded files as a static variable and have client_DownloadFileCompleted iterate tht list to find the next file to download.
Hope that helps but please post more information if I have miunderstood your question.
I would make a new method, for example named getUrlFromQueue which returns me a new url from the queue (collection or array) and deletes it.. then it calls downloadFile(url) - and in client_DownloadFileCompleted I call getUrlFromQueue again.

Categories