2

I have a web service that I can call and save the returned csv file. Everything seems to be working OK. What I am now interested in doing is returning multiple CSV files for the user to download. What is the proper way to handle this? I'm guessing I need a way to package them up (zip? perhaps)?

[HttpPost]
[Route("OutputTemplate")]
public HttpResponseMessage OutputTemplate()
{
    HttpResponseMessage msg = new HttpResponseMessage();
    string body = this.Request.Content.ReadAsStringAsync().Result;
    try
    {
        string contents = DoStuff(body) // get contents based on body

        MemoryStream stream = new MemoryStream();
        StreamWriter writer = new StreamWriter(stream);
        writer.Write(contents);
        writer.Flush();
        stream.Position = 0;

        msg.StatusCode = HttpStatusCode.OK;
        msg.Content = new StreamContent(stream);
        msg.Content.Headers.ContentType = new MediaTypeHeaderValue("text/csv");
        msg.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
            {
                FileName = "fileexport"
            };

        return msg;

        }
        ...
    }
Bender Bending
  • 729
  • 8
  • 25
Talen Kylon
  • 1,908
  • 7
  • 32
  • 60
  • 1
    Yes package them up into a zip file/archive and download – Nkosi Jul 18 '17 at 16:25
  • @Nkosi Thank you, can you point me to any good resources you know of that I can reference? – Talen Kylon Jul 18 '17 at 16:26
  • 1
    Take a look at an answer I gave here https://stackoverflow.com/questions/36776704/ziparchive-returning-empty-folder-c-sharp/36777930#36777930 you should be able to modify it to suit your needs – Nkosi Jul 18 '17 at 16:30

1 Answers1

5

Using the following model to abstract file name and content

public class FileModel {
    public string FileName { get; set; }
    public byte[] FileContent { get; set; }
}

The following extension was derived to compress the file content

public static class ZipArchiveExtensions {

    public static Stream Compress(this IEnumerable<FileModel> files) {
        if (files.Any()) {
            var ms = new MemoryStream();
            using(var archive = new ZipArchive(
                stream: ms, 
                mode: ZipArchiveMode.Create, 
                leaveOpen: true
            )){
                foreach (var file in files) {
                    var entry = archive.add(file);
                }
            }
            ms.Position = 0;
            return ms;
        }
        return null;
    }

    private static ZipArchiveEntry add(this ZipArchive archive, FileModel file) {
        var entry = archive.CreateEntry(file.FileName, CompressionLevel.Fastest);
        using (var stream = entry.Open()) {
            stream.Write(file.FileContent, 0, file.FileContent.Length);
        }
        return entry;
    }        
}

With that in place, the example API controller action could look something like this.

public class ExampleApiController : ApiController {
    public async Task<IHttpActionResult> OutputTemplate() {
        IHttpActionResult result = BadRequest();
        var body = await Request.Content.ReadAsStreamAsync();                
        List<FileModel> files = DoSomething(body);
        if (files.Count > 1) {
            //compress the files.
            var archiveStream = files.Compress();
            var content = new StreamContent(archiveStream);
            var response = Request.CreateResponse(System.Net.HttpStatusCode.OK);
            response.Content = content;
            response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/zip");
            response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") {
                FileName = "fileexport.zip"
            };
            result = ResponseMessage(response);
        } else if (files.Count == 1) {
            //return the single file
            var fileName = files[0].FileName; //"fileexport.csv"
            var content = new ByteArrayContent(files[0].FileContent);
            var response = Request.CreateResponse(System.Net.HttpStatusCode.OK);
            response.Content = content;
            response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/csv");
            response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") {
                FileName = fileName
            };
            result = ResponseMessage(response);
        }
        return result;
    }

    private List<FileModel> DoSomething(System.IO.Stream body) {
        //...TODO: implement file models
        throw new NotImplementedException();
    }
}
Nkosi
  • 235,767
  • 35
  • 427
  • 472