Запись в ZipArchive с использованием HttpContext OutputStream

пытался получить "новый» ZipArchive включен в .NET 4.5 (System.IO.Compression.ZipArchive) работать на сайте ASP.NET. Но похоже, что нетне люблю писать в поток.HttpContext.Response.OutputStream

Мой следующий пример кода будет

System.NotSupportedException: указанный метод не поддерживается

как только попытка записи в потоке.

CanWrite свойство в потоке возвращает true.

Если я обмениваюсь OutputStream с файловым потоком, указывающим на локальный каталог, он работает. Что дает?

ZipArchive archive = new ZipArchive(HttpContext.Response.OutputStream, ZipArchiveMode.Create, false);

ZipArchiveEntry entry = archive.CreateEntry("filename");

using (StreamWriter writer = new StreamWriter(entry.Open()))
{
    writer.WriteLine("Information about this package.");
    writer.WriteLine("========================");
}

Трассировки стека:

[NotSupportedException: Specified method is not supported.]
System.Web.HttpResponseStream.get_Position() +29
System.IO.Compression.ZipArchiveEntry.WriteLocalFileHeader(Boolean isEmptyFile) +389
System.IO.Compression.DirectToArchiveWriterStream.Write(Byte[] buffer, Int32 offset, Int32 count) +94
System.IO.Compression.WrappedStream.Write(Byte[] buffer, Int32 offset, Int32 count) +41
 Alok16 мая 2013 г., 14:41
Можете ли вы попробовать линейное кодирование, вы можете пропустить response.contenttype и т. д .; Тем временем я попытаюсь воссоздать ошибку и вернусь к вам. Удачного кодирования
 Carlos Ferreira22 мая 2013 г., 11:44
Вы можете'т доступ напрямую к OutputStream. Если вы нажмете F12 на OutputStream, вы 'увидим, что OutputStream является ReadOnly (Get).
 Jonathan Myers20 мая 2013 г., 07:17
Можете ли вы получить трассировку стека из исключения? Из какого фактического метода происходит исключение NotSupportedException? Какой объект?
 Daniel Sørensen16 мая 2013 г., 14:02
м в настоящее время работает локально, поэтому среда разработки. Я'получаю HttpContext от IHttpHandler.
 Daniel Sørensen17 мая 2013 г., 10:28
Я не уверен, что вы подразумеваете под линейным кодированием. Я'Мы уже пытались установить разные ContentTypes безрезультатно. Не уверен, если это как-то связано с outputStream в любом случае.
 Dave Hillier17 мая 2013 г., 11:29
Попробуйте написать в MemoryStream. Искать в начале, а затем использовать CopyTo, чтобы скопировать это в ответ
 Alok16 мая 2013 г., 13:58
Вы пытаетесь это в среде разработки или на сервере?
 Daniel Sørensen17 мая 2013 г., 12:28
Это будет работать так же, как FileStream, но яя стараюсь не помещать все мои файлы в память или на диск, потому что файлы, которые ям обработка может быть любого огромного размера. Тот'Поэтому мне нужно добавить его непосредственно в OutputStream.
 Alok16 мая 2013 г., 14:04
хорошо, MVC или веб-формы?
 Daniel Sørensen16 мая 2013 г., 14:05
Веб-формы. Я'Я ограничен от использования MVC в моем проекте, к сожалению.
 Daniel Sørensen21 мая 2013 г., 11:36
Я обновил пост с помощью трассировки стека. Похоже, что метод write вызывает get_Position () в потоке, что не поддерживается .. Есть обходной путь?

Ответы на вопрос(5)

это не приложение MVC, где вы можете легко использоватьFileStreamResult class.I»

м, используя это в настоящее время сZipArchive создан с использованиемMemoryStreamЯ знаю, что это работает.

Имея это в виду, взгляните наFileStreamResult.WriteFile() метод :(

protected override void WriteFile(HttpResponseBase response)
{
    // grab chunks of data and write to the output stream
    Stream outputStream = response.OutputStream;
    using (FileStream)
    {
        byte[] buffer = newbyte[_bufferSize];
        while (true)
        {
            int bytesRead = FileStream.Read(buffer, 0, _bufferSize);
            if (bytesRead == 0)
            {
                // no more data
                break;
            }
            outputStream.Write(buffer, 0, bytesRead);
        }
    }
}

Весь FileStreamResult на CodePlex)

Вот как ям генерация и возврат.ZipArchive

У вас не должно быть проблем с заменой FSR на смелостьWriteFile метод сверху, гдеFileStream становитсяresultStream из кода ниже:

var resultStream = new MemoryStream();

using (var zipArchive = new ZipArchive(resultStream, ZipArchiveMode.Create, true))
{
    foreach (var doc in req)
    {
        var fileName = string.Format("Install.Rollback.{0}.v{1}.docx", doc.AppName, doc.Version);
        var xmlData = doc.GetXDocument();
        var fileStream = WriteWord.BuildFile(templatePath, xmlData);

        var docZipEntry = zipArchive.CreateEntry(fileName, CompressionLevel.Optimal);
        using (var entryStream = docZipEntry.Open())
        {
            fileStream.CopyTo(entryStream);
        }
    }
}
resultStream.Position = 0;

