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:
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.
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.
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.
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.
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 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()); });
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); } }
// 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:
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With