走訪資料夾及Web下載檔案(zip)

最近遇到一個情況,是對方要求一個Excel有多個Sheet的時候,要產生多個檔案。
那網路上查過之後發現,Response出來的檔案一次只能有一個。

也就是說Excel有兩個Sheet的時候,就算我產生了兩個pdf,也沒辦法下載兩個pdf。

因此還需要打包成zip檔案下載。

----------------------------------------------------------------------------完整程式部分
// 1.------------- 建立檔案名稱以及走訪目標區域
                    //zip檔案名稱 這段主要是在取zip等等的名稱,因為有檔案傳入,因此直接取xls檔案位置的資料 xlsfilepath 的名字 without 副檔名後再加上.zip 這段單純是在取名字而已
                    string zipSteamName = Path.Combine(Path.GetDirectoryName(xlsfilepath), Path.GetFileNameWithoutExtension(xlsfilepath) + ".zip");

                    //這段的目的在於鎖定走訪的路徑
                    //取得目標路徑 目標路徑為pdf資料夾
                    DirectoryInfo di = new DirectoryInfo(pathPdf);
                    //取得目標pdf資料夾內pdf資料陣列
                    FileInfo[] files = di.GetFiles();

                    //建立zip檔案 若有則update 
                    using (var filestream = new FileStream(zipSteamName, FileMode.OpenOrCreate))
                    {
                        //建立zip檔案內資料 update新增資料
                        using (var archive = new ZipArchive(filestream, ZipArchiveMode.Update, true))
                        {
                            //走訪file資料夾
                            foreach (var fileinfo in files)
                            {
                                //取得新檔案 f = file 內含pdf資料夾內資料走訪
                                FileStream f = new FileStream(fileinfo.FullName, FileMode.Open, FileAccess.Read);
                                //取得filestream 成 byte array
                                byte[] xlsxBytes = BinaryReadToEnd(f);
                                //建立zip內檔案 含名稱及型態
                                var zipArchiveEntry = archive.CreateEntry(fileinfo.Name, CompressionLevel.Fastest);

                                //寫資料進入zip內檔案
                                using (var zipStream = zipArchiveEntry.Open())
                                {
                                    zipStream.Write(xlsxBytes, 0, xlsxBytes.Length);
                                }
                                //歸還空間 這邊不歸還的話沒辦法關掉file 會導致file卡死無法操作
                                f.Dispose();
                                f.Close();
                            }
                            //歸還空間
                            archive.Dispose();
                        };
                        //歸還空間
                        filestream.Dispose();
                        filestream.Close();
                    };

                    // 這邊我本身是偷懶 直接使用xls的資料夾來進行壓縮 反正裡面的資料當初專案設定的時候也是要刪除的 
                    //清除xls檔案 方便產生zip
                    System.IO.File.Delete(xlsfilepath);
                    //切換至xls資料夾走訪
                    di = new DirectoryInfo(pathXls);
                    files = di.GetFiles();
// 2.-------------------------------------------- 網路下載部分
                    //清空Response
                    Response.Clear();
                    //建立標頭及type
                    Response.AddHeader("Content-Disposition", "attachment;filename=" + Path.GetFileName(zipSteamName));
                    //Type為資料orZip檔案
                    Response.ContentType = "application/zip,application/octet-stream";
                    //走訪xls資料夾內資料,若資料夾中有其他資料則可能造成產生的zip檔案產生問題
                    foreach (var fileinfo in files)
                    {
                        //取得新檔案 f = file 內含pdf資料夾內資料走訪
                        FileStream f = new FileStream(fileinfo.FullName, FileMode.Open, FileAccess.Read);
                        //取得filestream 成 byte array
                        byte[] xlsxBytes = BinaryReadToEnd(f);
                        //寫入Response中 用於產生檔案
                        Response.OutputStream.Write(xlsxBytes, 0, xlsxBytes.Length);
                        //歸還空間 同樣上述道理
                        f.Dispose();
                        f.Close();
                        //清除已經產生的zip檔案 歸還空間
                        fileinfo.Delete();
                    }
                    //寫入Response
                    //清空並歸還空間
                    Response.OutputStream.Flush();
                    Response.OutputStream.Close();
                    Response.Flush();

