zip multiple pdfs from url link, how to - c#

I have a project that requires pdf files to be zipped up from an URL link and then downloaded and clickable by the end users browser. So far, I was able to zip one pdf file which isn't nearly what I'm looking to for.
I'm not sure how to proceed from here. Below is the code. Any help would be very much appreciated.
ASP.NET Core
[HttpGet("zipFiles")]
public IActionResult ZipPDFFiles()
{
var fileNames = _repo.GetFileNames();
foreach (var filesName in fileNames)
{
var urlLink = "https://example.com/folder/" + $"{filesName.PdfFileName}";
var net = new System.Net.WebClient();
var data = net.DownloadData(urlLink);
var file = $"{filesName.PdfFileName}";
var contentType = "application/zip";
string zippedFolderName = "Archive.zip";
using (MemoryStream ms = new MemoryStream())
{
using (var archive = new ZipArchive(ms, ZipArchiveMode.Create, true))
{
var zipArchiveEntry = archive.CreateEntry($"{file}", System.IO.Compression.CompressionLevel.Fastest);
using (var zipStream = zipArchiveEntry.Open()) zipStream.Write(data, 0, data.Length);
}
return File(ms.ToArray(), contentType, $"{zippedFolderName}");
}
}
return NotFound();
}

Some improvements:
used HttpClient
files uploaded simultaneously
var fileUrls = new[]
{
new Uri("https://learn.microsoft.com/en-us/dotnet/standard/microservices-architecture/implement-resilient-applications/media/image3.5.png"),
new Uri("https://learn.microsoft.com/en-us/dotnet/standard/microservices-architecture/implement-resilient-applications/media/image4.png"),
new Uri("https://learn.microsoft.com/en-us/dotnet/standard/microservices-architecture/implement-resilient-applications/media/image6.png")
};
var downloadResults = fileUrls
.Select(uri => (uri: uri, response: HttpClientFactory.Create().SendAsync(new HttpRequestMessage(HttpMethod.Get, uri))))
.ToArray();
await Task.WhenAll(downloadResults.Select(v => v.response));
using (var ms = new MemoryStream())
{
using (var archive = new ZipArchive(ms, ZipArchiveMode.Create, true))
{
foreach (var download in downloadResults)
{
var entry = archive.CreateEntry(download.uri.Segments.Last(), CompressionLevel.Fastest);
using (var zipStream = entry.Open())
{
var data = await download.response.Result.Content.ReadAsByteArrayAsync();
zipStream.Write(data, 0, data.Length);
}
}
}
return File(ms.ToArray(), contentType, $"{zippedFolderName}");
}

Related

Download multiple files .NET CORE Web API as ArchiveZip return root directory

I make a private class to get the bytes of the file and then return it into a zip file. Basically, I just want to pick specific files inside my folder and then zip it then download it. Here's my class:
private FileResult DownloadMultipleFiles(List<byte[]> byteArrayList)
{
var zipName = $"archive-EvidenceFiles-{DateTime.Now.ToString("yyyy_MM_dd-HH_mm_ss")}.zip";
using (MemoryStream ms = new MemoryStream())
{
using (var archive = new ZipArchive(ms, ZipArchiveMode.Create, true))
{
foreach (var file in byteArrayList)
{
string fPath = Encoding.ASCII.GetString(file);
var entry = archive.CreateEntry(fPath, CompressionLevel.Fastest);
using (var zipStream = entry.Open())
{
zipStream.Write(file, 0, file.Length);
}
}
}
return File(ms.ToArray(), "application/zip", zipName);
}
}
And then, here's my controller:
[HttpGet("GetBundleFiles/{rhaId}")]
public async Task<IActionResult> GetBundleFiles(string rhaId)
{
List<byte[]> filesPath = new List<byte[]>();
var results = await _rhaFileEvidence.GetByRhaID(rhaId);
var files = results.ToList();
if (files.Count == 0)
return Ok(new { status = "null", message = "Empty data" });
files.ForEach(file =>
{
var fPath = file.FilePath;
byte[] bytes = Encoding.ASCII.GetBytes(fPath);
filesPath.Add(bytes);
});
return DownloadMultipleFiles(filesPath);
}
The controller works well, I can download the zip but when I open it, I can't get the files instead I get the root directory of the project I saved, like D:. I think I make mistake when making the memory stream or something, is there any suggestion how can I fix this? (Paste some solution code in the answer please)
you are getting the directory root because when you use archive.CreateEntry you are passing the file full path in parameter, you should be using only the file name
var entry = archive.CreateEntry(System.IO.Path.GetFileName(fPath), CompressionLevel.Fastest);
a second issue is that you actually saving the file path to your files not the content of the original file. you can update your DownloadMultipleFiles like this
private FileResult DownloadMultipleFiles(List<byte[]> byteArrayList)
{
var zipName = $"archive-EvidenceFiles-{DateTime.Now.ToString("yyyy_MM_dd-HH_mm_ss")}.zip";
using (MemoryStream ms = new MemoryStream())
{
using (var archive = new ZipArchive(ms, ZipArchiveMode.Create, true))
{
foreach (var file in byteArrayList)
{
string fPath = Encoding.ASCII.GetString(file);
var entry = archive.CreateEntry(System.IO.Path.GetFileName(fPath), CompressionLevel.Fastest);
using (var zipStream = entry.Open())
{
var bytes = System.IO.File.ReadAllBytes(fPath);
zipStream.Write(bytes, 0, bytes.Length);
}
}
}
return File(ms.ToArray(), "application/zip", zipName);
}
}

