Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core
В этой статье рассмотрим, как создать на лету архив из файлов для их последующей загрузки в ASP.NET MVC. Создание архива из загружаемых файлов позволит уменьшить объем загружаемых данных и соответственно уменьшить траффик.
Для архивации файлов добавим в проект через NuGet библиотеку SharpZipLib.
Предположим, в проекте есть каталог 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 передается пользователю.