Архивируем файлы в ASP.NET MVC
В этой статье мы поговорим о том, каким образом быстро создать архив из файлов в целях их дальнейшей загрузки в ASP.NET MVC. Создание такого архива из загружаемых файлов позволит вам снизить объём загружаемых данных, что, в свою очередь, уменьшит и трафик.
Для архивации файлов давайте добавим в наш проект библиотеку SharpZipLib (сделаем это через NuGet).
Допустим, в нашем проекте уже есть каталог Files, где хранятся файлы для загрузки. Давайте определим модель, которая станет описывать файл, выбираемый для загрузки:
public class InputModel { public string Name { get; set; // имя файла public bool? Selected { get; set; } // выбран ли файл для загрузки }
Следующий этап — определить в контроллере метод Index, который станет передавать в представление список файлов из нашей папки Files:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.IO; using ZipApp.Models; using ICSharpCode.SharpZipLib.Zip; using ICSharpCode.SharpZipLib.Core; namespace ZipApp.Controllers { public class HomeController : Controller { public ActionResult Index() { string path = Server.MapPath("~/Files/"); List<string> files = new List<string>(); DirectoryInfo dir = new DirectoryInfo(path); files.AddRange(dir.GetFiles().Select(f => f.Name)); return View(files); } } }
Таким образом, представление станет выглядеть так:
@model List<string> @{ ViewBag.Title = "Home Page"; int i = 0; } <h2>Выбрать файлы для загрузки</h2> <form method="post"> @foreach(var filename in Model) { <p> <input type="checkbox" name="files[@i].Selected" value="true" /> <input type="hidden" name="files[@i].Selected" value="false" /> <input type="hidden" name="files[@i].Name" value="@filename" /> @filename </p> i++; } <input type="submit" value="Загрузить" /> </form>
При этом визуально мы увидим примерно следующее:
Что же, давайте определим post-версию метода Index, что позволит нам принимать данные от клиента:
[HttpPost] public ActionResult Index(List<InputModel> files) { // получаем выбранные файлы List<string> filenames = files.Where(m => m.Selected == true).Select(f => f.Name).ToList(); // создаем папку Temp для хранения архива if (!Directory.Exists(Server.MapPath("~/Files/Temp"))) Directory.CreateDirectory(Server.MapPath("~/Files/Temp")); // создаем имя для архива string filename = Guid.NewGuid().ToString() + ".zip"; string fullZipPath = Server.MapPath("~/Files/Temp/" + filename); // определяем потоки для создания архива FileStream fsOut = System.IO.File.Create(fullZipPath); ZipOutputStream zipStream = new ZipOutputStream(fsOut); zipStream.SetLevel(3); // уровень сжатия от 0 до 9 // перебираем выбранные файлы и добавляем их в архив foreach (string file in filenames) { FileInfo fi = new FileInfo(Server.MapPath("~/Files/" + file)); if (!fi.Exists) continue; string entryName = ZipEntry.CleanName(fi.Name); ZipEntry newEntry = new ZipEntry(entryName); newEntry.DateTime = fi.LastWriteTime; newEntry.Size = fi.Length; zipStream.PutNextEntry(newEntry); byte[] buffer = new byte[4096]; using (FileStream streamReader = System.IO.File.OpenRead(fi.FullName)) { StreamUtils.Copy(streamReader, zipStream, buffer); } zipStream.CloseEntry(); } zipStream.IsStreamOwner = true; zipStream.Close(); string file_type = "application/zip"; return File(fullZipPath, file_type, filename); }
В этом случае архив будет создаваться, как физический файл в папке Files/Temp с последующей его передачей пользователю для загрузки.
Однако этот метод может показаться нам не самым оптимальным, ведь потребуется создавать лишний файл. В таком случае мы можем менять метод Index так, чтобы он создавал в памяти архив без сохранения на диск:
[HttpPost] public ActionResult Index(List<InputModel> files) { List<string> filenames = files.Where(m => m.Selected == true).Select(f => f.Name).ToList(); string filename = Guid.NewGuid().ToString() + ".zip"; MemoryStream outputMemStream = new MemoryStream(); ZipOutputStream zipStream = new ZipOutputStream(outputMemStream); zipStream.SetLevel(3); // уровень сжатия от 0 до 9 foreach (string file in filenames) { FileInfo fi = new FileInfo(Server.MapPath("~/Files/" + file)); string entryName = ZipEntry.CleanName(fi.Name); ZipEntry newEntry = new ZipEntry(entryName); newEntry.DateTime = fi.LastWriteTime; newEntry.Size = fi.Length; zipStream.PutNextEntry(newEntry); byte[] buffer = new byte[4096]; using (FileStream streamReader = System.IO.File.OpenRead(fi.FullName)) { StreamUtils.Copy(streamReader, zipStream, buffer); } zipStream.CloseEntry(); } zipStream.IsStreamOwner = false; zipStream.Close(); outputMemStream.Position = 0; string file_type = "application/zip"; return File(outputMemStream, file_type, filename); }
В результате при создании потока используется класс MemoryStream, а непосредственный архив создаётся в памяти, после чего он передаётся пользователю в виде потока MemoryStream.
По материалам блога «Статьи по C# и .NET».