在 asp.net web api 中使用 MemoryStream 和 ZipArchive 将 zip 文件返回给客户端

Posted

技术标签:

【中文标题】在 asp.net web api 中使用 MemoryStream 和 ZipArchive 将 zip 文件返回给客户端【英文标题】:Use MemoryStream and ZipArchive to return zip file to client in asp.net web api 【发布时间】:2016-09-28 13:18:13 【问题描述】:

我正在尝试使用以下代码将 zip 文件从 asp.net web api 返回到客户端:

private byte[] CreateZip(string data)

    using (var ms = new MemoryStream())
    
        using (var ar = new ZipArchive(ms, ZipArchiveMode.Create, true))
        
            var file = archive.CreateEntry("file.html");

            using (var entryStream = file.Open())
            using (var sw = new StreamWriter(entryStream))
            
                sw .Write(value);
            
        
        return memoryStream.ToArray();
    


public HttpResponseMessage Post([FromBody] string data)

    HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
    result.Content = new ByteArrayContent(CreateZip(data));
    result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/zip, application/octet-stream");
    return result;

当我运行此代码时,我收到以下错误:

ExceptionMessage":"值'application/zip的格式, application/octet-stream' 无效。"

这是JS代码:

$.ajax(
  type: "POST",
  url: url,
  data: data,
  dataType: application/x-www-form-urlencoded
);

任何解释为什么会发生这种情况?我真的很感谢你的帮助

【问题讨论】:

显示您的 javascript 代码。您是否尝试过 return ResponseMessage(result) ? @RicardoPontual 查看更新。不,我没有使用 ResponseMessage(result) @RicardoPontual 我应该使用它而不是 result.Content.Headers.ContentType 吗?? 您的 API 代码工作正常吗?您是否尝试测试将文件保存在磁盘上?我只是想确保问题出在 JavaScript 而不是 API 上。 我在没有内存流的控制台应用程序中尝试了这个,它在本地保存是的.. 【参考方案1】:

这个适合asp.net core版本。

    [HttpGet("api/DownloadZip")]
    public async Task<IActionResult> Download()
    
        var path = "C:\\test.zip";
        var memory = new MemoryStream();
        using (var stream = new FileStream(path, FileMode.Open))
        
            await stream.CopyToAsync(memory);
        

        memory.Position = 0;
        return File(memory, GetContentType(path), Path.GetFileName(path));
    

然后使用Web客户端调用

      class Program
    

        static string url = "http://localhost:5000/api/DownloadZip";

        static async Task Main(string[] args)
        
            var p = @"c:\temp1\test.zip";

            WebClient webClient = new WebClient();

            webClient.DownloadFile(new Uri(url), p);                       

            Console.WriteLine("ENTER to exit...");
            Console.ReadLine();
        
    

【讨论】:

【参考方案2】:

这是对我有用的解决方案

C#端

public IActionResult GetZip([FromBody] List<DocumentAndSourceDto> documents)

    List<Document> listOfDocuments = new List<Document>();

    foreach (DocumentAndSourceDto doc in documents)
        listOfDocuments.Add(_documentService.GetDocumentWithServerPath(doc.Id));

    using (var ms = new MemoryStream())
    
        using (var zipArchive = new ZipArchive(ms, ZipArchiveMode.Create, true))
        
            foreach (var attachment in listOfDocuments)
            
                var entry = zipArchive.CreateEntry(attachment.FileName);

                using (var fileStream = new FileStream(attachment.FilePath, FileMode.Open))
                using (var entryStream = entry.Open())
                
                    fileStream.CopyTo(entryStream);
                
            

        
        ms.Position = 0;
        return File(ms.ToArray(), "application/zip");
    

    throw new ErrorException("Can't zip files");

不要错过这里的ms.Position = 0;

正面(Angular 4):

downloadZip(datas: any) 
    const headers = new Headers(
        'Content-Type': 'application/json',
        'Accept': 'application/zip'
    );

    const options = new RequestOptions( headers: headers, withCredentials: true, responseType: ResponseContentType.ArrayBuffer );
    const body = JSON.stringify(datas);
    return this.authHttp.post(`$environment.apiBaseUrlapi/documents/zip`, body, options)
        .map((response: Response) => 
            const blob = new Blob([response.blob()],  type: 'application/zip' );
            FileSaver.saveAs(blob, 'logs.zip');
        )
        .catch(this.handleError);

现在我可以将多个文件下载到 zip 中。

【讨论】:

` ms.Position = 0` 不需要调用。 ToArray() 将流写入字节数组,无论位置如何。 @ScottClark 没有我无法让我的代码按预期工作:/ @Fitch 是否立即开始下载?或者我们必须等到所有文件都被压缩后再开始下载? C# 部分结束了我永恒的痛苦(即复制流部分)。谢谢 我正在将流设置为响应内容,但在客户端得到空 zip 最后添加“ms.Position = 0”解决了我的问题【参考方案3】:

$.ajax 处理文本响应并将尝试 (utf-8) 解码内容:您的 zip 文件不是文本,您将获得损坏的内容。 jQuery 不支持二进制内容,因此您需要使用 this 链接并在 jQuery 上添加 ajax 传输或直接使用 XmlHttpRequest。使用 xhr,您需要设置 xhr.responseType = "blob" 并从 xhr.response 读取 blob。

// with xhr.responseType = "arraybuffer"
var arraybuffer = xhr.response;
var blob = new Blob([arraybuffer], type:"application/zip");
saveAs(blob, "example.zip");

// with xhr.responseType = "blob"
var blob = xhr.response;
saveAs(blob, "example.zip");
Edit: examples:

使用jquery.binarytransport.js(任何可以让您下载 Blob 或 ArrayBuffer 的库都可以)

$.ajax(
  url: url,
  type: "POST",
  contentType: "application/json",
  dataType: "binary", // to use the binary transport
  // responseType:'blob', this is the default
  data: data,
  processData: false,
  success: function (blob) 
    // the result is a blob, we can trigger the download directly
    saveAs(blob, "example.zip");
  
  // [...]
);

使用原始 XMLHttpRequest,您可以看到 this 问题,您只需添加 xhr.responseType = "blob" 即可获得 blob。

我个人建议你在 jQuery 上使用 ajax 传输,这很简单,你必须下载一个库,将它包含在项目中并写:dataType: "binary".

这是 API 代码,使用 DotNetZip (Ionic.Zip):

   [HttpPost]
    public HttpResponseMessage ZipDocs([FromBody] string[] docs)
    
        using (ZipFile zip = new ZipFile())
        
            //this code takes an array of documents' paths and Zip them
            zip.AddFiles(docs, false, "");
            return ZipContentResult(zip);
        
    

    protected HttpResponseMessage ZipContentResult(ZipFile zipFile)
    
        var pushStreamContent = new PushStreamContent((stream, content, context) =>
        
          zipFile.Save(stream);
            stream.Close(); 
        , "application/zip");

        return new HttpResponseMessage(HttpStatusCode.OK)  Content = pushStreamContent ;
    

【讨论】:

我都试过了,但都不适合我:/我不再收到错误但文件没有下载' 我没有看到我的后端有任何问题吗??postimg.org/image/3m861h77f 您是否检查了开发人员工具 (F12) 上的控制台?有什么错误吗?我个人是使用 DotNetZip 库而不是使用 ZipArchive 完成的。【参考方案4】:

您传递给MediaTypeHeaderValue 的构造函数的值的格式无效。您还尝试将多种内容类型添加到标头值。

内容类型标头采用单一类型/子类型,后跟用分号分隔的可选参数;

例如:

Content-Type: text/html; charset=ISO-8859-4

对于您的结果,您需要决定要使用哪一个。 application/zipapplication/octet-stream

result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/zip");

另外,为避免异常,您可以使用MediaTypeHeaderValue.TryParse 方法

var contentTypeString = "application/zip";
MediaTypeHeaderValue contentType = null;
if(MediaTypeHeaderValue.TryParse(contentTypeString, out contentType)) 
    result.Content.Headers.ContentType = contentType;

【讨论】:

U 不能将 null 分配给隐式类型变量 var contentType = null,这是错误的 @Kob_24 这是我的错字。我已经修好了。

以上是关于在 asp.net web api 中使用 MemoryStream 和 ZipArchive 将 zip 文件返回给客户端的主要内容,如果未能解决你的问题,请参考以下文章

Asp.Net Web API 2第十课——使用OWIN自承载Web API

如何在使用 HttpClient 使用 Asp Net Web Api 的 Asp Net Mvc 中提供实时数据?

Web API系列教程2.1 — ASP.NET Web API中的路由机制

Asp.Net Web API 2第三课——.NET客户端调用Web API

在 ASP.NET Web API 中使用多个 Get 方法进行路由

Web API1.1 ASP.NET Web API入门