下载使用 MVC5 选择的多个文件

Posted

技术标签:

【中文标题】下载使用 MVC5 选择的多个文件【英文标题】:Download multiple files selected with MVC5 【发布时间】:2017-07-29 01:06:24 【问题描述】:

我正在 MVC5 中开发一个看起来像这样的视图:

我需要选择表中的一条或多条记录,以便能够下载数据库中以前保存的文件。我一直在寻找解决方案并进行了几次测试,但我找不到解决方案。我试图从 javascript 将所选代码发送到控制器并从中下载文档,但我做不到。如果可以的话,可以下载所有文件并生成一个 zip,或者自动将所有文件下载到浏览器的默认文件夹中。 我将非常感谢任何可以帮助我的人...问候!

这是我的 ajax 调用:

  $.ajax(
            type: "POST",
            url: "/GestionGarantias/Garantias/Download",
            data:  pCodigo: mCodigo ,//Here I would send the list of //codes, but now to test, I only call the controller regardless of the value, //because the values in the list set them in the handy controller to test.
            xhrFields: 
                responseType: 'blob'
            ,
            success: function (data)  //Here should I bring a zip with all files supposedly?
                var a = document.createElement('a');
                var url = window.URL.createObjectURL(data);
                a.href = url;
                a.download = 'myfile.zip'; //------------------> I UNDERSTAND THAT THIS IS THE NAME THAT WE SHOULD INDICATE FOR Download ... is it so?
                a.click();
                window.URL.revokeObjectURL(url);
            ,
            error: function (request, status, errorThrown) 
                //alert("Se produjo un error al descargar los adjuntos.");
            
        );

我的控制器:

[HttpPost]
        public ActionResult Download(List<String> codes)
        
            codes = new List<string>();
            codes.Add("1079");
            codes.Add("1078");
            codes.Add("1077");
            MemoryStream ms = null;

            foreach (string codigoGar in codes)
            
                string mimetypeOfFile = "";
                Garantias oGarantia = ControladorGarantias.getGarantia(SessionHelper.GetEntorno(), codigoGar);
                var stream = new MemoryStream(oGarantia.comprobante);
                ms = new MemoryStream();

                byte[] buffer = new byte[256];
                if (stream.Length >= 256)
                    stream.Read(buffer, 0, 256);
                else
                    stream.Read(buffer, 0, (int)stream.Length);

                try
                
                    System.UInt32 mimetype;
                    FindMimeFromData(0, null, buffer, 256, null, 0, out mimetype, 0);
                    System.IntPtr mimeTypePtr = new IntPtr(mimetype);
                    mimetypeOfFile = Marshal.PtrToStringUni(mimeTypePtr);
                    Marshal.FreeCoTaskMem(mimeTypePtr);
                
                catch (Exception e)
                
                    return null;
                

                string fileName = "";

                if (!string.IsNullOrEmpty(mimetypeOfFile))
                
                    switch (mimetypeOfFile.ToLower())
                    
                        case "application/pdf":
                            fileName = "Comprobante_" + oGarantia.nombreService + "_" + oGarantia.nroFactura + ".pdf";
                            break;
                        case "image/x-png":
                            fileName = "Comprobante_" + oGarantia.nombreService + "_" + oGarantia.nroFactura + ".png";
                            break;
                        case "image/pjpeg":
                            fileName = "Comprobante_" + oGarantia.nombreService + "_" + oGarantia.nroFactura + ".jpg";
                            break;
                    
                
                using (var zip = new ZipArchive(ms, ZipArchiveMode.Create, true))
                
                    var entry = zip.CreateEntry(fileName);
                    using (var fileStream = stream)
                    using (var entryStream = entry.Open())
                    
                        fileStream.CopyTo(entryStream);
                    
                
            
            return File(ms.ToArray(), "application/zip");
        

我所做的是在其中创建一个列表,仅用于测试。 我的想法是针对表中选择的每条记录,找到它们的信息(文件)并将它们添加到 zip 中。

我做错了什么?非常感谢!以及对无知的宽恕。

【问题讨论】:

我将文档作为 varbinary 保存在数据库中。 Palaco - 我已经为您的问题添加了答案。您可以使用该功能,但为此您需要流数据 【参考方案1】:

您的问题有两点。首先是获取所有文件的问题,其次是下载文件的问题。在我们解决这些问题之前,让我们退后一步,了解一下请求-响应周期是如何工作的。

使用 HTTP,客户端发出请求,服务器返回响应。重要的是,这里有一个精确的 1-1 相关性。一个请求可以有一个且只有一个响应。这意味着如果您需要提供多个文件作为下载文件,您必须将它们压缩,因为您只能返回一个文件,而不是多个文件。创建一个 zip 文件允许您只返回一个文件,同时仍然满足允许用户一次下载所有文件的要求。

然后是 AJAX 的问题。 JavaScript 中的XMLHttpRequest 对象本质上是一个瘦客户端。它发出 HTTP 请求并接收响应,仅此而已。与通过地址栏导航时 Web 浏览器发出的请求不同,响应不会自动执行,即由您作为开发人员来处理响应并实际执行某些操作。

除此之外,第一部分是创建一个可以返回 zip 文件作为响应的操作。这实际上非常简单:您只需要返回一个FileResult

[HttpPost]
public ActionResult DownloadCodes(List<int> codes)

    // use codes to get the appropriate files, however you do that

    using (var ms = new MemoryStream())
    using (var zip = new ZipArchive(ms, ZipArchiveMode.Create, true))
    
        foreach (var file in files)
        
            // write zip archive entries
        

        return File(ms.ToArray(), "application/zip");
    

对于 zip 存档条目的写入,这取决于文件数据的来源。如果您有文件系统引用,您只需这样做:

zip.CreateEntryFromFile(file, Path.GetFileName(file));

如果您有字节数组,例如从数据库中的 varbinary 列返回的内容:

var entry = zip.CreateEntry(file.Name);
using (var fileStream = new MemoryStream(file.Data))
using (var entryStream = entry.Open())

    fileStream.CopyTo(entryStream);

file.Namefile.Data 由属性组成,分别指您存储文件名的位置和存储文件数据的位置。

现在,您可以简单地对此操作进行普通表单发布,并且由于响应是在网络浏览器(zip 存档)中无法查看的文件类型,浏览器将自动提示下载。此外,由于这在浏览器中不可见,因此选项卡/窗口中的实际视图也不会改变,从而消除了使用 AJAX 来完成保持在同一页面上的正常需要。但是,您可以根据需要使用 AJAX,但您只能在现代浏览器中处理 AJAX 中的文件响应(基本上除了 IE 10 或更低版本)。如果您不需要支持旧版本的 IE,那么您需要的代码类似于:

jQuery

$.ajax(
    url: '/url/to/download/code/action',
    data: data // where `data` is what you're posting, i.e. the list of codes
    xhrFields: 
        responseType: 'blob'
    ,
    success: function (data) 
        // handle file download
    
);

纯 JavaScript

var xhr = new XMLHttpRequest();
xhr.responseType = 'blob';
xhr.onload = function () 
    var data = xhr.response;
    // handle file download

xhr.open('POST', '/url/to/download/codes/action');
xhr.send();

无论你采用哪种路径,处理文件下载的代码是:

var a = document.createElement('a');
var url = window.URL.createObjectURL(data);
a.href = url;
a.download = 'myfile.pdf';
a.click();
window.URL.revokeObjectURL(url);

【讨论】:

感谢您的解释和贡献,我会尝试这个解决方案,我会尝试这样做。 我为每个与我在 foreach 中的文件对应的代码执行的代码。我的问题如下,假设对于每条记录,我都会创建一个zip文件,或者我将每一个都包含在一个内,因为这就是我需要的,创建一个zip,并且在里面,每一个从之前通过的列表中获得的文件。 使用 (var zip = new ZipArchive(ms, ZipArchiveMode.Create, true)) var entry = zip.CreateEntry(fileName);使用 (var fileStream = stream) 使用 (var entryStream = entry.Open()) fileStream.CopyTo(entryStream); 编写条目的代码将放在我添加// write zip archive entries 注释的位置,该注释位于inside for 循环中。 一千个借口克里斯,我缺的太少了,我认为你已经给了我我需要的东西,我设法生成了包含里面文件的 .rar 文件,但是当我尝试解扰文件后打开它们,就好像文件已损坏,我打开了每种文件类型的应用程序,但没有显示内容,有什么想法吗?我怎样才能向你展示我的方法?是我是新来的页面。也许是一封邮件?【参考方案2】:

