Архивируем файлы в ASP.NET MVC | OTUS

Архивируем файлы в ASP.NET MVC

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

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

20.1-20219-555bb8.png

Допустим, в нашем проекте уже есть каталог 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>

При этом визуально мы увидим примерно следующее:

20.2-20219-d1f835.png

Что же, давайте определим 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».

Не пропустите новые полезные статьи!

Спасибо за подписку!

Мы отправили вам письмо для подтверждения вашего email.
С уважением, OTUS!

Автор
0 комментариев
Для комментирования необходимо авторизоваться
Популярное
Сегодня тут пусто