Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/google-apps-script/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 使用HttpContext输出流写入ZipArchive_C#_.net 4.5_Outputstream_Httpcontext_Asp.net 4.5 - Fatal编程技术网

C# 使用HttpContext输出流写入ZipArchive

C# 使用HttpContext输出流写入ZipArchive,c#,.net-4.5,outputstream,httpcontext,asp.net-4.5,C#,.net 4.5,Outputstream,Httpcontext,Asp.net 4.5,我一直在尝试让.NET4.5(System.IO.Compression.ZipArchive)中包含的“新”ZipArchive在ASP.NET站点中工作。但它似乎不喜欢写入HttpContext.Response.OutputStream流 下面的代码示例将抛出 System.NotSupportedException:不支持指定的方法 在流上尝试写入时 流上的CanWrite属性返回true 如果我将OutputStream与指向本地目录的filestream交换,它就可以工作。有什么好处

我一直在尝试让.NET4.5(
System.IO.Compression.ZipArchive
)中包含的“新”ZipArchive在ASP.NET站点中工作。但它似乎不喜欢写入
HttpContext.Response.OutputStream

下面的代码示例将抛出

System.NotSupportedException:不支持指定的方法

在流上尝试写入时

流上的
CanWrite
属性返回true

如果我将OutputStream与指向本地目录的filestream交换,它就可以工作。有什么好处

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:不支持指定的方法。]
System.Web.HttpResponseStream.get_Position()+29
System.IO.Compression.ZipArchiveEntry.WriteLocalFileHeader(布尔isEmptyFile)+389
System.IO.Compression.DirectToArchiveWriterStream.Write(字节[]缓冲区,Int32偏移量,Int32计数)+94
System.IO.Compression.WrappedStream.Write(字节[]缓冲区,Int32偏移量,Int32计数)+41

如果将代码自适应与中显示的版本进行比较,您将看到从未使用ZipArchiveMode.Create,使用的是ZipArchiveMode.Update

尽管如此,主要问题是OutputStream不支持ZipArchive在更新模式下所需的读取和查找功能

将模式设置为更新时,基础文件或流必须 支持阅读、写作和寻找。整个报告的内容 归档文件保存在内存中,不会将任何数据写入底层 文件或流,直到释放存档

创建模式没有任何异常,因为它只需要写入:

将模式设置为“创建”时,基础文件或流必须支持写入,但不必支持查找。存档中的每个条目只能打开一次进行写入。如果创建单个条目,则数据一可用就会写入底层流或文件。如果创建多个条目,例如通过调用CreateFromDirectory方法,则在创建所有条目后,数据将写入基础流或文件

我相信您不能直接在OutputStream中创建zip文件,因为它是网络流,不支持seek:

流可以支持搜索。查找是指查询和修改流中的当前位置。搜索能力取决于流具有的备份存储类型。例如,网络流没有当前位置的统一概念,因此通常不支持搜索

另一种方法是写入内存流,然后使用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文件,因此内存流可能会给您带来问题


在这种情况下,我建议您在web服务器上创建zip文件,然后使用Response.WriteFile输出该文件。

注意:这在.Net Core 2.0中已修复。我不确定.Net Framework的修复程序的状态如何


卡尔伯托费雷拉的回答有一些有用的信息,但结论大多是错误的。要创建存档,您不需要搜索,但需要能够读取
位置

根据,读取
Position
应该只支持可查找的流,但是
ZipArchive
似乎需要这样做,即使是从不可查找的流,也不需要这样做

因此,要支持将ZIP文件直接写入
OutputStream
,只需将其封装在支持获取
位置的自定义
流中即可。比如:

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 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("========================");
    }
}

svick压缩服务器端文件并通过OutputStream发送的答案的简化版本:

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

(如果这看起来很明显,对我来说不是!)

这可能不是一个MVC应用程序,您可以轻松地使用
FileStreamResult

我目前正在使用它与使用
MemoryStream
创建的
ZipArchive
一起使用,因此我知道它是有效的

考虑到这一点,请查看
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);
        }
    }
}
()

下面是我如何生成和返回
ZipArchive

用上面的
WriteFile
方法替换FSR应该没有问题,其中
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文件中的基础流。因为您将在服务器的内存中保存所有文件内容。对于一个类似的问题,有一个很好的解释,说明了如何做到这一点

对斯维克2014年2月2日回答的改进。我发现有必要实现流抽象类的更多方法和属性,并尽可能长地声明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;
    }
}

您是在开发环境中还是在服务器中尝试它?我目前正在本地运行它,所以是在开发环境中。我从IHttpHandler.ok、MVC或Web表单?Web表单获取HttpContext。我在我的项目中使用MVC受到了限制,很遗憾。你能试试线性编码吗?你可能缺少响应。contenttype等等;在此期间,我将尝试重新创建错误并返回给您。快乐编码