241

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. Commented Apr 4, 2023 at 8:13

7 Answers 7

380

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.

Sign up to request clarification or add additional context in comments.

17 Comments

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 it is a helper method on ControllerBase and is part of the framework itself learn.microsoft.com/en-us/dotnet/api/…
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
@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.
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.
|
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 Comments

If within a ControllerBase there are many overloaded versions of ControllerBase.File helper that returns any one of those.
Your answer is still valid. So do not feel disheartened. I was just pointing out some resources you can use to support your answer.
Yes this is true.
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.
43

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.

Comments

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 Comments

why would you pass a filepath from the frontend to the backend
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.
you would pass a name not a path: path to upload, name or id to download
yeah, Id is the recommended field to pass. That code wasnt refactored.
Thanks. I know some questions accepted, but i use .net 7.0 only work with your answer.
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.

Comments

3

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

Comments

0

For anyone wondering how it is being done with Results<> instead of IActionResult.

public async Task<Results<FileStreamHttpResult, NotFound>> Download(string id)
{
    Stream stream = await GetMyStreamAsync();

    if(stream == null)
        return TypedResults.NotFound();

    return TypedResults.File(stream, contentType: "application/octet-stream", fileDownloadName: "filename.ext");
}    

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.