Building Dynamic Zip Bundle in ASP.NET Core: Part 2 - Dynamic Zip

In this second and final part, we take our previous dynamic PDF, add two other files, and pack them into a dynamic zip file.

Written by Jonathan "JD" Danylko • Last Updated: • Develop •
Package delivered sitting on a porch

In the last post, we had a request to build dynamic content. This dynamic content would create documents from your application and build PDF files.

We were able to create the PDF on-the-fly and deliver the document back to the user thanks to IronPDF.

In today's post, we take this approach a step further. We want to take all of our dynamic content which consists of any existing or virtual files and bundle everything into a dynamic zip. This virtual file will be delivered back to the user.

This exercise proves two things:

  1. You can dynamically bundle any number of files (within reason) and present the bundle back to the user.
  2. A file WILL NOT be saved on the server (unless you intentionally want to save it) since it's being dynamically generated.

I understand storage is dirt-cheap, but every little bit helps for maintenance purposes. ;-)

Flashback to Friday

On Friday, we delivered the PDF using the following two methods.

private ArchiveFile GetTableOfContents()
{
    // Test parameter.
    var projectId = 5;

    var uri = new Uri("http://localhost:42006/Template/ArchiveHeader/"+projectId);
    var pdf = CreatePdf(uri);
    return new ArchiveFile     {         Name="Header",         Extension = "pdf",         FileBytes = pdf.ToArray()     }; }
private
 MemoryStream CreatePdf(Uri uri) {     var urlToPdf = new HtmlToPdf();
    // if you want to create a Razor page with custom data,     // make the call here to call a local HTML page w/ your data     // in it.
    // var pdf = urlToPdf.RenderUrlAsPdf(uri);
    var pdf = urlToPdf.RenderHtmlAsPdf(@"         <html>         <head>         </head>         <body>             <h1>Table of Contents</h1>             <p>This is where you place you custom content!</p>         </body>         </html>     ");
    return pdf.Stream; }

With our new bundled package, we require a bit more.

We need a way to grab the contents from a list of pre-generated content whether it lives in a directory or you have images uploaded into a database.

Getting The List

Let's add some existing images to the package.

We'll add:

  • HeaviestObjectsInTheUniverse.jpg
  • SwedishChef-InterestingMan.jpg

These images are in the code, but as I mentioned before, you can add any type of file you want to your list so long as you can generate the bytes representing the file.

We'll add a new method called GetPackage into our IDocumentService (in bold).

public interface IDocumentService
{
    ArchiveFile GetDocument();

    byte[] GetPackage(string path); }

This new method only requires a path to the files, but how do we get the Server.MapPath in .NET Core?

We dependency inject the IHostingEnvironment into the IndexModel's constructor.

public class IndexModel : PageModel
{
    private IDocumentService _service;
    private readonly IHostingEnvironment _env;

    public IndexModel(IDocumentService service, IHostingEnvironment env)     {         _service = service;         _env = env;     }
    public void OnGet() { }
    public FileContentResult OnPostDownloadPdf()     {         var document = _service.GetDocument();
        var file = document.Name + "."+document.Extension;
        return File(document.FileBytes, "application/pdf", file);     }
    public FileContentResult OnPostDownloadZip()     {         var path = Path.Combine(_env.WebRootPath, @"images\");         var package = _service.GetPackage(path);
        var file = DateTime.UtcNow.ToString("yy-MMM-dd")+"-Package.zip";
        return File(package, "application/zip", file);     }
}

Once we pass the path into our GetPackage method (in bold), everything is smooth sailing from here.

Building the List Of Files

In our GetPackage method, we have a predefined list of images we want in our zip archive.

public byte[] GetPackage(string path)
{
    // Start off with our "Table of Contents"
    var fileList = new List<ArchiveFile>
    {
        GetTableOfContents()
    };

    fileList.Add(GetFile($"{path}SwedishChef-InterestingMan.jpg"));     fileList.Add(GetFile($"{path}HeaviestObjectsInTheUniverse.jpg"));
    return GeneratePackage(fileList); }

This code should look familiar with the dynamic PDF included from our previous example.

Along with the PDF, we'll add our images through the GetFile() method. 

private ArchiveFile GetFile(string filePath)
{
    return new ArchiveFile
    {
        Name = Path.GetFileNameWithoutExtension(filePath),
        Extension = Path.GetExtension(filePath).Replace(".",""),
        FileBytes = File.ReadAllBytes(filePath)
    };
}

You may have noticed that I'm using the File.ReadAllBytes() method.

A Word Of Warning! While I'm merely using image and PDF files as a demonstration, using File.ReadAllBytes() is probably not the best way to load files larger than ~500-600MB into memory. It would make more sense to save the archive file to a temporary directory using streams and then send it on to the user through the browser. Be mindful of your server's memory constraints.

Generating the Zip Archive

Once we have our list of archive files, we can now create our Zip Archive.

By default, .NET has a ZipArchive class to manage your zip files, but it also includes a GZipStream class.

This makes our life so much easier...and you thought I was going to recommend a third-party zip library, weren't you?

private byte[] GeneratePackage(List<ArchiveFile> fileList)
{
    byte[] result;

    using (var packageStream = new MemoryStream())     {         using (var archive = new ZipArchive(packageStream, ZipArchiveMode.Create, true))         {             foreach (var virtualFile in fileList)             {                 //Create a zip entry for each attachment                 var zipFile = archive.CreateEntry(virtualFile.Name + "." + virtualFile.Extension);                 using (var sourceFileStream = new MemoryStream(virtualFile.FileBytes))                 using (var zipEntryStream = zipFile.Open())                 {                     sourceFileStream.CopyTo(zipEntryStream);                 }             }         }         result = packageStream.ToArray();     }
    return result; }

Our package comes back to us as an array of bytes to our PageModel.

Once we have our bytes, we can easily name the file and use the FileContentResult to return our zip file.

public FileContentResult OnPostDownloadZip()
{
    var path = Path.Combine(_env.WebRootPath, @"images\");
    var package = _service.GetPackage(path);

    var file = DateTime.UtcNow.ToString("yy-MMM-dd")+"-Package.zip";
    return File(package, "application/zip", file); }

This presents the download as a zip file (named "2019-Apr-21-Package.zip") to our user with three files in it: header.pdf, HeaviestObjectsInTheUniverse.jpg, and SwedishChef-InterestingMan.jpg.

Conclusion

In today's post, we took multiple files (some dynamic and existing) and created a ZipArchive for the user. This type of packaging of data could be extremely helpful when:

  • User's requesting personal data from a commercial website (since it technically IS their data)
  • Archiving entire projects (like One-Sheet information)
  • SQL Data reports (as PDF) w/ the actual data (.CSV files) for analysis

The concept is to automate as much as you can by putting systems in place making it easier for users to self-service their own data.

Once they know how to ask for their own data, packaging their data and sending it to them becomes extremely easy.

Do you archive data in your systems? Do you use backup media? Or do you copy from one folder to another? Post your comments below and let's discuss.

Did you like this content? Show your support by buying me a coffee.

Buy me a coffee  Buy me a coffee
Picture of Jonathan "JD" Danylko

Jonathan Danylko is a web architect and entrepreneur who's been programming for over 25 years. He's developed websites for small, medium, and Fortune 500 companies since 1996.

He currently works at Insight Enterprises as an Principal Software Engineer Architect.

When asked what he likes to do in his spare time, he replies, "I like to write and I like to code. I also like to write about code."

comments powered by Disqus