如何在MVC4中运行FileResult函数时显示“正在进行中”

5
我有一个下载按钮,调用这个函数:
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));
}

因此,我调用一个生成文件的函数。这个函数需要一些时间,我不希望用户认为应用程序崩溃了。这就是为什么我想在用户等待时显示“进行中”的原因。我该如何实现这个功能呢?

澄清一下:这个问题并不是关于下载进度的,而是关于生成文件的进度。

3个回答

11

我最近遇到了完全相同的问题,我认为我的解决方案(基于这个答案中提到的一个相关问题)就是你在这个问题中寻找的。基本思路是在生成文件时显示进度旋转器(在我的情况下),在文件完成生成时隐藏进度旋转器,然后提供文件以供下载。为此,我需要四个东西:

首先,控制器上的操作需要将文件存储在会话中。

[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"
         }
      };
   }
}

控制器还需要一个新的动作,该动作将提供实际的下载文件

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();
   }
}

接下来,来自显示进度小圆圈的视图的 AJAX 调用会调用 RunReport(需要很长时间的操作),使用返回的 JSON 数组返回下载文件(这是一个快速的操作),然后再次隐藏该小圆圈。

<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>

最后,这个链接将启动所有操作,并生成用于AJAX调用的操作链接。

<a href="javascript: RunReport('@Url.Action("RunReport", "UserReport", new { ReportId = Model.Id })')">Run Report</a>

我希望这能帮助到某个人!


谢谢,这帮助我根据我的需求进行了微调。 - user1063108

3

上面的“XMLHttpRequest”解决方案对我没有用。文件保存对话框从未出现过。

这是我们团队想出来的解决方案:

不要返回FileResult,而是返回JSON。并将文件流放到Session变量中。

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

在控制器中添加另一个方法,将会话(Session)传递给FileStreamResult。
public FileStreamResult DownloadExport()
{
    var file = (Stream) Session[fileName];

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

在视图中,给下载按钮添加一个点击事件。
$("#yourBtnHere").click(function () {
            DownloadFile();
        });

创建 DownLoadFile 函数:
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;
            }
        });
    }

这种方法的负面影响是我们需要两次调用控制器。

3
你需要在客户端控制进度消息。
使用XHR(XMLHttpRequest)文件下载,您可以监视下载并在需要时显示进度条。或者,可以使用更简单的方法,在发出下载请求之前打开它,之后再关闭它以放置一个直接的消息。
这是如何做的:如何从XMLHttpRequest获取进度
针对ASP.NET MVC调整的代码:
在您的控制器方法中,向响应对象添加Content-Length标头。
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));
}

接下来,将您的提交按钮的 onclick 事件连接到下面的 sendreq() 函数。 updateProgress() 函数是 XMLHttpRequest 对象的 onprogress 事件的处理程序:

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 );
    } 
}   

编辑 - 使用消息代替进度条

<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>

请注意 req.open() 的第三个参数表示该调用是异步的。当调用完成后,onreadystate 事件处理程序会隐藏消息。

1
你误解了我的问题。这不是关于文件下载的。我正在调用一个生成文件的函数。这需要一些时间,而在它生成的过程中,我想显示进度条。 - Scuba Kay
1
解决方案是一样的。无论您是构建文件还是仅从服务器检索文件,您都在下载文件。关键是,进度消息需要由客户端进行控制。 - Paul Taylor
好的,我明白你所说的区别了...进度条方法只显示下载进度,而不是构建文件的长时间过程。在这种情况下,你可以像我在上面的编辑中展示的那样简化你的方法。 - Paul Taylor
这样做在执行异步调用时非常有效,我甚至获取了状态更改代码的调用,但当MVC控制器方法完成生成文件时,浏览器上没有打开文件下载对话框,尽管我可以在Firebug网络选项卡中看到该文件已返回。如何使它打开下载对话框? - Oscar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接