// 3. ----------------------------------- 取得Byte[]部分
public static byte[] BinaryReadToEnd(Stream stream)
        {
            //目前stream位置
            long originalPosition = 0;

            //若stream可支援搜尋的話
            //檢查是否有選定從某個點開始
            if (stream.CanSeek)
            {
                //目前位置 = stream位置
                originalPosition = stream.Position;
                //stream 歸零
                stream.Position = 0;
            }

            try
            {
                //byte array儲存區
                byte[] readBuffer = new byte[4096];
                //目前讀取位置 起始點
                int totalBytesRead = 0;
                //目前讀取位置 結束點
                int bytesRead;

                //一次抓4096個長度 抓到-1的時候停止
                //計算目前讀到哪一位數 從totalBytesRead開始讀 讀 readbuffer - 曾經讀過的長度
                //也就是從 array0開始 讀取 4096個 因此是 0 ~ 4095陣列有值
                //如果讀取長度超過的話則不會讀取值
                //如一個長度為10000的資料 讀取長度為 16000 讀取還是只會回饋 10000 而非 16000 
                while ((bytesRead = stream.Read(readBuffer, totalBytesRead, readBuffer.Length - totalBytesRead)) > 0)
                {
                    //目前讀到的長度
                    totalBytesRead += bytesRead;
                    //假如長度相同的話 代表還沒讀滿
                    if (totalBytesRead == readBuffer.Length)
                    {
                        //取目前讀的下一個值
                        int nextByte = stream.ReadByte();
                        //計算目前要讀的下一個是否有值 沒值是-1
                        if (nextByte != -1)
                        {
                            //建立一個大兩倍的空間 不夠大就繼續兩倍成長空間來讀取程式
                            byte[] temp = new byte[readBuffer.Length * 2];
                            //從readBuffer 0位開始複製整個readbuffer到temp中 
                            Buffer.BlockCopy(readBuffer, 0, temp, 0, readBuffer.Length);
                            //在上一個空間最大值中塞入一位byte
                            //(原理是因為 4096長度的空間是為 0 ~ 4095 因此在4096塞入一位 變成4097長度) 
                            //且因為讀取了值 因此不寫入的話會遺漏
                            Buffer.SetByte(temp, totalBytesRead, (byte)nextByte);
                            //複製temp的空間大小給readbuffer
                            readBuffer = temp;
                            //因為readbuffer有多加一位 因此+1
                            totalBytesRead++;
                        }
                    }
                }
                //輸出readbuffer
                byte[] buffer = readBuffer;
                if (readBuffer.Length != totalBytesRead)
                {
                    //建立一個buffer 他的長度為剛剛測定的全長
                    buffer = new byte[totalBytesRead];
                    //複製readBuffer的資料從頭到尾進入buffer內
                    Buffer.BlockCopy(readBuffer, 0, buffer, 0, totalBytesRead);
                }
                return buffer;
            }
            finally
            {
                //若可以搜尋的話
                if (stream.CanSeek)
                {
                    //資料流位置 = 目前stream位置
                    //更新stream位置回最初位置
                    stream.Position = originalPosition;
                }
            }
        }


參考:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
https://stackoverflow.com/questions/20100445/zip-file-content-type-for-http-request
https://dotblogs.com.tw/gelis/2016/09/04/161341
https://docs.microsoft.com/zh-tw/dotnet/api/system.io.compression.zipfile?view=net-6.0
https://stackoverflow.com/questions/834527/how-do-i-generate-and-send-a-zip-file-to-a-user-in-c-sharp-asp-net

留言

這個網誌中的熱門文章

無法載入檔案或組件 'System.IO.Compression' 或其相依性的其中之一。

MongoDB 入門

javascript 更改屬性及創建標籤