11

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

user3378165
  • 5,189
  • 15
  • 49
  • 86
Kob_24
  • 552
  • 1
  • 5
  • 25

4 Answers4

10

$.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 };
    }
user3378165
  • 5,189
  • 15
  • 49
  • 86
  • i tried both but are not working for me :/ i dont get the error anymore but the file is not downloaded' – Kob_24 May 30 '16 at 23:37
  • i dont see any problem on my backend part do u ??http://postimg.org/image/3m861h77f/ – Kob_24 May 30 '16 at 23:39
  • Did you check the Consol on the Developers Tool (F12)? Are there any error? I personaly did it using DotNetZip library and not using ZipArchive. – user3378165 May 31 '16 at 05:08
8

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.

Fitch
  • 994
  • 9
  • 15
  • ` ms.Position = 0` does not need to be called. [ToArray()](https://msdn.microsoft.com/en-us/library/system.io.memorystream.toarray(v=vs.110).aspx) writes the stream to a byte array regardless of position. – Scott Clark Dec 11 '17 at 14:33
  • @ScottClark without I couldn't get my code work as expected :/ – Fitch Dec 11 '17 at 16:34
  • @Fitch Does the download starts immediately? Or we have to wait till all files are zipped and then download starts? – Shyamal Parikh Dec 22 '17 at 16:15
  • @ShyamalParikh It does wait that all files are zipped then download starts – Fitch Dec 29 '17 at 16:26
  • 2
    C# part finished my eternity suffering (that copying streams part). Thanks – Ademar May 31 '18 at 10:25
  • 1
    I was setting stream into response content but was getting empty zip on client side finally adding "ms.Position = 0" solve my problem – Bhavesh Aug 01 '19 at 11:49
2

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();
        }
    }
Ken
  • 39
  • 1
  • 4
0

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;
}
Nkosi
  • 191,971
  • 29
  • 311
  • 378