Generated Zip file is invalid after downloading from S3

I am creating a zip file that, appears, valid but is always invalid after I have put it to a Amazon S3 bucket. I am using System.IO.Compression for the task and AmazonS3Client for uploading:
private byte[] GenerateZipFile(string tenant)
{
byte[] zipData;
var results = QueryAggregateTable(tenant);
using (var memoryStream = new MemoryStream())
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
{
foreach (var item in results)
{
var archiveEntry = archive.CreateEntry($"{item.RowKey:D3}.json", CompressionLevel.Fastest);
using (var entryStream = archiveEntry.Open())
{
var entryBytes = Encoding.UTF8.GetBytes(item.Data);
entryStream.Write(entryBytes, 0, item.Data.Length);
}
}
zipData = memoryStream.ToArray();
}
return zipData;
}
and
private async Task UploadToAmazon(byte[] zipData, string tenant)
{
var bucketName = _config["egestionBucketName"];
var configCreds = _config["egestionAwsCredentials"].Split(":");
var awsCreds = new BasicAWSCredentials(configCreds[0], configCreds[1]);
var awsRegion = Amazon.RegionEndpoint.GetBySystemName(_config["egestionRegionEndpointSystemName"]);
var s3Client = new AmazonS3Client(awsCreds, awsRegion);
using (var stream = new MemoryStream(zipData))
{
var putRequest = new PutObjectRequest
{
BucketName = bucketName,
Key = $"{tenant}-{DateTime.UtcNow.ToString("s")}.zip",
InputStream = stream,
CannedACL = S3CannedACL.BucketOwnerFullControl
};
await s3Client.PutObjectAsync(putRequest);
}
}
The byte array looks good after returning from generation and the upload method does, in fact, load a file with the correct name to the bucket. When I attempt to download the file to check it I cannot open it with a message stating it is invalid.
I have had some problems with async/await and suspect it may be something related but there is no non async option for PutObject that I can find. Any help appreciated.
This is not an async-await issue.
The bytes from the memory stream are being collected before the archive has had a chance to write all the data to the stream. The uploaded archive is incomplete and therefore invalid when downloaded.
Move
zipData = memoryStream.ToArray();
to outside of the archive using block so that any buffered data is flushed to the backing stream when the archive is disposed.
//...
using (var memoryStream = new MemoryStream()) {
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true)) {
foreach (var item in results) {
var archiveEntry = archive.CreateEntry($"{item.RowKey:D3}.json", CompressionLevel.Fastest);
using (var entryStream = archiveEntry.Open()) {
var entryBytes = Encoding.UTF8.GetBytes(item.Data);
entryStream.Write(entryBytes, 0, entryBytes.Length);
}
}
}//Archive disposed and pushed any remaining buffered data to the stream.
zipData = memoryStream.ToArray();
}
//...

How to convert GZipStream to HttpContent?

