在ASP.Net Core Web API中返回文件

227

问题

我想在ASP.Net Web API控制器中返回文件,但我的所有方法都将HttpResponseMessage返回为JSON格式。

目前的代码

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

当我在浏览器中调用此端点时,Web API 将 HttpResponseMessage 作为 JSON 返回,并将 HTTP Content Header 设置为 application/json


对于.NET 6,您可以在此处查看我的答案:https://stackoverflow.com/a/75832332/16877062。 - rust4
7个回答

354
如果这是ASP.net-Core,那么您正在混合Web API版本。让操作返回派生的 IActionResult ,因为在您当前的代码中,框架将 HttpResponseMessage 视为模型。
[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
    }    
}

注意:

在这种情况下,框架会在响应完成时处理使用的流。如果使用了 using 语句,则该流将在响应发送之前被处理并导致异常或损坏的响应。


34
在我的情况下,我需要将 Excel 渲染到内存中并返回以供下载,因此我需要定义一个带有扩展名的文件名:return File(stream, "application/octet-stream", "filename.xlsx");这样下载时用户可以直接打开。 - Kurtis Jungersen
3
@ΩmegaMan 这是 ControllerBase 上的一个帮助方法,它是框架本身的一部分。https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.controllerbase.notfound?view=aspnetcore-2.2 - Nkosi
5
好的,我发现了我的问题,虽然我的控制器在.NET Core 2.2中工作,但它没有从基类 Controller 派生,因此无法访问 ControllerBase.NotFound() 方法。一旦派生,一切都正常了。笑 / 谢谢 - ΩmegaMan
9
在这种情况下不需要@RobL,当响应完成时,框架将处理流的清除。如果使用using语句,则在响应发送之前流将被清除。 - Nkosi
4
__get_stream_based_on_id_here__后面的魔法可能很有趣,因为通常返回文件流的常见函数不是异步的,而异步函数只返回字节数组等。当然,可以从字节数组创建另一个流,但我想知道是否有仅使用一个流的解决方案。 - Martin Schneider
显示剩余12条评论

54

您可以使用以下方法返回FileResult:

1:返回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:返回 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
如果在一个ControllerBase中有许多重载版本的ControllerBase.File帮助程序,则返回其中任何一个。 - Nkosi
2
你的答案仍然有效,所以不要感到沮丧。我只是在指出一些资源,可以支持你的回答。 - Nkosi
1
是的,这是真的。 - Hamed Naeemaei
1
使用 FileStreamResult 可以帮助你控制服务器内存消耗。因为它不会将整个大文件加载到内存中,而是进行流式传输。 - Jawand Singh

36

这里是一个简单的流式传输文件的例子:

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

注意:

请务必使用来自Microsoft.AspNetCore.Mvc 而非 System.Web.MvcFileStreamResult


8

ASP.NET 5 WEB API & Angular 12

你可以从服务器返回一个FileContentResult对象(Blob),它不会自动下载。你可以在前端应用程序中编程创建一个锚标签,并将其href属性设置为下面方法创建的Blob的对象URL。现在点击该锚点将会下载文件。你也可以通过将“download”属性设置为锚点来设置文件名。

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

后端

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

}

为什么要从前端传递文件路径到后端? - Mark Homer
假设有一个页面,按文件名列出了上传的用户文档,每个列表项(文档)都有一个下载按钮,后端是WEB API。 - Tanvir
你需要传递一个名称而不是路径:上传的路径,下载的名称或ID。 - Mark Homer
是的,ID是推荐传递的字段。那段代码没有重构过。 - Tanvir
谢谢。我知道有些问题已经被接受,但是我只使用 .net 7.0,只能使用你的答案。 - Lê Duy Thứ

2
以下是在.NET Core Web API中返回文件(例如图片文件)的基本示例:
<img src="@Url.Action("RenderImage", new { id = id})" alt="No Image found" />

以下是从控制器返回文件的代码。以下是返回文件的Action方法:
```csharp public ActionResult DownloadFile() { //获取文件路径 string filePath = Server.MapPath("~/Content/Sample.pdf");
//返回文件 return File(filePath, "application/pdf", "Sample.pdf"); } ```
以上代码将会把位于~/Content/Sample.pdf的文件以"application/pdf"的格式返回给用户,文件名为"Sample.pdf"。
    [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);
        }
    }

注意:
我们的文件应该首先被转换为byte[],然后作为varbinary(max)保存在数据库中以便检索。

2

FileStreamResult适用于我,而File不是IActionResult。我不知道它怎么能起作用。


1

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接