문제

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.

the "XMLHttpRequest" solution above did not work for me. The file save dialog box never showed up for me.

Here is the solution our team came up with:

instead of returning the FileResult, return JSON. And put the file stream to the Session variable.

public ActionResult SetupExport()
{
    var fileName = //your file here;
    Session[fileName] = baseOutputStream;
    return Json(new { success = true, fileName }, JsonRequestBehavior.AllowGet);
}

Make another method in the controller to pass the Session to the FileStreamResult

public FileStreamResult DownloadExport()
{
    var file = (Stream) Session[fileName];

    return new FileStreamResult(file, "your file type goes here");
}

In the View, add a click event to the download button

$("#yourBtnHere").click(function () {
            DownloadFile();
        });

Create the DownLoadFile function:

function DownloadFile()
    {
        $('#progressMsg').show();
        $.ajax({
            dataType: 'json',
            type: 'POST',
            url: "/YourController/SetupExport",
            success: function (result) {
                if (result.success) {
                    $('#progressMsg').hide();
                    window.open( "/YourController/DownloadExport" + "?fileName=" + result.fileName);
                }
            },
            error: function () {
                //show your error here;
            }
        });
    }

The Negative part of this approach is that we have to call the controller twice.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top