0

I am writing an ASP.NET Core Web API with .NET 5.0 as an exercise.

In MyController.cs there is the method DownloadZip(). Here, it should be possible for the client to download a zip file. By the way, I create a zip file because I did not achieve to transfer multiple pictures. That is the actual goal. Provisionally, the zip file is still stored in the picture folder. Of course, that should not happen either. I simply still have difficulties with web services and transferring zip files via them.

Anyway, in the line return File(fullName, "text/plain"); I get the following error message:

System.InvalidOperationException: No file provider has been configured to process the supplied file.

I found several threads on StackOverflow last Friday about how to transfer a zip file using a memory stream. When I do it this way, the browser shows the individual bytes, but no finished file has been downloaded.

Postings is a list(of post) with

using System;
using System.Collections.Generic;

namespace ImageRepository
{
    public sealed class Posting
    {
        public DateTime CreationTime { get; set; }
        public List<ImageProperties> Imageproperties { get; }

        public Posting(DateTime creationTime, List<ImageProperties> imPr)
        {
            CreationTime = creationTime;
            Imageproperties = imPr;
        }
    }
}

And Imageproperties is the following:

namespace ImageRepository
{
    public sealed class ImageProperties
    {
        public string FullName { get; set; }
        public string _Name { get; set; }
        public byte[] DataBytes { get; set; }

        public ImageProperties(string FullName, string Name, byte[] dataBytes)
        {
            this.FullName = FullName;
            this._Name = Name;
            this.DataBytes = dataBytes;
        }
    }
}

MyController.cs

using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using ImageRepository;
using System.IO.Compression;

namespace WebApp2.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class MyController : ControllerBase
    {
        private readonly IImageTransferRepository imageRepository;
        private readonly System.Globalization.CultureInfo Deu = new System.Globalization.CultureInfo("de-DE");

        public MyController(IImageTransferRepository imageTransferRepository)
        {
            this.imageRepository = imageTransferRepository;
        }
        //––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
        [HttpGet("WhatAreTheNamesOfTheLatestPictures")] // Route will be https://localhost:44355/api/My/WhatAreTheNamesOfTheLatestPictures/
        public ActionResult GetNamesOfNewestPosting()
        {
            List<string> imageNames = this.imageRepository.GetImageNames();

            if (imageNames.Count == 0)
            {
                return NoContent();
            }
            return Ok(imageNames);
        }
        //––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
        [HttpGet("ImagesOfLatestPost")] //route will be https://localhost:44355/api/My/ImagesOfLatestPost
        public ActionResult DownloadZip()
        {
            List<Posting> Postings = this.imageRepository.GetImages();

            if (Postings is null || Postings.Count == 0)
            {
                return NoContent();
            }

            System.DateTime now = System.DateTime.Now;
            string now_as_string = now.ToString("G", Deu).Replace(':', '-');
            string folderPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyPictures);
            string fullName = $"{folderPath}\\{now_as_string}.zip";
            using (ZipArchive newFile = ZipFile.Open(fullName, ZipArchiveMode.Create))
            {
                for (int i = 0; i < Postings[0].Imageproperties.Count; i++)
                {
                    newFile.CreateEntryFromFile(Postings[0].Imageproperties[i].FullName,
                                                Postings[0].Imageproperties[i]._Name);
                }
            }
            return File(fullName, "text/plain");
        }
    }
}

Edit June 20, 2022, 4:16 pm

Based on Bagus Tesa's comment, I wrote the following:

byte[] zip_as_ByteArray = System.IO.File.ReadAllBytes(fullName);

return File(zip_as_ByteArray, "application/zip");

The automatic download takes place, but I still have to rename the file by attaching (a) .zip so that Windows recognises it as a zip file.

enter image description here

Furthermore, there is still the problem that I am still creating the zip file on the hard disk (using (ZipArchive newFile = ZipFile.Open(fullName, ZipArchiveMode.Create))). How can I change this?

Daniel
  • 374
  • 1
  • 12
  • 2
    the easiest would be passing a bytes as the first parameter with `application/zip` or `application/octet-stream` as mime type for zip files. also, make sure if you are passing string as `File` parameter, the path is relative within application directory (in `virtualPath`), [see docs](https://learn.microsoft.com/en-gb/dotnet/api/microsoft.aspnetcore.mvc.controllerbase.file?view=aspnetcore-3.0&WT.mc_id=DT-MVP-4015686#Microsoft_AspNetCore_Mvc_ControllerBase_File_System_String_System_String_). if the path is absolute one, use `PhysicalFile` instead. – Bagus Tesa Jun 20 '22 at 12:29
  • 1
    Browsers will display content of type text/plain directly in the viewer. If you want it to be handled like a binary file, you have change the content type to something that gets downloaded by default (the suggestions by Bagus Tesa (application/zip or application/octet-stream) should all work. IIRC application/zip might be handled directly by a zip program on some machines. – Gus Jun 20 '22 at 14:53
  • 1
    @Daniel you can use [`MemoryStream`](https://learn.microsoft.com/en-us/dotnet/api/system.io.memorystream?view=net-6.0) as parameter to create zip file in memory, see [creating zip archive in memory](https://stackoverflow.com/q/17232414) for reference. however, remember to set the stream's position to 0 after you finish writing the zip archive so it can be used by `File()` method. – Bagus Tesa Jun 21 '22 at 10:06

1 Answers1

0

Thanks to the thread linked by Bagus Tesa, I can now answer my question. I have adapted a few things to my needs, see for-loop, because I have several images.

[HttpGet("ImagesOfLatestPost")] //route will be https://localhost:44355/api/My/ImagesOfLatestPost
        public ActionResult DownloadZip()
        {
            List<Posting> Postings = this.imageRepository.GetImages();

            if (Postings is null || Postings.Count == 0)
            {
                return NoContent();
            }

            byte[] compressedBytes;
            using (var outStream = new System.IO.MemoryStream())
            {
                using (var archive = new ZipArchive(outStream, ZipArchiveMode.Create, true))
                {
                    for (int i = 0; i < Postings[0].Imageproperties.Count; i++)
                    {
                        ZipArchiveEntry fileInArchive = archive.CreateEntry(Postings[0].Imageproperties[i]._Name, CompressionLevel.Optimal);
                        using System.IO.Stream entryStream = fileInArchive.Open();
                        using System.IO.MemoryStream fileToCompressStream = new System.IO.MemoryStream(Postings[0].Imageproperties[i].DataBytes);
                        fileToCompressStream.CopyTo(entryStream);
                    }  
                }
                compressedBytes = outStream.ToArray();
            }

            return File(compressedBytes, "application/zip", $"Export_{System.DateTime.Now:yyyyMMddhhmmss}.zip");
        }
Daniel
  • 374
  • 1
  • 12