Dan*_*sen 36 c# outputstream httpcontext .net-4.5 asp.net-4.5
我一直试图让.NET 4.5(System.IO.Compression.ZipArchive
)中包含的"新"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("========================");
}
Run Code Online (Sandbox Code Playgroud)
堆栈跟踪:
[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
Run Code Online (Sandbox Code Playgroud)
svi*_*ick 45
注意:这已在.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
}
Run Code Online (Sandbox Code Playgroud)
使用此代码,以下代码将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("========================");
}
}
Run Code Online (Sandbox Code Playgroud)
对svick 2014年2月2日答案的改进.我发现有必要实现Stream抽象类的更多方法和属性,并声明pos成员.之后,它就像一个魅力.我没有对这个类进行过广泛的测试,但它的工作原理是在HttpResponse中返回一个ZipArchive.我假设我已经正确实现了Seek和Read,但是他们可能需要一些调整.
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;
}
}
Run Code Online (Sandbox Code Playgroud)
如果将您的代码改编与MSDN 页面中提供的版本进行比较,您会发现 ZipArchiveMode.Create 从未使用过,使用的是 ZipArchiveMode.Update。
尽管如此,主要问题是 OutputStream 不支持 ZipArchive 在更新模式下需要的 Read and Seek:
当您将模式设置为更新时,底层文件或流必须支持读取、写入和查找。整个存档的内容保存在内存中,在存档被释放之前,不会将数据写入底层文件或流。
使用 create 模式没有任何异常,因为它只需要编写:
当您将模式设置为创建时,底层文件或流必须支持写入,但不必支持查找。存档中的每个条目只能打开一次进行写入。如果您创建单个条目,数据将在可用时立即写入底层流或文件。如果您创建多个条目,例如通过调用 CreateFromDirectory 方法,则在创建所有条目后,数据将写入基础流或文件。
我相信你不能直接在 OutputStream 中创建一个 zip 文件,因为它是一个网络流并且不支持搜索:
流可以支持搜索。查找是指查询和修改流中的当前位置。查找能力取决于流具有的后备存储类型。例如,网络流没有统一的当前位置概念,因此通常不支持搜索。
另一种方法是写入内存流,然后使用 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);
Run Code Online (Sandbox Code Playgroud)
编辑:根据评论和进一步阅读的反馈,您可能正在创建大型 Zip 文件,因此内存流可能会给您带来问题。
在这种情况下,我建议您在 Web 服务器上创建 zip 文件,然后使用 Response.WriteFile 输出文件。