Julian Wen Hsi Lee
Julian Wen Hsi Lee

Reputation: 206

ServiceStack's HttpResult class does not properly format CSV files

I am trying to output a CSV file using an endpoint on a service in ServiceStack, using the HttpResult class.

The CSV string itself is being constructed via StringWriter and CsvHelper.

If the content type is set to "text/plain", the text appears on the browser screen fine when the endpoint URL is hit. However, if it is set to "text/csv", a CSV file is generated, but the information inside it is not correct.

For example:

Expected output:

Header 1, Header 2, Header 3

Actual output:

H,e,a,d,e,r, ,1,,, ,H,e,a,d,e,r, ,2,,, ,H,e,a,d,e,r, 3,"

Is there something I'm possibly missing?

Also, on a side note, how do I set the file name for the file itself? It appears I have to use HttpHeaders.ContentDisposition, but when I tried to set it I got an error along the lines of having multiple header elements.

EDIT: Sorry forgot to include code snippet.

string response = string.Empty;

using (var writer = new StringWriter())
{
    using (var csv = new CsvWriter(writer))
    {
        csv.WriteHeader<TestClass>();
        foreach (var element in elements)
        {
            csv.WriteField(elements.header1);
            csv.WriteField(elements.header2);
            csv.WriteField(elements.header3);
            csv.NextRecord();
        }
    }

    //apparently double quotes can cause the rendered CSV to go wrong in some parts, so I added this as part of trying
    response = writer.ToString().Replace("\"", "");
}

return new HttpResult(response)
{
    StatusCode = HttpStatusCode.OK,
    ContentType = "test/csv"
};

And the info on TestClass:

public class TestClass
{
    public string Header1 { get; set; }
    public string Header2 { get; set; }
    public string Header3 { get; set; }
}

Upvotes: 0

Views: 194

Answers (1)

mythz
mythz

Reputation: 143319

From your description your HttpResult File Response may be serialized by the built-in CSV Format.

If you're not using it you can remove it with:

Plugins.RemoveAll(x => x is CsvFormat);

Otherwise if you are using it, you can circumvent its serialization by writing the CSV file in your Services implementation, e.g:

public class MyCsv : IReturn<string> {}

public async Task Any(MyCsv request)
{
    var file = base.VirtualFileSources.GetFile("path/to/my.csv");
    if (file == null)
        throw HttpError.NotFound("no csv here");

    Response.ContentType = MimeTypes.Csv;
    Response.AddHeader(HttpHeaders.ContentDisposition, 
        $"attachment; filename=\"{file.Name}\";");
    using (var stream = file.OpenRead())
    {
        await stream.CopyToAsync(Response.OutputStream);
        await Response.OutputStream.FlushAsync();
        Response.EndRequest(skipHeaders:true);
    }
}        

Edit since you're returning a raw CSV string you can write it to the response with:

Response.ContentType = MimeTypes.Csv;
await Response.WriteAsync(response);
Response.EndRequest(skipHeaders:true);

Upvotes: 1

Related Questions