// add the Response Header for downloading the file
var cd = new ContentDisposition
    {
        FileName = string.Format(
            "{0}.{1}.{2}.{3}.Install.Rollback.Documents.zip",
            DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, (long)DateTime.Now.TimeOfDay.TotalSeconds),
        // always prompt the user for downloading, set to true if you want 
        // the browser to try to show the file inline
        Inline = false,
    };
Response.AppendHeader("Content-Disposition", cd.ToString());

// stuff the zip package into a FileStreamResult
var fsr = new FileStreamResult(resultStream, MediaTypeNames.Application.Zip);    
return fsr;

в заключениеЕсли вы будете записывать большие потоки (или большее их количество в любой момент времени), то вы можете рассмотреть возможность использования анонимных каналов для записи данных в выходной поток сразу же после записи их в основной поток в zip-файле. файл. Потому что вы будете хранить все содержимое файла в памяти на сервере.Конец этого ответа Подобный вопрос имеет хорошее объяснение того, как это сделать.

Замечания: Это было исправлено в .Net Core 2.0. Я'Я не уверен, каково состояние исправления для .Net Framework.

Calbertoferreira»Ответ имеет некоторую полезную информацию, но в большинстве своем вывод неверный. Чтобы создать архив, вы неНе нужно искать, но вы должны быть в состоянии прочитать.Position

В соответствии сдокументация, чтениеPosition должен поддерживаться только для потоков поиска, ноZipArchive кажется, требует этого даже из потоков без поиска, чтоЖук.

Итак, все, что вам нужно сделать, чтобы поддерживать запись файлов ZIP непосредственно вOutputStream это обернуть его в обычайStream это поддерживает получениеPosition, Что-то вроде:

class PositionWrapperStream : Stream
{
    private readonly Stream wrapped;

    private int pos = 0;

    public PositionWrapperStream(Stream wrapped)
    {
        this.wrapped = wrapped;
    }

    public override bool CanSeek { get { return false; } }

    public override bool CanWrite { get { return true; } }

    public override long Position
    {
        get { return pos; }
        set { throw new NotSupportedException(); }
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        pos += count;
        wrapped.Write(buffer, offset, count);
    }

    public override void Flush()
    {
        wrapped.Flush();
    }

    protected override void Dispose(bool disposing)
    {
        wrapped.Dispose();
        base.Dispose(disposing);
    }

    // all the other required methods can throw NotSupportedException
}

Используя это, следующий код запишет ZIP-архив в:OutputStream

using (var outputStream = new PositionWrapperStream(Response.OutputStream))
using (var archive = new ZipArchive(outputStream, ZipArchiveMode.Create, false))
{
    var entry = archive.CreateEntry("filename");

    using (var writer = new StreamWriter(entry.Open()))
    {
        writer.WriteLine("Information about this package.");
        writer.WriteLine("========================");
    }
}
 Brandon Tull10 мая 2017 г., 17:13
Потратив более одного дня на эту проблему, вы помогли мне решить проблему с помощью ZipArchive с созданным мной настраиваемым потоком без возможности поиска. Спасибо!
 AndyD03 февр. 2014 г., 00:14
Хорошо, я действительно задавался вопросом, было ли это просто получением позиции, которая была проблемой, и предполагал, что будут другие проблемы. Я не'попробуйте, вы сделали так, спасибо, что узнали! Я согласен, код ZipArchive может легко отслеживать, сколько байтов он уже написал, и не требует такой оболочки.
 TheNorthWes21 мая 2014 г., 20:16
Спасая мой бекон, пока мы говорим @svick спасибо огромное.
 Daniel Sørensen06 февр. 2014 г., 11:03
Хорошо, что вместо этого я использовал стороннюю zip-библиотеку, потому что не могнайти решение в то время. Но я'Я вернусь и попробую использовать вашу идею, когда у меня будет время. Я'Вернусь и отметлю ответ, если он работает! :)

что необходимо реализовать еще несколько методов и свойств абстрактного класса Stream и объявить член pos как можно дольше. После этого он работал как шарм. У меня нетМы тщательно протестировали этот класс, но он работает с целью возврата ZipArchive в HttpResponse. Я полагаюреализован поиск и чтение правильно, но им может потребоваться некоторая настройка.

class PositionWrapperStream : Stream
{
    private readonly Stream wrapped;

    private long pos = 0;

    public PositionWrapperStream(Stream wrapped)
    {
        this.wrapped = wrapped;
    }

    public override bool CanSeek
    {
        get { return false; }
    }

    public override bool CanWrite
    {
        get { return true; }
    }

    public override long Position
    {
        get { return pos; }
        set { throw new NotSupportedException(); }
    }

    public override bool CanRead
    {
        get { return wrapped.CanRead; }
    }

    public override long Length
    {
        get { return wrapped.Length; }
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        pos += count;
        wrapped.Write(buffer, offset, count);
    }

