231

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.

1
  • For .NET 6 you can check my answer here.
    – rust4
    Commented Apr 4, 2023 at 8:13

6 Answers 6

369

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", "{{filename.ext}}"); // 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.

17
  • 34
    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. Commented Jan 21, 2019 at 22:32
  • 3
    @ΩmegaMan it is a helper method on ControllerBase and is part of the framework itself learn.microsoft.com/en-us/dotnet/api/…
    – Nkosi
    Commented Mar 30, 2019 at 18:18
  • 5
    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
    – ΩmegaMan
    Commented Mar 30, 2019 at 18:57
  • 9
    @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
    Commented Nov 16, 2020 at 16:40
  • 4
    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. Commented Dec 12, 2020 at 14:28
58

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
        };
    }
4
  • 5
    If within a ControllerBase there are many overloaded versions of ControllerBase.File helper that returns any one of those.
    – Nkosi
    Commented Oct 26, 2019 at 22:44
  • 2
    Your answer is still valid. So do not feel disheartened. I was just pointing out some resources you can use to support your answer.
    – Nkosi
    Commented Oct 26, 2019 at 22:46
  • 1
    Yes this is true. Commented Feb 21, 2020 at 13:13
  • 1
    using FileStreamResult will help you keep the server memory consumption under control. Since you are not bringing the entire large file in memory, rather streaming it. Commented Jul 16, 2022 at 18:38
41

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.

9

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();

}
5
  • why would you pass a filepath from the frontend to the backend
    – Mark Homer
    Commented Feb 18, 2022 at 13:33
  • 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.
    – Tanvir
    Commented Feb 18, 2022 at 15:34
  • you would pass a name not a path: path to upload, name or id to download
    – Mark Homer
    Commented Mar 15, 2022 at 13:27
  • yeah, Id is the recommended field to pass. That code wasnt refactored.
    – Tanvir
    Commented Apr 2, 2022 at 15:57
  • Thanks. I know some questions accepted, but i use .net 7.0 only work with your answer.
    – thuleduy
    Commented Mar 24, 2023 at 17:36
3

Following is the basic example of returning file (e.g Image file) in .NET Core Web API:

<img src="@Url.Action("RenderImage", new { id = id})" alt="No Image found" />

Below is the code for returning File from controller to view. Following is Action method which will return file:

    [Route("api/[controller]")]
    public class DownloadController : Controller
    {
        //GET api/download/123
        [HttpGet]
        public async Task<IActionResult> RenderImage(string userId)
        {
            //get Image file using _fileservice from db
            var result = await _fileService.getFile(userId);

            if (result.byteStream == null)
                return NotFound();

            return File(result.byteStream, result.ContentType, result.FileName);
        }
    }

Note:

Our file should be first converted into byte[] and then saved in database as varbinary(max) in order to retrieve.

2

add builder.Services.AddSingleton(); in Program.cs

    [HttpGet("{fileId}")]
    public ActionResult GetFile(string fileId)
    {
        string pathToFile = "test.rar";
        if (!System.IO.File.Exists(pathToFile))
        {
            return NotFound();
        }

        if(!_fileExtensionContentTypeProvider.TryGetContentType(pathToFile,
            out var ContentType))
        {
            ContentType = "application/octet-stream";
        }
        var byets=System.IO.File.ReadAllBytes(pathToFile);
        return File(byets, ContentType, Path.GetFileName(pathToFile));
    }
}

Not the answer you're looking for? Browse other questions tagged or ask your own question.