1

I have the following method for talking a list of a SomeMetric model and converting it to a csv string. The method write the csv string as a file to HttpResponseMessage:

private HttpResponseMessage ConvertToCsvFileResponse(IEnumerable<SomeMetric> filterRecords, string fileName)
{
    var csvBuilder = new StringBuilder();

    //write the header
    csvBuilder.AppendLine("ColA,ColB,ColC,ColD,ColE");

    // Write the data lines.
    foreach (var record in filterRecords)
        csvBuilder.AppendFormat("{0},{1},{2},{3},{4}{5}", record.A, record.B, record.C, record.D, record.E, Environment.NewLine);

    // Convert to Http response message for outputting from api.
    HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
    result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
    result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment"); //attachment will force download
    result.Content.Headers.ContentDisposition.FileName = fileName;
    result.Content = new StringContent(csvBuilder.ToString());

    return result;
}

This method is called from my API controller and the response is output to the client as follows:

[HttpGet]
public HttpResponseMessage GetExampleCsv()
{
    try
    {
        var entries = _auditTableStorage.ListEntities<SomeMetric>();

        return ConvertToCsvFileResponse(entries.ToList(), "Example.csv");
    }
    catch (Exception ex)
    {
        return new HttpResponseMessage(HttpStatusCode.InternalServerError) {
            Content = new StringContent($"Problem occurred connecting to storage: {ex.Message}")
        };
    }
}

I generate the data by querying Azure Table Storage - but that's kind of irrelevant for my question.

The data comes from a data source in a paginated format. My current approach is to gather all the data together first (paging through until the end) to generate a list (IEnumerable is returned and .ToList(); is called). This data is then passed to the ConvertToCsv method.

As you can imagine, building the dataset up front in this way is Ok for small to medium size data sets BUT quickly becomes inefficient for large datasets.

Ideally, I'd like to modify my code to take chunks of the CSV string as it's being built and stream that down as file to the client making the request - just not sure how to convert what I have into a streamed version. Any points greatly appreciated!

NOTE: The code for generating csv here is only for demonstration purposes. I could integrate an object to CSV parser but wanted to show the string generation and dropping that out as it happens.

4
  • Start by using a library instead of creating it yourself. What would happen if record.B would have a comma in it? Commented Aug 1, 2019 at 11:47
  • This comment is not really useful - I could easily integrate a csv library for parsing.... the point of the question is about streaming the string content to the client in chunks. Commented Aug 1, 2019 at 12:20
  • TRY Epplus Commented Aug 1, 2019 at 15:59
  • Creating an excel sheet/csv file or any other file is not generally what I am interested here - I'm really interested in the streaming content as its being built part of this Commented Aug 1, 2019 at 19:54

1 Answer 1

4

For anyone else looking to get an example of this, here's how I achieved streaming content to the client browser as it was being built:

private HttpResponseMessage ConvertToCsvFileResponse(IEnumerable<SomeMetric> filterRecords, string fileName)
{
    HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
    result.Content = new PushStreamContent((outputStream, httpContext, transportContext) =>
    {
        using (StreamWriter sWriter = new StreamWriter(outputStream, UnicodeEncoding.ASCII))
        {
            sWriter.Write("A,B,C,D,E");

            foreach (var record in filterRecords)
                sWriter.Write($"{Environment.NewLine}{record.A},{record.B},{record.C},{record.D},{record.E}");
        }

    });
    result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
    result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment"); 
    result.Content.Headers.ContentDisposition.FileName = fileName;

    return result;
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.