Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Returning CSV from .NET Core controller

I'm having trouble having a .NET Core API Controller endpoint resolve to a CSV download. I'm using the following code which I pulled from a .NET 4.5 controller:

[HttpGet] [Route("{id:int}")] public async Task<HttpResponseMessage> Get(int id) {     string csv = await reportManager.GetReport(CustomerId, id);     var response = new HttpResponseMessage(HttpStatusCode.OK);     response.Content = new StringContent(csv);     response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/csv");     response.Content.Headers.ContentDisposition =          new ContentDispositionHeaderValue("attachment") { FileName = "report.csv" };     return response; } 

When I hit this endpoint from my Angular 4 app, I get the following response written to the browser:

{     "version": {         "major": 1,         "minor": 1,         "build": -1,         "revision": -1,         "majorRevision": -1,         "minorRevision": -1     },     "content": {         "headers": [             {                 "key": "Content-Type",                 "value": [                     "text/csv"                 ]             },             {                 "key": "Content-Disposition",                 "value": [                     "attachment; filename=11.csv"                 ]             }         ]     },     "statusCode": 200,     "reasonPhrase": "OK",     "headers": [ ],     "requestMessage": null,     "isSuccessStatusCode": true } 

My expectation is that when I hit the endpoint, the user will be prompted to download the CSV.

I found this post on how to "export" a CSV in .NET Core. The problem is that I'm retrieving the CSV in-memory from its source (an AWS S3 bucket) whereas this code seems to only work when you have an IEnumerable<object>.

I'm wondering if my problem lies in either request or response headers, where there is something preventing the browser from retrieving a CSV from my API. Here is what I see in the browser console:

enter image description here

like image 686
im1dermike Avatar asked Oct 27 '17 13:10

im1dermike


People also ask

How do I return a CSV file in C#?

Using new streamwriter to return CSV files The CSV headers (the names of the object properties, e.g. FirstName, LastName) are written on the first line. The object is then looped through and each property's value on the object are written, comma delimited.

How do I export a CSV file in MVC?

When the Export Button is clicked, the data from Database is fetched using Entity Framework and a Comma Separated (Delimited) string is generated which is exported and downloaded as CSV file in ASP.Net MVC Razor. Here I am making use of Microsoft's Northwind Database. You can download it from here.

What is CSV file in asp net c#?

CSV file is a computer file that contains Comma Separated (Comma Delimited) Values. The information from a CSV file is browsed and then once the values are separated, a DataTable is created which is able to populate the ASP.NET GridView control.


2 Answers

Solution: Use FileResult

This should be used if you want the client to get the "Save File" dialog box.

There are a variety to choose from here, such as FileContentResult, FileStreamResult, VirtualFileResult, PhysicalFileResult; but they all derive from FileResult - so we will go with that one for this example.

public async Task<FileResult> Download() {     string fileName = "foo.csv";     byte[] fileBytes = ... ;      return File(fileBytes, "text/csv", fileName); // this is the key! } 

The above will also work if you use public async Task<IActionResult> if you prefer using that instead.

The key is that you return a File type.

Extra: Content-Disposition

The FileResult will automatically provide the proper Content-Disposition header to attachment.

If you want to open the file in the browser ("inline"), instead of prompting the "Save File" dialog ("attachment"). Then you can do that by changing the Content-Disposition header value.

Take for example, we want to show the PDF file in the browser.

public IActionResult Index() {     byte[] contents = FetchPdfBytes();     Response.AddHeader("Content-Disposition", "inline; filename=test.pdf");     return File(contents, "application/pdf"); } 

Credit to this SO Answer


Custom Formatters

Custom formatters are a great choice in general, because they allow the client to ask for the type they want the data as, such as the more popular JSON or the less popular XML.

This primarily works by serving the content as specified in the Accept header that the client passes to the server, such as CSV, XLS, XML, JSON, etc.

You want to use a format type of "text/csv" but there is no predefined formatter for this, so you will have to manually add it to the input and output formatter collections:

services.AddMvc(options => {     options.InputFormatters.Insert(0, new MyCustomInputFormatter());     options.OutputFormatters.Insert(0, new MyCustomOutputFormatter()); }); 

Very Simple Custom Formatter

Here's a very simple version of a custom formatter, which is a stripped-down version that was provided with the Microsoft Docs example.

public class CsvOutputFormatter : TextOutputFormatter {     public CsvOutputFormatter()     {         SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/csv"));         SupportedEncodings.Add(Encoding.UTF8);         SupportedEncodings.Add(Encoding.Unicode);     }      protected override bool CanWriteType(Type type)     {         return true; // you could be fancy here but this gets the job done.     }      public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)     {         var response = context.HttpContext.Response;                  // your magic goes here         string foo = "Hello World!";          return response.WriteAsync(foo);     } } 

Forcing a Particular Format

// force all actions in the controller [Produces("text/csv")] public class FooController {     // or apply on to a single action     [Produces("text/csv")]     public async Task<IActionResult> Index()     {     } }   

For more information, I would recommend that you read:

  • Introduction to formatting response data in ASP.NET Core MVC
  • Custom formatters in ASP.NET Core MVC
like image 131
Svek Avatar answered Sep 17 '22 21:09

Svek


Newcomers to this question please see Svek's answer. The original question is concerning http Content-Disposition, but it looks like search engines send generic .net core csv queries here. Svek's answer provides a good overview of the tools available to .Net Core for returning CSV data from a controller.


The proper way to force a file to be downloaded instead of displayed inline is using the Content-Disposition response header. While the below solution works (see documentation) it's been pointed out that this can have unintended side effects.


Old Answer

Setting the Content-Type response header to application/octet-stream will force most major browsers to prompt the user to save the file instead of displaying it in the window.

Try doing something like this:

var result = new FileContentResult(myCsvByteArray, "application/octet-stream"); result.FileDownloadName = "my-csv-file.csv"; return result; 

See my answer to this similar question for more info

like image 43
Neil Avatar answered Sep 17 '22 21:09

Neil