I have a download button that calls this function:
public FileResult DownloadExport()
{
string fileName = "example";
// Activate 'In progress'
// Call to a function that takes a while
// Deactivate 'In progress'
return File(fileName, System.Net.Mime.MediaTypeNames.Application.Octet, Path.GetFileName(fileName));
}
So, I call a function that generates a file for me. This function takes a while and I don't want my users to think the application crashed. That's why I want to show 'In progress' while the user is waiting. How can I implement this?
For clarification: This question is not about the progress of the download, but about the progress of the function generating the file.
I recently had this exact same problem and I believe my solution (based on this answer to a related problem) is what you were looking for with this question. The basic idea is to get a progress spinner (in my case) to show while the file is being generated, hiding the progress spinner when the file is finished generating, and then providing the file to download. To achieve this I needed four things:
First, the action on the controller needs to store the file in the Session
[HttpPost]
public ActionResult RunReport(ReportViewModel viewmodel)
{
// Process that generates the object while will become the file here
// ...
using (var stream = new MemoryStream())
{
// Convert the object to a memory stream
report.Generate(stream); // Use your object here
string handle = Guid.NewGuid().ToString();
Session[handle] = stream.ToArray();
return new JsonResult()
{
Data = new
{
FileGuid = handle,
MimeType = "application/pptx",
FileName = "My File.pptx"
}
};
}
}
The controller also needs a new action which will provide the actual download file
public ActionResult Download(string fileGuid, string mimeType, string filename)
{
if(Session[fileGuid] != null)
{
byte[] data = Session[fileGuid] as byte[];
Session.Remove(fileGuid); // Cleanup session data
return File(data, mimeType, filename);
}
else
{
// Log the error if you want
return new EmptyResult();
}
}
Next, an AJAX call from the view which shows the progress spinner, calls RunReport (the action that takes a long time), uses the JSON array it returns to return the download file (which is a quick action), and then hides the spinner again.
<script type="text/javascript">
function RunReport(reportUrl) {
$.ajax({
cache: false,
url: reportUrl,
type: "POST",
success: function(response) {
window.location = "/Report/Download?fileGuid=" + response.FileGuid +
"&mimeType=" + response.MimeType + "&filename=" + response.FileName;
$("#progress-spinner").hide();
}
});
$("#progress-spinner").show();
}
</script>
Finally, the link that launches it all and generates the action link for use in the AJAX call
<a href="javascript: RunReport('@Url.Action("RunReport", "UserReport", new { ReportId = Model.Id })')">Run Report</a>
I hope this helps someone!
You will need to control the progress message client-side.
Using XHR (XMLHttpRequest) file download you can monitor the download and show a progress bar if you want to. Or to use the simpler approach of putting up a straightforward message, switch it on before you make the download request, and switch it off again afterwards.
Here's how: How to get progress from XMLHttpRequest.
Code adapted for ASP.NET MVC:
In your controller method, add a Content-Length header to the Response object:
public FileResult DownloadExport()
{
string fileName = "example";
// Add Content-Length header
FileInfo i = new FileInfo(fileName);
Response.AddHeader("Content-Length", i.Length.ToString());
return File(fileName, System.Net.Mime.MediaTypeNames.Application.Octet, Path.GetFileName(fileName));
}
Then, wire the onclick
event of your submit button to the sendreq()
function, below. The updateProgress()
function is a handler for the XMLHttpRequest object's onprogress
event:
function sendreq(evt)
{
var req = new XMLHttpRequest();
$('#progressbar').progressbar();
req.onprogress=updateProgress;
req.open('GET', 'Controller/DownloadExport', true);
req.onreadystatechange = function (aEvt) {
if (req.readyState == 4)
{
}
};
req.send();
}
function updateProgress(evt)
{
if (evt.lengthComputable)
{ //evt.loaded the bytes browser receive
//evt.total the total bytes seted by the header
//
var percentComplete = (evt.loaded / evt.total)*100;
$('#progressbar').progressbar( "option", "value", percentComplete );
}
}
EDIT - use a message instead of progress bar
<div>
<!-- Your other markup -->
<div id="progressMsg" style="display:none">Please wait...</div>
<button onclick="sendreq()">Submit</button>
</div>
<script>
function sendreq(evt)
{
var req = new XMLHttpRequest();
req.open('GET', 'Controller/DownloadExport', true);
req.onreadystatechange = function (aEvt) {
if (req.readyState == 4) {
//4 = complete
$('#progressMsg').hide();
}
};
$('#progressMsg').show();
req.send();
}
</script>
Note the third argument to the req.open() states that the call is async. The onreadystate event handler hides the message once the call is complete.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With