Когда нужно ZIPовать на лету

    • ASP.NET
    • .NET
    • C#
    • SharpZipLib
  • modified:
  • reading: 3 minutes

Задача тривиальная. Делаете какие то отчеты, файлы и хотите дать возможность пользователю их скачать в своем ASP.NET приложении. Почему полезно использовать архивацию?: а) уменьшается скачиваемый объем б) можно отдавать файлы пакетами по несколько.

В .net есть специальный класс для работы с GZip, находится в System.IO.Compression, и называется GZipStream, но он не позволяет хранить несколько файлов в одном архиве, такая специфика. Есть, конечно, энтузиасты, которые при помощи его создают и полноценные zip архивы (правда открывать они, вроде, могут их только при помощи своих программ - по крайней мере, мне попадались только такие).

В .NET 3.0 и выше можно использовать класс ZipPackage из System.IO.Packaging, который находится в сборке WindowsBase.DLL (находится примерно в C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\WindowsBase.dll), не знаю почему данная сборка не лежит в GAC, вот тут есть пример, как это все использовать: Creating Zip archives in .NET (without an external library like SharpZipLib).

Но все же если используете SharpZipLib, как сделать архивирование в памяти? Наверняка, можно сделать через OutputZipStream. Но мне понравился класс FileZip, у которого есть метод Add(), в который можно передать имя файла, или готовый ZipEntry, а так же есть возможность передать объект с типом, унаследованным от IStaticDataSource.

Итак, реализуем класс, который будет использоваться в качестве Stream (потока) для архивирования классом FileZip:

/// <summary>
/// Реализация ресурса для зипования
/// </summary>
class MemoryStreamStaticDataSource : ICSharpCode.SharpZipLib.Zip.IStaticDataSource, IDisposable
{
  private MemoryStream MemoryStream { get; set; }
 
  /// <summary>
  /// Создаем ресурс с MemoryStream
  /// </summary>
  /// <param name="bytes"></param>
  public MemoryStreamStaticDataSource(byte[] bytes)
  {
    MemoryStream = new MemoryStream(bytes) { Position = 0 };
  }
 
  #region IStaticDataSource Members
 
  public Stream GetSource()
  {
    return MemoryStream;
  }
 
  #endregion
 
  #region IDisposable Members
 
  public void Dispose()
  {
    if (MemoryStream != null)
      MemoryStream.Dispose();
  }
 
  #endregion
}

Файлы, пускай, у нас уже будут в виде набора байт (byte[]), идея в том, что эти файлы мы не хотим хранить физически, только в оперативной памяти. Задача - мы генерируем эти файлы сами, для примера я написал такой код:

/// <summary>
/// Пример, получаем данные первого сгенерированного файла
/// </summary>
/// <returns></returns>
private static byte[] GetFirstFileData()
{
  return GetFileData(@"Первый файл.");
}
 
/// <summary>
/// Пример, получаем данные второго сгенерированного файла
/// </summary>
/// <returns></returns>
private static byte[] GetSecondFileData()
{
  return GetFileData(@"Второй файл.");
}
 
private static byte[] GetFileData(string textdata)
{
  return Encoding.UTF8.GetBytes(textdata);
}

Итак, последнее, используя класс ZipFile, добавляем в архив наши бутафорские файлы и возвращаем в Response страницы (вместо контента страницы).

/// <summary>
/// Переопределяем загрузку страницы, и возвращаем zip файл
/// </summary>
/// <param name="e"></param>
protected override void OnLoad(EventArgs e)
{
  base.OnLoad(e);
 
  Response.Clear();
  //Указывает MIME тип
  Response.ContentType = "application/zip";
  //Указываем, что приаттачен файл с именем file.zip
  Response.AddHeader("Content-Disposition", string.Format("attachment; filename=\"{0}\"", HttpUtility.UrlEncodeUnicode("file.zip")));
 
  //Получаем данные(файл архива) и записываем в Response
  byte[] bytes = GetZipData();
  Response.OutputStream.Write(bytes, 0, bytes.Length);
}
 
private static byte[] GetZipData()
{
  //Создаем архив в памяти
  using (MemoryStream ms = new MemoryStream())
  {
    using (ICSharpCode.SharpZipLib.Zip.ZipFile file = new ICSharpCode.SharpZipLib.Zip.ZipFile(ms))
    {
      file.BeginUpdate();
 
      //Добавляем в архив первый файл
      file.Add(new MemoryStreamStaticDataSource(GetFirstFileData()), "file1.txt");
      //Добавляем в архив второй файл
      file.Add(new MemoryStreamStaticDataSource(GetSecondFileData()), "file2.txt");
 
      file.CommitUpdate();
    }
 
    return ms.ToArray();
  }
}

В результате у нас получиться страница aspx, которая будет возвращать zip архив с двумя файлами.

Скачать пример: WebApplicationZipSample.zip

See Also