Using Azure Function (.NET Core) to Download a file
Asked Answered
S

1

12

I have created and HTTP Triggered Azure Function (v2) using .NET Core with the hopes that I can execute the function while passing in some info in the request body and then have the function return/download a file in the browser. Unfortunately I am struggling to get this working.

Below is a snippet of Code

public static async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]HttpRequest req, ILogger log)
{
    string csv;

    //Do some stuff to create a csv

    byte[] filebytes = Encoding.UTF8.GetBytes(csv);

    req.HttpContext.Response.Headers.Add("content-disposition", "attachment;filename=Export.csv");
    req.HttpContext.Response.ContentType = "application/octet-stream";

    return (ActionResult)new OkObjectResult(filebytes);
}

When I do a post using Postman the request is accepted but the response is 406 "unacceptable" and the output in the log states

"Microsoft.AspNetCore.Mvc.Infrastructure.DefaultOutputFormatterSelector[1] No output formatter was found for content type 'application/octet-stream' to write the response."

I've tried multiple content types including text/plain and text/csv, all give the same response about output formatting.

If I remove or comment out the ContentType the request processes and returns a 200 but the filebytes are returned in the response body instead of being downloaded in the browser.

Supple answered 8/10, 2018 at 15:38 Comment(5)
return new FileContentResult(fileBytes, "text/csv") { FileDownloadName = "Export.csv" }; No need for manually changing the Response.Commandant
Your function should not return the file directly. The whole point is to offload the work and handle it in the background. If your action has to wait on the function to create the file, you might as well do it all in the action instead and forgo the additional overhead of an Azure Function. Assuming it's handled in the background, the function should somehow notify your app that it has completed, via a message queue, SignalR, etc. Then, the app can notify the client accordingly and either push the download or provide a link where the client can fetch it.Ketonuria
Yes, definitely at what Chris said. Returning files from from Azure functions can also lead to very unpredictable costs, because the same function may run 1 second for a user with fast internet connection and 30 seconds for someone with a slow connection. Azure functions should be computations mostly or background tasks and if you return files they should be very small (I'd say under 1-2 kb, like a small json response etc.) to avoid your costs skyrocket on many slow connections.Monocyte
When processing files, you should store the result file into azure file or blob storage and make that link available to the caller so they can directly access it from thereMonocyte
Thank you @AndyJ that worked perfectly! As I mention in the answer that was posted, this is for a super small part of a project and the function itself isn't ran often, returns very small amounts of data and runs in a second or two which is why i would disagree with the other solutions posted in the comments. Also, we will be hosting this in an already existing app service and so the cost associated with the consumption model don't apply. Thank you though for your input!Supple
I
32

You'll need a FileContentResult for this:

public static async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]HttpRequest req, ILogger log)
{
    string csv;

    //Do some stuff to create a csv

    byte[] filebytes = Encoding.UTF8.GetBytes(csv);

    return new FileContentResult(filebytes, "application/octet-stream") {
        FileDownloadName = "Export.csv"
    };
}

While the comments correctly point out that the ideal solution is to kick off processing in the HTTP function asynchronously, return a 202 Accepted response, save the result to blob storage, have the client wait for processing to complete before starting the blob download and then delete the blob once it's been downloaded, current Azure Functions pricing is only $0.000016/GB-s so you may find that to be unnecessarily complicated unless you have quite high traffic.

Imidazole answered 8/10, 2018 at 16:48 Comment(3)
Thank you! 1 small line of code has been holding me up! This is for a super small part of a project and the function itself isn't ran often, returns very small amounts of data and runs in a second or two which is why i would disagree with the other solutions posted in the comments. Also, we will be hosting this in an already existing app service and so the cost associated with the consumption model don't apply. Thanks again!Supple
Just in case someone else came here looking for how to return a stream as a file, you can use FileStreamResult: return new FileStreamResult(memoryStream, "application/octet-stream") { FileDownloadName = "Export.csv" }; Furthermore
This doesn't work for .net5. Do you know how to work with?Kassiekassity

© 2022 - 2024 — McMap. All rights reserved.