5

I would like to return a csv generated from my database using Web API 5.0 It is working fine except that the csv returned is truncated.

I assume the issue is on the MemoryBuffer management, but I can't find where it is.

My code (solved):

        IEnumerable<MasterObsTrip> masterTripList = _obsvMasterRepo.GetObsTrips(vesselName, dateYear, port, obsvCode, obsvTripCode, obsvProgCode, lastModifiedDateYear, lastModifiedBy, statusCode);
        IList<MasterObsTripModel> masterTripModelList = new List<MasterObsTripModel>();
        foreach (MasterObsTrip trip in masterTripList)
            masterTripModelList.Add(new MasterObsTripModel(trip));

        var stream = new MemoryStream();
        var writer = new StreamWriter(stream);

        CsvFileDescription outputFileDescription = new CsvFileDescription
        {
            SeparatorChar = ',', // comma delimited
            FirstLineHasColumnNames = true, // no column names in first record
            FileCultureName = "nl-NL" // use formats used in The Netherlands
        };
        CsvContext cc = new CsvContext();
        cc.Write(masterTripModelList,writer,outputFileDescription);
        writer.Flush();
        stream.Position = 0;
        HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
        response.Content = new StreamContent(stream);
        response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
        response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
        response.Content.Headers.ContentDisposition.FileName = "ObserverTripList.csv";
        stream.Flush();
        return response;

Thanks

11
  • avoid calling stream.Flush() as the actual write to the stream happens way later...try and see if this resolves your issue.. Commented Nov 13, 2013 at 22:44
  • Thx Kiran, but removing the stream.Flush does not change anything Commented Nov 13, 2013 at 23:08
  • hmm, ok...everything else looks ok to me...just to make sure, have you tried seeing the actual content in the memory stream and see if it has the whole content? you could try writing this memory stream to a disk file and see if it indeed has entire content... Commented Nov 13, 2013 at 23:16
  • Yes, I generated the file on the server and it contains my whole dataset Commented Nov 13, 2013 at 23:22
  • hmm,I am not sure whats going on...would it be possible for you to share a repro? (which version of Web API are you using?) Commented Nov 13, 2013 at 23:30

1 Answer 1

5

I would try doing writer.Flush() just before you reset the stream.Position = 0

Also, if you often have a need for CSV content I would also suggest creating yourself a CsvContent class.

public class CsvContent<T> : HttpContent
    {
        private readonly MemoryStream _stream = new MemoryStream();
        public CsvContent(CsvFileDescription outputFileDescription, string filename, IEnumerable<T> data)
        {
            var cc = new CsvContext();
            var writer = new StreamWriter(_stream);
            cc.Write(data, writer, outputFileDescription);
            writer.Flush();
            _stream.Position = 0;

            Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
            Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
            Headers.ContentDisposition.FileName = filename;

        }
        protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
        {
            return _stream.CopyToAsync(stream);
        }

        protected override bool TryComputeLength(out long length)
        {
            length = _stream.Length;
            return true;
        }
    }

Then your controller action reduces to...

IEnumerable<MasterObsTrip> masterTripList = _obsvMasterRepo.GetObsTrips(vesselName, dateYear, port, obsvCode, obsvTripCode, obsvProgCode, lastModifiedDateYear, lastModifiedBy, statusCode);
        IList<MasterObsTripModel> masterTripModelList = new List<MasterObsTripModel>();
        foreach (MasterObsTrip trip in masterTripList)
            masterTripModelList.Add(new MasterObsTripModel(trip));

        CsvFileDescription outputFileDescription = new CsvFileDescription
        {
            SeparatorChar = ',', // comma delimited
            FirstLineHasColumnNames = true, // no column names in first record
            FileCultureName = "nl-NL" // use formats used in The Netherlands
        };
        HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK) {
           Content = new CsvContent<MasterObsTripModel> (outputFileDescription, 
                                                      "ObserverTripList.csv", 
                                                       masterTripModelList);
        }
        return response;

This could be simplified further by including the creation of the CsvFileDescription inside the CsvContent class.

Sign up to request clarification or add additional context in comments.

2 Comments

There are discussions such as this - stackoverflow.com/questions/15334227/… which suggest using a MediaTypeFormatter for serialzing (and de-serializing if needed) the return from an ApiController. Just curious, how would you compare that with your approach where (i guess) the controller return type is a Csv stream?
@mishrsud I return HttpResponseMessage with the Content property set to the CSVContent instance. This allows me access to the response header messages.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.