Когда нужно ZIPовать на лету
- 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 архив с двумя файлами.