223

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.

Nkosi
  • 235,767
  • 35
  • 427
  • 472
Jan Kruse
  • 2,615
  • 2
  • 12
  • 17
  • For .NET 6 you can check my answer [here](https://stackoverflow.com/a/75832332/16877062). – rust4 Apr 04 '23 at 08:13

7 Answers7

352

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.

Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • 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. – Kurtis Jungersen Jan 21 '19 at 22:32
  • I understand what the `NotFound()` ultimately does, but does it reside in .NET Core or is it something local to your project? – ΩmegaMan Mar 30 '19 at 18:15
  • 3
    @ΩmegaMan it is a helper method on `ControllerBase` and is part of the framework itself https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.controllerbase.notfound?view=aspnetcore-2.2 – Nkosi Mar 30 '19 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 Mar 30 '19 at 18:57
  • 1
    @Nkosi any way to transfer obtain file from url and stream it to client in chunks as we get from source? https://stackoverflow.com/questions/62476452/download-large-file-from-url-in-net-core-web-api – user1066231 Jun 19 '20 at 20:20
  • 1
    @user1066231 have a look at this https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-2.0?view=aspnetcore-3.1#enhanced-http-header-support focus on range header – Nkosi Jun 19 '20 at 21:11
  • Do you not need a using statement here? – Rob L Nov 16 '20 at 15:58
  • 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 Nov 16 '20 at 16:40
  • 2
    @RobL check the source code that actually writes the stream to the response. Note the `using` statement applied to the passed in stream https://source.dot.net/#Microsoft.AspNetCore.Mvc.Core/Infrastructure/FileResultExecutorBase.cs,398 – Nkosi Nov 16 '20 at 16:48
  • 1
    Won't even compile because it can't convert the File(...) to Task. – CoderSteve Nov 24 '20 at 14:38
  • 1
    @CoderSteve did you await anything in the action? – Nkosi Nov 24 '20 at 14:45
  • @CoderSteve if you look at the [source code](https://source.dot.net/#Microsoft.AspNetCore.Mvc.Core/ControllerBase.cs,1104) You will see that `File` does return an `IActionResult` derived object. – Nkosi Nov 24 '20 at 14:47
  • 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. – Martin Schneider Dec 12 '20 at 14:28
  • 3
    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. – Kevin Brydon Jan 14 '21 at 13:55
  • 1
    @KevinBrydon I am glad it helped. Happy Coding. – Nkosi Jan 14 '21 at 14:00
  • 1
    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 – Vaibhav Deshmukh Dec 16 '21 at 12:41
  • 2
    @KurtisJungersen You do not know how many lives you are saving with your comment!! I get it working for me only after giving the file name. Thanks to you! – Pawan Nogariya Jan 06 '23 at 11:40
53

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
        };
    }
Hamed Naeemaei
  • 8,052
  • 3
  • 37
  • 46
  • 4
    If within a `ControllerBase` there are many overloaded versions of [`ControllerBase.File`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.controllerbase.file?view=aspnetcore-2.2) helper that returns any one of those. – Nkosi Oct 26 '19 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 Oct 26 '19 at 22:46
  • 1
    Yes this is true. – Hamed Naeemaei Feb 21 '20 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. – Jawand Singh Jul 16 '22 at 18:38
36

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.

gpresland
  • 1,690
  • 2
  • 20
  • 31
8

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

}
Tanvir
  • 111
  • 1
  • 3
2

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.

Adeel Ahmed
  • 147
  • 9
2

FileStreamResult works for me. and File is not an IActionResult. I don't know how it can work.

yww325
  • 315
  • 4
  • 12
0

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