    public override void Flush()
    {
        wrapped.Flush();
    }

    protected override void Dispose(bool disposing)
    {
        wrapped.Dispose();
        base.Dispose(disposing);
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        switch (origin)
        {
            case SeekOrigin.Begin:
                pos = 0;
                break;
            case SeekOrigin.End:
                pos = Length - 1;
                break;
        }
        pos += offset;
        return wrapped.Seek(offset, origin);
    }

    public override void SetLength(long value)
    {
        wrapped.SetLength(value);
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        pos += offset;
        int result = wrapped.Read(buffer, offset, count);
        pos += count;
        return result;
    }
}

Упрощенная версияsvick»s ответ для архивирования файла на стороне сервера и отправки его через OutputStream:

using (var outputStream = new PositionWrapperStream(Response.OutputStream))
using (var archive = new ZipArchive(outputStream, ZipArchiveMode.Create, false))
{
    var entry = archive.CreateEntryFromFile(fullPathOfFileOnDisk, fileNameAppearingInZipArchive);
}

(Если это кажется очевидным, это нет мне!)

Страница MSDN вы'Вы увидите, что ZipArchiveMode.Create никогда не используется, то есть ZipArchiveMode.Update.

Несмотря на это,основной проблемой является OutputStream, который не• Поддерживает чтение и поиск, которые необходимы ZipArchive в режиме обновления:

Когда вы устанавливаете режим «Обновление», базовый файл или поток должен поддерживать чтение, запись и поиск. Содержимое всего архива хранится в памяти, и никакие данные не записываются в базовый файл или поток, пока архив не будет удален.

Источник: MSDN

Вы не былиполучить какие-либо исключения в режиме создания, потому что нужно только написать:

Когда вы устанавливаете режим «Создать», базовый файл или поток должен поддерживать запись, но не должен поддерживать поиск. Каждая запись в архиве может быть открыта только один раз для записи. Если вы создаете одну запись, данные записываются в основной поток или файл, как только они становятся доступны. Если вы создаете несколько записей, например, вызывая метод CreateFromDirectory, данные записываются в базовый поток или файл после создания всех записей.

Источник: MSDN

Я верю, что ты можешьсоздать zip-файл непосредственно в OutputStream, так как онs сетевой поток и поиск не поддерживаются:

Потоки могут поддерживать поиск. Поиск относится к запросу и изменению текущей позиции в потоке. Возможности поиска зависят от вида резервного хранилища в потоке. Например, сетевые потоки не имеют единой концепции текущей позиции и, следовательно, обычно не поддерживают поиск.

Альтернативой может быть запись в поток памяти, а затем использование метода OutputStream.Write для отправки zip-файла.

MemoryStream ZipInMemory = new MemoryStream();

    using (ZipArchive UpdateArchive = new ZipArchive(ZipInMemory, ZipArchiveMode.Update))
    {
        ZipArchiveEntry Zipentry = UpdateArchive.CreateEntry("filename.txt");

        foreach (ZipArchiveEntry entry in UpdateArchive.Entries)
        {
            using (StreamWriter writer = new StreamWriter(entry.Open()))
            {
                writer.WriteLine("Information about this package.");
                writer.WriteLine("========================");
            }
        }
    }
    byte[] buffer = ZipInMemory.GetBuffer();
    Response.AppendHeader("content-disposition", "attachment; filename=Zip_" + DateTime.Now.ToString() + ".zip");
    Response.AppendHeader("content-length", buffer.Length.ToString());
    Response.ContentType = "application/x-compressed";
    Response.OutputStream.Write(buffer, 0, buffer.Length);

РЕДАКТИРОВАТЬ: С помощью комментариев и дальнейшего чтения вы можете создавать большие Zip-файлы, поэтому поток памяти может вызвать проблемы.

В этом случае я предлагаю вам создать zip-файл на веб-сервере, а затем вывести файл с помощью Response.WriteFile.

 Carlos Ferreira23 мая 2013 г., 11:47
я пытаюсь с помощью response.filter посмотреть, смогу ли я найти обходной путь, но в соответствии с MSDN вы можете 't: потоки могут поддерживать поиск. Поиск относится к запросу и изменению текущей позиции в потоке. Возможности поиска зависят от вида резервного хранилища в потоке. Например, сетевые потоки не имеют единой концепции текущей позиции и, следовательно, обычно не поддерживают поиск.
 Carlos Ferreira20 янв. 2014 г., 18:07
Вы'Правильно. Проблема возникает, когда вы обновляете zip-архив, для обновления архива поток должен поддерживать поиск.
 Daniel Sørensen22 мая 2013 г., 16:30
Другими словами, этоНевозможно записать это прямо в outputStream? Как я уже говорил ранее, memoryStream не подходит, так как размеры файлов различаются и могут стать огромными.
 Doug Domeny25 авг. 2016 г., 15:28
Проблема возникает сZipArchiveMode.Create тоже.
 svick20 янв. 2014 г., 17:58
я в замешательстве, нет ваши цитаты означают, что это должно работать с?ZipArchiveMode.Create

Ваш ответ на вопрос