I have written a Web API code to return a zip file. But I am not able to convert the GZipStream content to HttpContent. I get the following error:
cannot implicitly convert type 'system.io.compression.GZipStream' to
'System.Net.Http.HttpContent'
Where did I go wrong?
My WebApi Code:
var content =
new GZipStream(memStream, CompressionMode.Compress);
HttpResponseMessage httpResponseMessage = new HttpResponseMessage();
httpResponseMessage.Content = content;
httpResponseMessage.Content.Headers.Add("x-filename", document.FileName);
httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/zip");
httpResponseMessage.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
httpResponseMessage.Content.Headers.ContentDisposition.FileName = "xyz.zip";
httpResponseMessage.StatusCode = HttpStatusCode.OK;
I got a solution using Zip archive and I have created a static class to push the memory stream into a zip archive as below,
var pushStreamContent = ZipStreamContent.Create("MultipleDocument.zip", memStList);
ZipStreamContent class,
public static class ZipStreamContent
{
public static PushStreamContent Create(string fileName, List<MemoryStream> msList)
{
var content = new PushStreamContent((outputStream, httpContent, transportContext) =>
{
using (var zip = new ZipArchive(outputStream, ZipArchiveMode.Create, leaveOpen: false))
{
msList[0].Position = 0;
var createenter = zip.CreateEntry("xyz.jpg", CompressionLevel.Optimal);
using (var s = createenter.Open())
{
msList[0].CopyTo(s);
}
}
});
content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
content.Headers.ContentDisposition.FileName = fileName;
return content;
}
}
I have simplified the above given GipStreamContent static class like following, It's working well, so I hope It will help all others.
CloudBlockBlob blob = null;
//azure storage connection
var container = GetBlobClient(tenantInfo);
//directory reference
var directory = container.GetDirectoryReference(
string.Format(DirectoryNameConfigValue, tenantInfo.TenantId.ToString(), documentList[0].ProjectId));
var pushStreamContent = new PushStreamContent(async (outputStream, httpContent, transportContext) =>
{
//zip the multiple files
using (var zipEntry = new ZipArchive(outputStream, ZipArchiveMode.Create, leaveOpen: false))
{
for (int docId = 0; docId < documentList.Count; docId++)
{
blob = directory.GetBlockBlobReference(DocumentNameConfigValue + documentList[docId].DocumentId);
if (!blob.Exists()) continue;
MemoryStream memStream = new MemoryStream();
await blob.DownloadToStreamAsync(memStream);
memStream.Position = 0;
var createEntry = zipEntry.CreateEntry(documentList[docId].FileName, CompressionLevel.Fastest);
using (var stream = createEntry.Open())
{
memStream.CopyTo(stream);
}
}
}
});

SharePoint - download folder as zip file

