locked
how to download multiple folders in one zip file through Web API RRS feed

  • Question

  • User2097725413 posted

    Hi 

    I am working on Web API, I have a requirement where the user wants to download multiple folders and its contents into one folder and able to download in one zip file.

    for.e.g.

    Multiple folders are 

    Lesson1

    Lesson2

    Lesson3

    .............

    Lesson100

    A user wants to download selected lessons, i.e. Lesson1, Lesson2, Lesson5, Lesson7.

    when the user downloads all the lessons it should download in one zip file, i.e. in a folder "UserLessons" it will add a user-selected folder and its contents and will download the folder "UserLessons" in one zip file.

    The above scenario I want to implement dynamically in Web API.

    Please suggest the approach to implement, if possible share me the sample of code.

    Thanks,

    Uday

    Thursday, August 27, 2020 4:29 AM

All replies

  • User1686398519 posted

    Hi Uday Mahajan,

    According to your needs, I made a detailed example, you can refer to it, I hope it can help you.

    1. This is an MVC project and uses EF, you can modify it according to your own actual situation.
      • You only need to write the relevant code for downloading ZIP format files in WebAPI.
    2. In order to display related files on the view, jQuery jsTree is used in the example.
      • You can install jquery.jstree through NuGet.
      • You need to reference the style.min.css file and the jstree.min.js file.
    3. In order to download files in ZIP format, you can choose SharpZipLib.
      • You need to install SharpZipLib via NuGet.

    Model

        public class TreeViewNode
        {
            public string id { get; set; }
            public string parent { get; set; }
            public string text { get; set; }
        }
        public class File
        {
            public int FileId { get; set; }
            public string FileName { get; set; }
            public string FilePath { get; set; }
            public int LessonId { get; set; }
            [ForeignKey("LessonId")]
            public Lesson Lesson { get; set; }
    
        }
        public class Lesson
        {
            public int LessonId { get; set; }
            public string LessonName { get; set; }
            public ICollection<File> Files { get; set; }
        }

    FileDemoContext

        public class FileDemoContext : DbContext
        {
            public FileDemoContext()
                : base("name=FileDemoContext")
            {
            }
            public virtual DbSet<File> Files { get; set; }
            public virtual DbSet<Lesson> Lessons { get; set; }
        }

    Controller

        public class HomeController : Controller
        {
            public FileDemoContext db = new FileDemoContext();
            public ActionResult Index()
            {
                ViewBag.Json = GetAllFiles();//set  tree view data
                return View();
            }
            //get all files and show them in tree view
            public string GetAllFiles()
            {
                var fileslist = db.Files.Include(f => f.Lesson).ToList().GroupBy(f => f.LessonId);
                List<TreeViewNode> nodes = new List<TreeViewNode>();
                fileslist.ForEach(f => {
                    var tree = new TreeModel();
                    tree.nodes = new List<TreeModel>();
                    string lessonname = null;
                    f.ForEach(g => {
    
                        nodes.Add(new TreeViewNode { id = g.FileId.ToString() + "-" + f.Key.ToString(), parent =f.Key.ToString(), text =g.FileName.ToString() });
                        lessonname = g.Lesson.LessonName;
                    });
    
                    nodes.Add(new TreeViewNode { id = f.Key.ToString(), parent = "#", text = lessonname });
                });
                return JsonConvert.SerializeObject(nodes);
            }
            [HttpPost]
            public ActionResult DownloadFiles(string selectedItems)
            {
                var allFilesPath= GetAllFilesPath(GetAllFilesId(selectedItems));
                return DownloadZipFile(allFilesPath);
            }
            //get selected files path
            public List<string> GetAllFilesPath(List<int> fileidlist)
            {
                var allFilesPath = db.Files.Where(f => fileidlist.Contains(f.FileId)).Select(f=>f.FilePath).ToList();
                return allFilesPath;
            }
            //get selected files id
            public List<int> GetAllFilesId(string selectedItems)
            {
                List<TreeViewNode> items = JsonConvert.DeserializeObject<List<TreeViewNode>>(selectedItems);
                List<int> fileidlist = new List<int>();
                items.ForEach(i => {
                     fileidlist.Add(Int32.Parse(i.id));
                });
                return fileidlist;
            }
            //DownloadZipFile
            public FileResult DownloadZipFile(List<string> allFilesPath)
            {
                var fileName = string.Format("{0}_files.zip", DateTime.Today.Date.ToString("dd-MM-yyyy") + "_1");
                var temppath = Server.MapPath("~/TempFiles/");
                if (!Directory.Exists(temppath))
                {
                    Directory.CreateDirectory(temppath);
                }
                var tempOutPutPath = Path.Combine(temppath, fileName);
                using (ZipOutputStream s = new ZipOutputStream(System.IO.File.Create(tempOutPutPath)))
                {
                    s.SetLevel(9); // 0-9, 9 being the highest compression  
                    byte[] buffer = new byte[4096];
                    var filepathList = new List<string>();
                    for (int i = 0; i < allFilesPath.Count; i++)
                    {
                        string currentfilepath= Path.Combine(Server.MapPath("~/"+ @allFilesPath[i]));
                        ZipEntry entry = new ZipEntry(Path.GetFileName(currentfilepath));
                        entry.DateTime = DateTime.Now;
                        entry.IsUnicodeText = true;
                        s.PutNextEntry(entry);
                        using (FileStream fs = System.IO.File.OpenRead(currentfilepath))
                        {
                            int sourceBytes;
                            do
                            {
                                sourceBytes = fs.Read(buffer, 0, buffer.Length);
                                s.Write(buffer, 0, sourceBytes);
                            } while (sourceBytes > 0);
                        }
                    }
                    s.Finish();
                    s.Flush();
                    s.Close();
                }
                byte[] finalResult = System.IO.File.ReadAllBytes(tempOutPutPath);
                if (System.IO.File.Exists(tempOutPutPath))
                    System.IO.File.Delete(tempOutPutPath);
    
                if (finalResult == null || !finalResult.Any())
                    throw new Exception(String.Format("No Files found with Image"));
                return File(finalResult, "application/zip", fileName);
            }
        }

    Index

    <div id="jstree">
    </div>
    @using (Html.BeginForm("DownloadFiles", "Home", FormMethod.Post))
    {
        <input type="hidden" name="selectedItems" id="selectedItems" />
        <input type="submit" value="Submit" />
    }
    <link href="~/Content/themes/default/style.min.css" rel="stylesheet" />
    @section scripts{
        <script src="~/Scripts/jstree.min.js"></script>
        <script>
            $(function () {
                $('#jstree').on('changed.jstree', function (e, data) {
                    setdata(data);
                }).jstree({
                    "core": {
                        "themes": {
                            "variant": "large"
                        },
                        "data": @Html.Raw(ViewBag.Json)
                        },
                    "checkbox": {
                        "keep_selected_style": false
                    },
                    "plugins": ["wholerow", "checkbox"],
                });
                function setdata(data) {//Set the selected file
                    var i, j;
                    var selectedItems = [];
                    for (i = 0, j = data.selected.length; i < j; i++) {
                        var id = data.selected[i];
                        if (id.indexOf('-') != -1) {
                            selectedItems.push({
                                text: data.instance.get_node(data.selected[i]).text,
                                id: id.split("-")[0],
                                parent: id.split("-")[1]
                            });
                        }
                    }
                    $('#selectedItems').val(JSON.stringify(selectedItems));
                }
            });
        </script>
    }

    Here is the result.

    Best Regards,

    YihuiSun

    Thursday, August 27, 2020 10:55 AM
  • User2097725413 posted

    Hi YihuiSun

    Thanks for the solution.

    I added the same functionality in Web API to download a zip file containing list of different folders.

    For e.g.
    List<string> lstPath = new List<string>();
    lstPath.Add("/ScoContainer/Package0");
    lstPath.Add("/ScoContainer/Package1");
    lstPath.Add("/ScoContainer/Package2");

    And passed the list (lstPath) to function - DownloadZipFile

    Here I want to download Package0, Package1, Package2 including subfolders and files into one zip file.

    At a stage in code - using (FileStream fs = System.IO.File.OpenRead(currentfilepath)), I am getting exception as

    System.UnauthorizedAccessException: 'Access to the path 'F:\Practise projects\WebApi_DownloadZip\WebApi_DownloadZip\ScoContainer\Package0' is denied.'

    For this I set full permission the folder "ScoContainer" but not working.

    Can you tell me, how to give access to folders so that it will create a zip to download?

    Thanks,

    Uday Mahajan

    Monday, August 31, 2020 7:57 PM
  • User1686398519 posted

    Hi Uday Mahajan,

    1. File. Open is used to open an existing file for reading.This means that currentfilepath is the path of the file that needs to be read, not the path of the folder.When you read the path of the folder, the error "System.UnauthorizedAccessException" will appear.
    2. It seems that the file you want to download will still be in the corresponding subdirectory. You need to modify the code like this.
      • ZipEntry entry = new ZipEntry(allFilesPath[i]);
        //For example, the value of allFilesPath[i]): Files/Lesson2/File4.docx
    3. I wrote an example of webapi, and use mvc to call webpai, you can refer to it.

    Controller in MVC

            [HttpPost]
            public async Task<ActionResult> DownloadFiles(string selectedItems)
            {
                var allFilesPath= GetAllFilesPath(GetAllFilesId(selectedItems));
                var client = new HttpClient();
                string url = "https://localhost:44333/api/DownloadFiles/DownloadZipFile"; 
                client.DefaultRequestHeaders.Accept.Clear();
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                HttpContent senddatacontent = new StringContent(JsonConvert.SerializeObject(allFilesPath), Encoding.UTF8, "application/json");
                HttpResponseMessage response = await client.PostAsync(url, senddatacontent);
                if (response.IsSuccessStatusCode)
                {
                    byte[] finalResult = await response.Content.ReadAsByteArrayAsync();
                    string filetype= response.Content.Headers.ContentType.ToString();
                    var fileName = response.Content.Headers.ContentDisposition.FileName;
                    return File(finalResult, filetype, fileName);
                }
                return Content("fail");
            }

    Controller in WebApi

    public class DownloadFilesController : ApiController
    {
    public HttpResponseMessage DownloadZipFile([FromBody] List<string> allFilesPath) { HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK); var fileName = string.Format("{0}_files.zip", DateTime.Today.Date.ToString("dd-MM-yyyy") + "_1"); var temppath = HostingEnvironment.MapPath("~/TempFiles/"); if (!Directory.Exists(temppath)) { Directory.CreateDirectory(temppath); } var tempOutPutPath = Path.Combine(temppath, fileName); using (ZipOutputStream s = new ZipOutputStream(System.IO.File.Create(tempOutPutPath))) { s.SetLevel(9); byte[] buffer = new byte[4096]; var filepathList = new List<string>(); for (int i = 0; i < allFilesPath.Count; i++) { string currentfilepath = Path.Combine(HostingEnvironment.MapPath("~/" + @allFilesPath[i])); ZipEntry entry = new ZipEntry(allFilesPath[i]); entry.DateTime = DateTime.Now; entry.IsUnicodeText = true; s.PutNextEntry(entry); using (FileStream fs = System.IO.File.OpenRead(currentfilepath)) { int sourceBytes; do { sourceBytes = fs.Read(buffer, 0, buffer.Length); s.Write(buffer, 0, sourceBytes); } while (sourceBytes > 0); } } s.Finish(); s.Flush(); s.Close(); } byte[] finalResult =File.ReadAllBytes(tempOutPutPath); if (File.Exists(tempOutPutPath)) File.Delete(tempOutPutPath); if (finalResult == null || !finalResult.Any()) throw new Exception(String.Format("No Files")); response.Content = new ByteArrayContent(finalResult); //Set the Response Content Length. response.Content.Headers.ContentLength = finalResult.ToArray().LongLength; //Set the Content Disposition Header Value and FileName. response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment"); response.Content.Headers.ContentDisposition.FileName = fileName; //Set the File Content Type. response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/zip"); return response; }
    }

    Here is the result.

    Best Regards,

    YihuiSun

    Wednesday, September 2, 2020 5:11 AM