Kob_24
Kob_24

Reputation: 612

Use MemoryStream and ZipArchive to return zip file to client in asp.net web api

I am trying to return zip file from asp.net web api to client using The following Code:

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;
}

When i run this code i get the following error:

ExceptionMessage":"The format of value 'application/zip, application/octet-stream' is invalid."

this is the JS code:

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

Any explanation why this is happen? I would really appriciate your help guys

Upvotes: 13

Views: 39803

Answers (5)

Abhishek Grover
Abhishek Grover

Reputation: 21

If you want to return it as a downloadable file. Try this. Return type can be IActionResult

 var theJson = JsonConvert.SerializeObject(result);
            using (var zipStream = new MemoryStream())
            {
                using (var zipArchive = new ZipArchive(zipStream, ZipArchiveMode.Create, leaveOpen: true))
                {
                    var entry = zipArchive.CreateEntry("data.json");
                    using (var entryStream = entry.Open())
                    using (var jsonStreamWriter = new StreamWriter(entryStream))
                    {
                        jsonStreamWriter.Write(theJson);
                    }
                }
                // Reset zip stream to point at the beginning of data
                zipStream.Seek(0, SeekOrigin.Begin);
                // Set the response headers
                //Response.Headers.Add("Content-Disposition", "attachment; filename=compressed_response.zip");
                // Return zip file containing JSON data as response
                return File(zipStream.ToArray(), "application/zip");
            }

Upvotes: 1

Ken
Ken

Reputation: 49

This is suitable for asp.net core version.

    [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));
    }

Then use Web client call

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

Upvotes: 3

fitch
fitch

Reputation: 1104

Here is my solution that worked for me

C# side

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

don't miss the ms.Position = 0; here

Front side (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.apiBaseUrl}api/documents/zip`, body, options)
        .map((response: Response) => {
            const blob = new Blob([response.blob()], { type: 'application/zip' });
            FileSaver.saveAs(blob, 'logs.zip');
        })
        .catch(this.handleError);
}

Now I'm able to download multiple files to zip.

Upvotes: 11

user3378165
user3378165

Reputation: 6916

$.ajax handles text responses and will try to (utf-8) decode the content: your zip file isn't text, you will get a corrupted content. jQuery doesn't support binary content so you need to use this link and add an ajax transport on jQuery or use directly a XmlHttpRequest. With an xhr, you need to set xhr.responseType = "blob" and read from xhr.response the 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:

with jquery.binarytransport.js (any library that let you download a Blob or an ArrayBuffer will do)

$.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");
  }
  // [...]
});

with a raw XMLHttpRequest, you can see this question, you just need to add a xhr.responseType = "blob" to get a blob.

I personally recommended you to use an ajax transport on jQuery, that's very easy, you have to download a library, include it in the project and write: dataType: "binary".

This is the API code, using 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 };
    }

Upvotes: 10

Nkosi
Nkosi

Reputation: 247323

The format of the value you are passing into the constructor of the MediaTypeHeaderValue is invalid. You are also trying to add multiple content types to the header value.

Content type header takes a single type/subtype, followed by optional parameters separated with semi-colons ;

eg:

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

For your result you need to decide on which one you want to use. application/zip or application/octet-stream

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

Also, to avoid exception you can use the MediaTypeHeaderValue.TryParse method

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

Upvotes: 0

Related Questions