I need an API (C#) to download folder as one zip file in SharePoint. This feature is enabled in SharePoint website after selecting one or a few folders but I can't find how to do this programmatically.
I don't see something in class Microsoft.SharePoint.Client.Folder. Perhaps to use somehow Microsoft.SharePoint.Client.DocumentSet. Can't find an answer. I can do this file by file with using File.OpenBinaryDirect but maybe already exists ready function. Thanks.
Sample code for your reference.
static void Main(string[] args)
{
string _SiteUrl = "https://domain.sharepoint.com/sites/tst";
using (var clientContext = new ClientContext(_SiteUrl))
{
Console.ForegroundColor = ConsoleColor.Green;
string password = "pw";
SecureString sec_pass = new SecureString();
Array.ForEach(password.ToArray(), sec_pass.AppendChar);
sec_pass.MakeReadOnly();
clientContext.Credentials = new SharePointOnlineCredentials("lee#domain.onmicrosoft.com", sec_pass);
Web web = clientContext.Web;
Folder folder = web.GetFolderByServerRelativeUrl("/sites/TST/MyDoc4/Folder");
var files = folder.Files;
clientContext.Load(files);
clientContext.ExecuteQuery();
Console.WriteLine();
//Regex regex = new Regex(_SiteUrl, RegexOptions.IgnoreCase);
var list = web.Lists.GetByTitle("MyDoc4");
var libRootFolder = list.RootFolder;
var subFolder = libRootFolder.Folders.GetByUrl("Folder");
clientContext.Load(libRootFolder);
clientContext.Load(subFolder);
clientContext.ExecuteQuery();
Console.WriteLine(libRootFolder.ItemCount);
Console.WriteLine(subFolder.ItemCount);
using (System.IO.MemoryStream mStream = new System.IO.MemoryStream())
{
using (var archive = new ZipArchive(mStream, ZipArchiveMode.Create, true))
{
foreach (var file in files)
{
clientContext.Load(file);
Console.WriteLine(file.Name);
ClientResult<Stream> stream = file.OpenBinaryStream();
clientContext.ExecuteQuery();
var zipArchiveEntry = archive.CreateEntry(file.Name);
using (Stream zipEntryStream = zipArchiveEntry.Open())
{
if (stream != null)
{
stream.Value.CopyTo(zipEntryStream);
}
}
}
}
using (var fileStream = new FileStream(#"C:\Lee\FileDownload\test.zip", FileMode.Create))
{
mStream.Seek(0, SeekOrigin.Begin);
mStream.CopyTo(fileStream);
}
}
Console.WriteLine("done");
Console.ReadKey();
}
}

Invalid zip file after creating it with System.IO.Compression

I'm trying to create a zip file that contains one or more files.
I'm using the .NET framework 4.5 and more specifically System.IO.Compression namespace.
The objective is to allow a user to download a zip file through a ASP.NET MVC application.
The zip file is being generated and sent to the client but when I try to open it by doing double click on it I get the following error:
Windows cannot open the folder.
The compressed (zipped) folder ... is invalid.
Here's my code:
[HttpGet]
public FileResult Download()
{
var fileOne = CreateFile(VegieType.POTATO);
var fileTwo = CreateFile(VegieType.ONION);
var fileThree = CreateFile(VegieType.CARROT);
IEnumerable<FileContentResult> files = new List<FileContentResult>() { fileOne, fileTwo, fileThree };
var zip = CreateZip(files);
return zip;
}
private FileContentResult CreateFile(VegieType vType)
{
string fileName = string.Empty;
string fileContent = string.Empty;
switch (vType)
{
case VegieType.BATATA:
fileName = "batata.csv";
fileContent = "THIS,IS,A,POTATO";
break;
case VegieType.CEBOLA:
fileName = "cebola.csv";
fileContent = "THIS,IS,AN,ONION";
break;
case VegieType.CENOURA:
fileName = "cenoura.csv";
fileContent = "THIS,IS,A,CARROT";
break;
default:
break;
}
var fileBytes = Encoding.GetEncoding(1252).GetBytes(fileContent);
return File(fileBytes, MediaTypeNames.Application.Octet, fileName);
}
private FileResult CreateZip(IEnumerable<FileContentResult> files)
{
byte[] retVal = null;
if (files.Any())
{
using (MemoryStream zipStream = new MemoryStream())
{
using (ZipArchive archive = new ZipArchive(zipStream, ZipArchiveMode.Create, false))
{
foreach (var f in files)
{
var entry = archive.CreateEntry(f.FileDownloadName, CompressionLevel.Fastest);
using (var entryStream = entry.Open())
{
entryStream.Write(f.FileContents, 0, f.FileContents.Length);
entryStream.Close();
}
}
zipStream.Position = 0;
retVal = zipStream.ToArray();
}
}
}
return File(retVal, MediaTypeNames.Application.Zip, "horta.zip");
}
Can anyone please shed some light on why is windows saying that my zip file is invalid when I double click on it.
A final consideration, I can open it using 7-Zip.
You need to get the MemoryStream buffer via ToArray after the ZipArchive object gets disposed. Otherwise you end up with corrupted archive.
And please note that I have changed the parameters of ZipArchive constructor to keep it open when adding entries.
There is some checksumming going on when the ZipArchive is beeing disposed so if you read the MemoryStream before, it is still incomplete.
private FileResult CreateZip(IEnumerable<FileContentResult> files)
{
byte[] retVal = null;
if (files.Any())
{
using (MemoryStream zipStream = new MemoryStream())
{
using (ZipArchive archive = new ZipArchive(zipStream, ZipArchiveMode.Create, true))
{
foreach (var f in files)
{
var entry = archive.CreateEntry(f.FileDownloadName, CompressionLevel.Fastest);
using (BinaryWriter writer = new BinaryWriter(entry.Open()))
{
writer.Write(f.FileContents, 0, f.FileContents.Length);
writer.Close();
}
}
zipStream.Position = 0;
}
retVal = zipStream.ToArray();
}
}
return File(retVal, MediaTypeNames.Application.Zip, "horta.zip");
}
Just return the stream...
private ActionResult CreateZip(IEnumerable files)
{
if (files.Any())
{
MemoryStream zipStream = new MemoryStream();
using (ZipArchive archive = new ZipArchive(zipStream, ZipArchiveMode.Create, false))
{
foreach (var f in files)
{
var entry = archive.CreateEntry(f.FileDownloadName, CompressionLevel.Fastest);
using (var entryStream = entry.Open())
{
entryStream.Write(f.FileContents, 0, f.FileContents.Length);
entryStream.Close();
}
}
}
zipStream.Position = 0;
return File(zipStream, MediaTypeNames.Application.Zip, "horta.zip");
}
return new EmptyResult();
}
Try changing
using (ZipArchive archive = new ZipArchive(zipStream, ZipArchiveMode.Create, false))
to
using (ZipArchive archive = new ZipArchive(zipStream, ZipArchiveMode.Create, true))
In this usage, the archive is forced to write to the stream when it is closed. However, if the leaveOpen argument of the constructor is set to false, it will close the underlying stream too.
When I added a wrong name for the entry as in the example
var fileToZip = "/abc.txt";
ZipArchiveEntry zipFileEntry = zipArchive.CreateEntry(fileToZip);
I got the same error. After correcting the file name, it is ok now.
I got the "The compressed (zipped) folder ... is invalid." error because my entries were named with a leading "/" in front of them. Some zip extractors had no problem with this but the Windows one does. I resolved it by removing the slash from the entry name (from "/file.txt" to "file.txt").

Categories