问题
I am developing a view in MVC5 that looks like this:
I need to select one or more records of the table and to be able to download the previously saved files, in the database. I have been looking for a solution and done several tests, but I can not find a solution. I was trying to send from javascript the selected codes to the controller and download the documents from the same, but I could not do it. The download could be in case you can, taking all the files and generating a zip, or automatically downloading all to the default folder of the browser. I would greatly appreciate anyone who can help me ... Greetings!
this is my ajax call:
$.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.");
}
});
My controller:
[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");
}
what I do is create a list in it, just for testing. My idea is for each of the records selected in the table, find their information, (file) and add them to the zip.
What am I doing wrong? thank you very much! And forgiveness for ignorance.
回答1:
There's two pieces to your question. First, there's the issue of getting all the files, and second there's the issue of downloading them. Before we tackle these, let's take a step back and realize how the request-response cycle works.
With HTTP a client makes a request and the server returns a response. Importantly, there is an exact 1-1 correlation here. A request can have one and only one response. This means that if you need to provide multiple files as a download, you have to zip them up, as you can only return one file, not multiple. Creating a zip file allows you to return just one file, while still satisfying the requirement of allowing the user to download all the files at once.
Then there's the issue of AJAX. The XMLHttpRequest
object in JavaScript is essentially a thin client. It makes HTTP requests and receives responses, but that's it. Unlike with requests made by the web browser when navigation through the address bar, nothing is done with the response automatically, that is on you, as the developer to handle the response and actually make something happen.
With that out of the way, the first part is creating an action that can return a zip file as a response. This is actually pretty straight-forward: you just need to return a 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");
}
}
For the writing of the zip archive entries, it depends on where the file data is coming from. If you've got filesystem references, you'd just do:
zip.CreateEntryFromFile(file, Path.GetFileName(file));
If you have byte arrays, such as what you'd get back from a varbinary column in a DB:
var entry = zip.CreateEntry(file.Name);
using (var fileStream = new MemoryStream(file.Data))
using (var entryStream = entry.Open())
{
fileStream.CopyTo(entryStream);
}
Where file.Name
and file.Data
are made up properties referring to where you store the filename and where you store the file data, respectively.
Now, you can simply do a normal form post to this action, and since the response is a file type not viewable in a web browser (zip archive), the browser will automatically prompt a download. Additionally, since this is not viewable in a browser, the actual view in your tab/window will not change either, negating the normal need to use AJAX to accomplish staying on the same page. However, you can use AJAX if you want, but you will only be able to handle a file response in AJAX in modern browsers (basically anything but IE 10 or less). If you don't need to support older versions of IE, then the code you need would be something like:
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
}
});
Plain 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();
Whichever path you take, the code for handling the file download is:
var a = document.createElement('a');
var url = window.URL.createObjectURL(data);
a.href = url;
a.download = 'myfile.pdf';
a.click();
window.URL.revokeObjectURL(url);
回答2:
Use the following class to download file:
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();
}
}
}
And then in your controller, use like:
public FileDownloadResult Download(string fileName, Stream fileStream)
{
return new FileDownloadResult(fileName, fileStream, true);
}
Hope it helps :)
来源:https://stackoverflow.com/questions/42671760/download-multiple-files-selected-with-mvc5