Архивация файлов на лету

Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core

Последнее обновление: 1.11.2015

В этой статье рассмотрим, как создать на лету архив из файлов для их последующей загрузки в ASP.NET MVC. Создание архива из загружаемых файлов позволит уменьшить объем загружаемых данных и соответственно уменьшить траффик.

Для архивации файлов добавим в проект через NuGet библиотеку SharpZipLib.

Создание архива файлов на лету в ASP.NET MVC 5

Предположим, в проекте есть каталог 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 передается пользователю.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850