使用以下类下载文件:

public class FileDownloadResult : ContentResult

    private string fileName;
    private Stream fileData;
    private bool downloadFlag;
    public FileDownloadResult(string fileName, Stream fileData, bool downloadFlag)
    
        this.fileName = fileName;
        this.fileData = fileData;
        this.downloadFlag = downloadFlag;
    

    public override void ExecuteResult(ControllerContext context)
    
        if (string.IsNullOrEmpty(this.fileName))
            throw new Exception("A file name is required.");
        if (this.fileData == null)
            throw new Exception("File data is required.");
        var contentDisposition = "";
        if (!downloadFlag)
            contentDisposition = string.Format("inline; filename=0", this.fileName);
        else
            contentDisposition = string.Format("attachment; filename=0", this.fileName);
        context.HttpContext.Response.AddHeader("Content-Disposition", contentDisposition);


        if (downloadFlag)
            ContentType = "application/force-download";
        else
        
            if (this.fileName.IndexOf(".pdf", fileName.IndexOf(".")) > 0)
                ContentType = "application/pdf";
            else if (this.fileName.IndexOf(".csv", fileName.IndexOf(".")) > 0)
                ContentType = "application/csv";
            else if (this.fileName.IndexOf(".xls", fileName.IndexOf(".")) > 0)
                ContentType = "application/xls";
            else if (this.fileName.IndexOf(".xlsx", fileName.IndexOf(".")) > 0)
                ContentType = "application/xslx";
            else
                ContentType = "application/txt";
        
        context.HttpContext.Response.AddHeader("Content-Type", ContentType);

        byte[] buffer = new byte[1024];


        int count = 0;
        while ((count = fileData.Read(buffer, 0, buffer.Length)) > 0)
        
            context.HttpContext.Response.OutputStream.Write(buffer, 0, count);
            context.HttpContext.Response.Flush();
        
    

然后在你的控制器中,使用如下:

public FileDownloadResult Download(string fileName, Stream fileStream)

     return new FileDownloadResult(fileName, fileStream, true);

希望对你有帮助:)

【讨论】:

非常感谢 Muhammad Qasim,我会尝试您的解决方案,然后发表评论。 @SebaPalacio - 欢迎您。如果这能解决您的问题,请告诉我,并将其标记为答案,如果对您有帮助,请点赞:) 感谢帮助伙伴,我不处理多个文件,但无论如何谢谢你,或者我不知道如何实现该代码,克里斯,更接近我想要的,设法用他们的代码压缩 zip 文件并下载 Zip,但是在解压缩此 zip 后,文件已损坏,我不明白为什么。无论如何,非常感谢你们!【参考方案3】:

我从 @Chris Pratt 那里得到了一些改变,以使其适合我

using (var ms = new MemoryStream())

    using (var zip = new ZipArchive(ms, ZipArchiveMode.Create, true))
    
        foreach (var file in files)
        
           // write zip archive entries
           zip.CreateEntryFromFile(file, Path.GetFileName(file), CompressionLevel.Optimal);
         

     
     return File(ms.ToArray(), "application/zip");

【讨论】:

以上是关于下载使用 MVC5 选择的多个文件的主要内容,如果未能解决你的问题,请参考以下文章

MVC5 Identity + EntityFramework 中的多个上下文

使用ajax一次上传多个文件

C# MVC5 @Html.EnumDropDownListFor 在“回发”上丢失选择

ASP.NET Mvc5+EF7

最新版 INSPINIA IN+ - WebApp Admin Theme v2.7.1,包含asp.net MVC5示例代码,做管理系统最佳的选择。

Asp.net mvc5引用ExtJS6全网首发