Return file in ASP.Net Core Web API

250,052

Solution 1

If this is ASP.net-Core then you are mixing web API versions. Have the action return a derived IActionResult because in your current code the framework is treating HttpResponseMessage as a model.

[Route("api/[controller]")]
public class DownloadController : Controller {
    //GET api/download/12345abc
    [HttpGet("{id}")]
    public async Task<IActionResult> Download(string id) {
        Stream stream = await {{__get_stream_based_on_id_here__}}

        if(stream == null)
            return NotFound(); // returns a NotFoundResult with Status404NotFound response.

        return File(stream, "application/octet-stream"); // returns a FileStreamResult
    }    
}

Note:

The framework will dispose of the stream used in this case when the response is completed. If a using statement is used, the stream will be disposed before the response has been sent and result in an exception or corrupt response.

Solution 2

You can return FileResult with this methods:

1: Return FileStreamResult

    [HttpGet("get-file-stream/{id}"]
    public async Task<FileStreamResult> DownloadAsync(string id)
    {
        var fileName="myfileName.txt";
        var mimeType="application/...."; 
        Stream stream = await GetFileStreamById(id);

        return new FileStreamResult(stream, mimeType)
        {
            FileDownloadName = fileName
        };
    }

2: Return FileContentResult

    [HttpGet("get-file-content/{id}"]
    public async Task<FileContentResult> DownloadAsync(string id)
    {
        var fileName="myfileName.txt";
        var mimeType="application/...."; 
        byte[] fileBytes = await GetFileBytesById(id);

        return new FileContentResult(fileBytes, mimeType)
        {
            FileDownloadName = fileName
        };
    }

Solution 3

Here is a simplistic example of streaming a file:

using System.IO;
using Microsoft.AspNetCore.Mvc;
[HttpGet("{id}")]
public async Task<FileStreamResult> Download(int id)
{
    var path = "<Get the file path using the ID>";
    var stream = File.OpenRead(path);
    return new FileStreamResult(stream, "application/octet-stream");
}

Note:

Be sure to use FileStreamResult from Microsoft.AspNetCore.Mvc and not from System.Web.Mvc.

Solution 4

ASP.NET 5 WEB API & Angular 12

You can return a FileContentResult object (Blob) from the server. It'll not get automatically downloaded. You may create an anchor tag in your front-end app programmatically and set the href property to an object URL created from the Blob by the method below. Now clicking on the anchor will download the file. You can set a file name by setting the 'download' attribute to the anchor as well.

downloadFile(path: string): Observable<any> {
        return this._httpClient.post(`${environment.ApiRoot}/accountVerification/downloadFile`, { path: path }, {
            observe: 'response',
            responseType: 'blob'
        });
    }

saveFile(path: string, fileName: string): void {
            this._accountApprovalsService.downloadFile(path).pipe(
                take(1)
            ).subscribe((resp) => {
                let downloadLink = document.createElement('a');
                downloadLink.href = window.URL.createObjectURL(resp.body);
                downloadLink.setAttribute('download', fileName);
                document.body.appendChild(downloadLink);
                downloadLink.click();
                downloadLink.remove();
            });
            
        }

Backend

[HttpPost]
[Authorize(Roles = "SystemAdmin, SystemUser")]
public async Task<IActionResult> DownloadFile(FilePath model)
{
    if (ModelState.IsValid)
    {
        try
        {
            var fileName = System.IO.Path.GetFileName(model.Path);
            var content = await System.IO.File.ReadAllBytesAsync(model.Path);
            new FileExtensionContentTypeProvider()
                .TryGetContentType(fileName, out string contentType);
            return File(content, contentType, fileName);
        }
        catch
        {
            return BadRequest();
        }
    }

    return BadRequest();

}
Share:
250,052

Related videos on Youtube

Jan Kruse
Author by

Jan Kruse

Updated on July 08, 2022

Comments

  • Jan Kruse
    Jan Kruse almost 2 years

    Problem

    I want to return a file in my ASP.Net Web API Controller, but all my approaches return the HttpResponseMessage as JSON.

    Code so far

    public async Task<HttpResponseMessage> DownloadAsync(string id)
    {
        var response = new HttpResponseMessage(HttpStatusCode.OK);
        response.Content = new StreamContent({{__insert_stream_here__}});
        response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
        return response;
    }
    

    When I call this endpoint in my browser, the Web API returns the HttpResponseMessage as JSON with the HTTP Content Header set to application/json.

  • Kurtis Jungersen
    Kurtis Jungersen over 5 years
    In my case I needed to render an Excel in memory and return it for download, so I needed to define a file name with extension as well: return File(stream, "application/octet-stream", "filename.xlsx"); This way when it downloads the user can open it directly.
  • ΩmegaMan
    ΩmegaMan about 5 years
    I understand what the NotFound() ultimately does, but does it reside in .NET Core or is it something local to your project?
  • Nkosi
    Nkosi about 5 years
    @ΩmegaMan it is a helper method on ControllerBase and is part of the framework itself docs.microsoft.com/en-us/dotnet/api/…
  • ΩmegaMan
    ΩmegaMan about 5 years
    Ok, found my issue, though my controller was working in .NET Core 2.2, it was not derived from the base class Controller an thus didn't have access to the ControllerBase.NotFound() method. Once derived, it all worked. lol / thx
  • Nkosi
    Nkosi over 4 years
    If within a ControllerBase there are many overloaded versions of ControllerBase.File helper that returns any one of those.
  • Nkosi
    Nkosi over 4 years
    Your answer is still valid. So do not feel disheartened. I was just pointing out some resources you can use to support your answer.
  • Hamed Naeemaei
    Hamed Naeemaei about 4 years
    Yes this is true.
  • user1066231
    user1066231 almost 4 years
    @Nkosi any way to transfer obtain file from url and stream it to client in chunks as we get from source? stackoverflow.com/questions/62476452/…
  • Nkosi
    Nkosi almost 4 years
    @user1066231 have a look at this docs.microsoft.com/en-us/aspnet/core/release-notes/… focus on range header
  • Rob L
    Rob L over 3 years
    Do you not need a using statement here?
  • Nkosi
    Nkosi over 3 years
    @RobL not in this case. the framework will dispose of the stream when the response is completed. If you use a using statement the stream will be disposed before the response has been sent.
  • Nkosi
    Nkosi over 3 years
    @RobL check the source code that actually writes the stream to the response. Note the using statement applied to the passed in stream source.dot.net/#Microsoft.AspNetCore.Mvc.Core/Infrastructure‌​/…
  • CoderSteve
    CoderSteve over 3 years
    Won't even compile because it can't convert the File(...) to Task<IActionResult>.
  • Nkosi
    Nkosi over 3 years
    @CoderSteve did you await anything in the action?
  • Nkosi
    Nkosi over 3 years
    @CoderSteve if you look at the source code You will see that File does return an IActionResult derived object.
  • Martin Schneider
    Martin Schneider over 3 years
    The magic behind __get_stream_based_on_id_here__ could be interesting since common functions that return a Stream of a file are not async whereas functions that are async are only returning a byte array etc. Ofc, I could create another Stream from the byte array but I was wondering if there is a solution with one Stream only.
  • Kevin Brydon
    Kevin Brydon over 3 years
    Thank you @Nkosi, your comment saved my sanity! I was getting a 500 server error when returning a File(...). I had the memoryStream incorrectly wrapped in a using statement.
  • Nkosi
    Nkosi over 3 years
    @KevinBrydon I am glad it helped. Happy Coding.
  • Vaibhav Deshmukh
    Vaibhav Deshmukh over 2 years
    If someone trying in postman then you can also convert from stream to byte[] and then return byte[] in the return File(byte[]., mimetype) parameter
  • Mark Homer
    Mark Homer about 2 years
    why would you pass a filepath from the frontend to the backend
  • Tanvir
    Tanvir about 2 years
    Assume there is a page that lists uploaded user documents by file name, each list item(document) has a download button, the backend is WEB API.
  • Mark Homer
    Mark Homer about 2 years
    you would pass a name not a path: path to upload, name or id to download
  • Tanvir
    Tanvir about 2 years
    yeah, Id is the recommended field to pass. That code wasnt refactored.