Как я могу закрепить на лету и потоком в Response.Output в режиме реального времени?
Я пытаюсь использовать следующий код: я получаю поврежденный zip файл. Зачем?
Имена файлов выглядят нормально. Возможно, они не являются относительными именами, и что проблема?
private void trySharpZipLib(ArrayList filesToInclude)
{
// Response header
Response.Clear();
Response.ClearHeaders();
Response.Cache.SetCacheability(HttpCacheability.NoCache);
Response.StatusCode = 200; // http://community.icsharpcode.net/forums/p/6946/20138.aspx
long zipSize = calculateZipSize(filesToInclude);
string contentValue =
string.Format("attachment; filename=moshe.zip;"
); // + " size={0}", zipSize);
Response.ContentType = "application/octet-stream"; //"application/zip";
Response.AddHeader("Content-Disposition", contentValue);
Response.Flush();
using (ZipOutputStream zipOutputStream = new ZipOutputStream(Response.OutputStream) )
{
zipOutputStream.SetLevel(0);
foreach (string f in filesToInclude)
{
string filename = Path.Combine(Server.MapPath("."), f);
using (FileStream fs = File.OpenRead(filename))
{
ZipEntry entry =
new ZipEntry(ZipEntry.CleanName(filename))
{
DateTime = File.GetCreationTime(filename),
CompressionMethod = CompressionMethod.Stored,
Size = fs.Length
};
zipOutputStream.PutNextEntry(entry);
byte[] buffer = new byte[fs.Length];
// write to zipoutStream via buffer.
// The zipoutStream is directly connected to Response.Output (in the constructor)
ICSharpCode.SharpZipLib.Core.StreamUtils.Copy(fs, zipOutputStream, buffer);
Response.Flush(); // for immediate response to user
} // .. using file stream
}// .. each file
}
Response.Flush();
Response.End();
}
Ответы
Ответ 1
Мальчик, много кода! Ваша работа будет проще с помощью DotNetZip. Предполагая клиента HTTP 1.1, это работает:
Response.Clear();
Response.BufferOutput = false;
string archiveName= String.Format("archive-{0}.zip", DateTime.Now.ToString("yyyy-MMM-dd-HHmmss"));
Response.ContentType = "application/zip";
// see http://support.microsoft.com/kb/260519
Response.AddHeader("content-disposition", "attachment; filename=" + archiveName);
using (ZipFile zip = new ZipFile())
{
// filesToInclude is a IEnumerable<String> (String[] or List<String> etc)
zip.AddFiles(filesToInclude, "files");
zip.Save(Response.OutputStream);
}
// Response.End(); // will throw an exception internally.
// Response.Close(); // Results in 'Failed - Network error' in Chrome.
Response.Flush(); // See https://stackoverflow.com/a/736462/481207
// ...more code here...
Если вы хотите зашифровать пароль zip, то перед AddFiles() вставьте следующие строки:
zip.Password = tbPassword.Text; // optional
zip.Encryption = EncryptionAlgorithm.WinZipAes256; // optional
Если вам нужен самораспаковывающийся архив, замените zip.Save() на zip.SaveSelfExtractor().
Добавление; некоторые люди прокомментировали мне, что DotNetZip "не годится", потому что он создает весь ZIP в памяти перед потоковой передачей. Это не так. Когда вы вызываете AddFiles, библиотека создает список записей - объектов, которые представляют состояние вещей, подлежащих зашивке. Сжатие или шифрование не выполняются до вызова Save. Если вы укажете поток для вызова Save(), все сжатые байты передаются непосредственно клиенту.
В модели SharpZipLib можно создать запись, затем передать ее, затем создать другую запись и передать ее и т.д. С помощью DotNetZip ваше приложение сначала создает полный список записей, а затем выводит их все. Ни один из подходов не обязательно "быстрее", чем другой, хотя для длинных списков файлов, скажем, 30 000, время от времени до первого байта будет быстрее с SharpZipLib. С другой стороны, я бы не рекомендовал динамически создавать zip файлы с 30 000 записей.
EDIT
Как и в DotNetZip v1.9, DotNetZip также поддерживает ZipOutputStream. Я все еще считаю, что проще сделать то, что я показал здесь.
Некоторые люди имеют случай, когда у них есть zip-контент, который "в основном тот же" для всех пользователей, но есть несколько файлов, которые отличаются друг от друга. DotNetZip хорош в этом тоже. Вы можете читать zip-архив из файла файловой системы, обновлять несколько записей (добавлять несколько, удалять несколько и т.д.), А затем сохранять в Response.OutputStream. В этом случае DotNetZip не будет повторно сжимать или повторно шифровать любую из записей, которые вы не изменили. Намного быстрее.
Конечно, DotNetZip предназначен для любого приложения .NET, а не только для ASP.NET. Таким образом, вы можете сохранить любой поток.
Если вам нужна дополнительная информация, просмотрите сайт или опубликуйте dotnetzip.
Ответ 2
Не совсем уверен, как это сделать в ASP.NET(ранее не пробовал), но в целом, если HTTP-клиент поддерживает HTTP v1.1 (как указано в версии его запроса), сервер может отправить заголовок ответа "Передача-кодирование", который указывает "chunked", а затем отправляет данные ответа с использованием нескольких блоков данных по мере их появления. Это позволяет передавать потоки данных в реальном времени, когда вы заранее не знаете окончательный размер данных (и, следовательно, не можете установить заголовок ответа Content-Length). Посмотрите RFC 2616 Раздел 3.6 для более подробной информации.
Ответ 3
Для тех, кто пропустит ZipOutputStream от SharpZipLib, вот простой код, который позволяет использовать DotNetZip "обычный путь потока .NET".
Однако учтите, что он неэффективен по сравнению с реальным потоковым потоковым решением, подобным SharpZipLib, поскольку он использует внутренний MemoryStream, прежде чем на самом деле вызывает функцию DotNetZip.Save(). Но, к сожалению, SharpZibLib пока не защищает шифрование EAS (и, конечно же, никогда). Надеюсь, что Cheeso добавит эту функцию в dotNetZip в ближайшее время?; -)
/// <summary>
/// DotNetZip does not support streaming out-of-the-box up to version v1.8.
/// This wrapper class helps doing so but unfortunately it has to use
/// a temporary memory buffer internally which is quite inefficient
/// (for instance, compared with the ZipOutputStream of SharpZibLib which has other drawbacks besides).
/// </summary>
public class DotNetZipOutputStream : Stream
{
public ZipFile ZipFile { get; private set; }
private MemoryStream memStream = new MemoryStream();
private String nextEntry = null;
private Stream outputStream = null;
private bool closed = false;
public DotNetZipOutputStream(Stream baseOutputStream)
{
ZipFile = new ZipFile();
outputStream = baseOutputStream;
}
public void PutNextEntry(String fileName)
{
memStream = new MemoryStream();
nextEntry = fileName;
}
public override bool CanRead { get { return false; } }
public override bool CanSeek { get { return false; } }
public override bool CanWrite { get { return true; } }
public override long Length { get { return memStream.Length; } }
public override long Position
{
get { return memStream.Position; }
set { memStream.Position = value; }
}
public override void Close()
{
if (closed) return;
memStream.Position = 0;
ZipFile.AddEntry(nextEntry, Path.GetDirectoryName(nextEntry), memStream);
ZipFile.Save(outputStream);
memStream.Close();
closed = true;
}
public override void Flush()
{
memStream.Flush();
}
public override int Read(byte[] buffer, int offset, int count)
{
throw new NotSupportedException("Read");
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException("Seek");
}
public override void SetLength(long value)
{
memStream.SetLength(value);
}
public override void Write(byte[] buffer, int offset, int count)
{
memStream.Write(buffer, offset, count);
}
}
Ответ 4
Попробуйте добавить следующий заголовок.
Response.AddHeader("Content-Length", zipSize);
Я знаю, что это вызывало у меня проблемы раньше.
Edit:
Эти другие 2 также могут помочь:
Response.AddHeader("Content-Description", "File Transfer");
Response.AddHeader("Content-Transfer-Encoding", "binary");
Ответ 5
Вы пробовали промывать ZipOutputStream до того, как сбросили ответ?
Вы можете сохранить zip на клиенте и протестировать его в zip-утилите?
Ответ 6
Некромантия.
Здесь, как это сделано правильно, используя закрытие, начиная с DotNetZip v1.9 +, как рекомендовано Cheeso в комментариях:
public static void Run()
{
using (Ionic.Zip.ZipFile zip = new Ionic.Zip.ZipFile())
{
for (int i = 1; i < 11; ++i)
{
zip.AddEntry("LeaseContractForm_" + i.ToString() + ".xlsx", delegate(string filename, System.IO.Stream output)
{
// ByteArray from ExecuteReport - only ONE ByteArray at a time, because i might be > 100, and ba.size might be > 20 MB
byte[] ba = Portal_Reports.LeaseContractFormPostProcessing.ProcessWorkbook();
output.Write(ba, 0, ba.Length);
});
} // Next i
using (System.IO.Stream someStream = new System.IO.FileStream(@"D:\test.zip", System.IO.FileMode.Create, System.IO.FileAccess.Write, System.IO.FileShare.None))
{
zip.Save(someStream);
}
} // End Using zip
} // End Sub Run
И вариант VB.NET, если кому-то это понадобится (обратите внимание, что это всего лишь тест, на самом деле он будет вызываться с разными in_contract_uid и in_premise_uid для каждого шага цикла):
Imports System.Web
Imports System.Web.Services
Public Class test
Implements System.Web.IHttpHandler
Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
Dim in_contract_uid As String = context.Request.Params("in_contract_uid")
Dim in_premise_uid As String = context.Request.Params("in_premise_uid")
If String.IsNullOrWhiteSpace(in_contract_uid) Then
in_contract_uid = "D57A62D7-0FEB-4FAF-BB09-84106E3E15E9"
End If
If String.IsNullOrWhiteSpace(in_premise_uid) Then
in_premise_uid = "165ECACA-04E6-4DF4-B7A9-5906F16653E0"
End If
Dim in_multiple As String = context.Request.Params("in_multiple")
Dim bMultiple As Boolean = False
Boolean.TryParse(in_multiple, bMultiple)
If bMultiple Then
Using zipFile As New Ionic.Zip.ZipFile
For i As Integer = 1 To 10 Step 1
' Dim ba As Byte() = Portal_Reports.LeaseContractFormReport.GetLeaseContract(in_contract_uid, in_premise_uid) '
' zipFile.AddEntry("LeaseContractForm_" + i.ToString() + ".xlsx", ba) '
zipFile.AddEntry("LeaseContractForm_" + i.ToString() + ".xlsx", Sub(filename As String, output As System.IO.Stream)
Dim ba As Byte() = Portal_Reports.LeaseContractFormReport _
.GetLeaseContract(in_contract_uid, in_premise_uid)
output.Write(ba, 0, ba.Length)
End Sub)
Next i
context.Response.ClearContent()
context.Response.ClearHeaders()
context.Response.ContentType = "application/zip"
context.Response.AppendHeader("content-disposition", "attachment; filename=LeaseContractForm.zip")
zipFile.Save(context.Response.OutputStream)
context.Response.Flush()
End Using ' zipFile '
Else
Dim ba As Byte() = Portal_Reports.LeaseContractFormReport.GetLeaseContract(in_contract_uid, in_premise_uid)
Portal.ASP.NET.DownloadFile("LeaseContractForm.xlsx", "attachment", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", ba)
End If
End Sub
ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
Get
Return False
End Get
End Property
End Class