注意
这不是本文的最新版本。 有关当前版本,请参阅 本文的 .NET 10 版本。
警告
此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 .NET Core 支持策略。 有关当前版本,请参阅 本文的 .NET 10 版本。
本文说明了如何在 Blazor 应用中下载文件。
文件下载
本文介绍针对以下场景的方法,即文件不应被浏览器打开,但需要下载并保存在客户端上:
- 将文件内容流式传输到客户端上的原始二进制数据缓冲区:通常,此方法用于相对较小的文件 (< 250 MB)。
- 通过 URL 下载文件而不使用流式传输:通常,此方法用于相对较大的文件 (> 250 MB)。
从不同于应用的来源下载文件时,需要考虑跨域资源共享 (CORS)。 有关详细信息,请参阅跨域资源共享 (CORS) 部分。
安全注意事项
向用户提供从服务器下载文件的功能时,必须格外小心。 网络攻击者可能会执行拒绝服务 (DoS) 攻击、API 利用攻击,或试图以其他方式入侵网络和服务器。
降低成功攻击可能性的安全措施如下:
- 从服务器上专用的文件下载区域下载文件,该区域最好位于非系统驱动器上。 使用专用位置便于对下载的文件实施安全限制。 禁用对文件下载区域的执行权限。
- 客户端安全检查很容易被恶意用户规避。 在服务器上也要始终执行客户端执行的安全检查。
- 不要从用户或其他不受信任的源接收文件,然后在不对文件执行安全检查的情况下,让这些文件可立即下载。 有关详细信息,请参阅在 ASP.NET Core 中上传文件。
从流中下载
本部分适用于通常最大为 250 MB 的文件。
下载相对较小的文件 (< 250 MB) 时,建议使用 JavaScript (JS) 互操作将文件内容流式传输到客户端上的原始二进制数据缓冲区。 此方法对于采用交互式呈现模式的组件有效,但对采用静态服务器端呈现(静态 SSR)的组件无效。
下载相对较小的文件 (< 250 MB) 时,建议使用 JavaScript (JS) 互操作将文件内容流式传输到客户端上的原始二进制数据缓冲区。
警告
本部分采用的方法是将文件内容读取到 JS ArrayBuffer。 此方法会将整个文件加载到客户端的内存中,这可能会损害性能。 若要下载相对较大的文件 (>= 250 MB),建议遵循从 URL 下载部分中的指南执行操作。
以下 downloadFileFromStreamJS 函数:
- 将提供的流读入
ArrayBuffer。 - 创建
Blob以包装ArrayBuffer。 - 创建一个对象 URL,用作文件的下载地址。
- 创建
HTMLAnchorElement(<a>元素)。 - 为下载分配文件名 (
fileName) 和 URL (url)。 - 通过在锚元素上触发
click事件来触发下载。 - 移除锚点元素。
- 通过调用
URL.revokeObjectURL撤销对象 URL (url)。 这是确保客户端上不会发生内存泄漏的重要步骤。
<script>
window.downloadFileFromStream = async (fileName, contentStreamReference) => {
const arrayBuffer = await contentStreamReference.arrayBuffer();
const blob = new Blob([arrayBuffer]);
const url = URL.createObjectURL(blob);
const anchorElement = document.createElement('a');
anchorElement.href = url;
anchorElement.download = fileName ?? '';
anchorElement.click();
anchorElement.remove();
URL.revokeObjectURL(url);
}
</script>
注意
有关 JS 的常规指导以及我们对生产应用的建议,请参阅 ASP.NET Core Blazor 应用中的 JavaScript 位置。
以下组件:
- 使用本机字节-流式处理互操作,确保将文件高效传输到客户端。
- 拥有一个名为
GetFileStream的方法,用于检索下载到客户端的文件的 Stream。 替代方法包括从存储中检索文件或在 C# 代码中动态生成文件。 对于此演示,应用从新字节数组 (new byte[]) 创建一个包含 50 KB 随机数据的文件。 这些字节用 MemoryStream 包装,作为该示例动态生成的二进制文件。 -
DownloadFileFromStream方法:- 从
GetFileStream检索 Stream。 - 在用户计算机上保存文件时指定文件名。 以下示例将该文件命名为
quote.txt。 - 将 Stream 包装在 DotNetStreamReference 中,从而可以将文件数据流式传输到客户端。
- 调用
downloadFileFromStreamJS 函数以在客户端接收数据。
- 从
FileDownload1.razor:
@page "/file-download-1"
@using System.IO
@inject IJSRuntime JS
<PageTitle>File Download 1</PageTitle>
<h1>File Download Example 1</h1>
<button @onclick="DownloadFileFromStream">
Download File From Stream
</button>
@code {
private Stream GetFileStream()
{
var randomBinaryData = new byte[50 * 1024];
var fileStream = new MemoryStream(randomBinaryData);
return fileStream;
}
private async Task DownloadFileFromStream()
{
var fileStream = GetFileStream();
var fileName = "log.bin";
using var streamRef = new DotNetStreamReference(stream: fileStream);
await JS.InvokeVoidAsync("downloadFileFromStream", fileName, streamRef);
}
}
@page "/file-download-1"
@using System.IO
@inject IJSRuntime JS
<PageTitle>File Download 1</PageTitle>
<h1>File Download Example 1</h1>
<button @onclick="DownloadFileFromStream">
Download File From Stream
</button>
@code {
private Stream GetFileStream()
{
var randomBinaryData = new byte[50 * 1024];
var fileStream = new MemoryStream(randomBinaryData);
return fileStream;
}
private async Task DownloadFileFromStream()
{
var fileStream = GetFileStream();
var fileName = "log.bin";
using var streamRef = new DotNetStreamReference(stream: fileStream);
await JS.InvokeVoidAsync("downloadFileFromStream", fileName, streamRef);
}
}
@page "/file-download-1"
@using System.IO
@inject IJSRuntime JS
<h1>File Download Example</h1>
<button @onclick="DownloadFileFromStream">
Download File From Stream
</button>
@code {
private Stream GetFileStream()
{
var randomBinaryData = new byte[50 * 1024];
var fileStream = new MemoryStream(randomBinaryData);
return fileStream;
}
private async Task DownloadFileFromStream()
{
var fileStream = GetFileStream();
var fileName = "log.bin";
using var streamRef = new DotNetStreamReference(stream: fileStream);
await JS.InvokeVoidAsync("downloadFileFromStream", fileName, streamRef);
}
}
@page "/file-download-1"
@using System.IO
@inject IJSRuntime JS
<h1>File Download Example</h1>
<button @onclick="DownloadFileFromStream">
Download File From Stream
</button>
@code {
private Stream GetFileStream()
{
var randomBinaryData = new byte[50 * 1024];
var fileStream = new MemoryStream(randomBinaryData);
return fileStream;
}
private async Task DownloadFileFromStream()
{
var fileStream = GetFileStream();
var fileName = "log.bin";
using var streamRef = new DotNetStreamReference(stream: fileStream);
await JS.InvokeVoidAsync("downloadFileFromStream", fileName, streamRef);
}
}
对于服务器端应用中必须为物理文件返回一个 Stream 的组件,组件可以调用 File.OpenRead,如以下示例所示:
private Stream GetFileStream() => File.OpenRead(@"{PATH}");
在前面的示例中,{PATH} 占位符是文件路径。 前缀 @ 指示该字符串是逐字字符串字面量,它允许在 Windows OS 路径中使用反斜杠 (\),并在路径中使用嵌入的双引号 ("")。 或者,请避免使用字符串字面量 (@) 并使用以下任一方法:
- 使用转义反斜杠 (
\\) 和引号 (\")。 - 在路径中使用正斜杠 (
/)(在 ASP.NET Core 应用中跨平台支持这些斜杠),以及转义引号 (\")。
从 URL 下载
本部分适用于相对较大(通常为 250 MB 或更大)的文件。
对于以交互方式呈现的组件下载相对较大的文件(>= 250 MB),或对于静态呈现组件下载任何大小的文件,建议使用 JS 来触发带有文件名和 URL 的锚点元素。
对于下载相对较大的文件(>= 250 MB),推荐的方法是使用 JS 触发带有文件名和 URL 的锚点元素。
本部分中的示例使用名为 quote.txt 的下载文件,该文件位于应用的 Web 根目录(wwwroot 文件夹)下名为 files 的文件夹中。
files 文件夹的使用仅用于演示目的。 可以在喜欢的 Web 根目录(wwwroot 文件夹)内以任何文件夹布局组织可下载文件,包括直接从 wwwroot 文件夹中提供文件。
wwwroot/files/quote.txt:
When victory is ours, we'll wipe every trace of the Thals and their city from the face of this land. We will avenge the deaths of all Kaleds who've fallen in the cause of right and justice and build a peace which will be a monument to their sacrifice. Our battle cry will be "Total extermination of the Thals!"
- General Ravon (Guy Siner, http://guysiner.com/)
Dr. Who: Genesis of the Daleks (https://www.bbc.co.uk/programmes/p00vd5g2)
Copyright 1975 BBC (https://www.bbc.co.uk/)
When victory is ours, we'll wipe every trace of the Thals and their city from the face of this land. We will avenge the deaths of all Kaleds who've fallen in the cause of right and justice and build a peace which will be a monument to their sacrifice. Our battle cry will be "Total extermination of the Thals!"
- General Ravon (Guy Siner, http://guysiner.com/)
Dr. Who: Genesis of the Daleks (https://www.bbc.co.uk/programmes/p00vd5g2)
Copyright 1975 BBC (https://www.bbc.co.uk/)
When victory is ours, we'll wipe every trace of the Thals and their city from the face of this land. We will avenge the deaths of all Kaleds who've fallen in the cause of right and justice and build a peace which will be a monument to their sacrifice. Our battle cry will be "Total extermination of the Thals!"
- General Ravon (Guy Siner, http://guysiner.com/)
Dr. Who: Genesis of the Daleks (https://www.bbc.co.uk/programmes/p00vd5g2)
Copyright 1975 BBC (https://www.bbc.co.uk/)
When victory is ours, we'll wipe every trace of the Thals and their city from the face of this land. We will avenge the deaths of all Kaleds who've fallen in the cause of right and justice and build a peace which will be a monument to their sacrifice. Our battle cry will be "Total extermination of the Thals!"
- General Ravon (Guy Siner, http://guysiner.com/)
Dr. Who: Genesis of the Daleks (https://www.bbc.co.uk/programmes/p00vd5g2)
Copyright 1975 BBC (https://www.bbc.co.uk/)
以下 triggerFileDownloadJS 函数:
- 创建
HTMLAnchorElement(<a>元素)。 - 为下载分配文件名 (
fileName) 和 URL (url)。 - 通过在锚元素上触发
click事件来触发下载。 - 移除锚点元素。
<script>
window.triggerFileDownload = (fileName, url) => {
const anchorElement = document.createElement('a');
anchorElement.href = url;
anchorElement.download = fileName ?? '';
anchorElement.click();
anchorElement.remove();
}
</script>
注意
有关 JS 位置的常规指导和我们对生产应用的建议,请参阅 ASP.NET Core Blazor 应用中的 JavaScript 位置。
以下示例组件从应用使用的相同源下载文件。 如果尝试从其他源下载文件,请配置跨域资源共享 (CORS)。 有关详细信息,请参阅跨域资源共享 (CORS) 部分。
FileDownload2.razor:
@page "/file-download-2"
@inject IJSRuntime JS
<PageTitle>File Download 2</PageTitle>
<h1>File Download Example 2</h1>
<button @onclick="DownloadFileFromURL">
Download File From URL
</button>
@code {
private async Task DownloadFileFromURL()
{
var fileName = "quote.txt";
var fileURL = "/files/quote.txt";
await JS.InvokeVoidAsync("triggerFileDownload", fileName, fileURL);
}
}
对于交互式组件,前面示例中的按钮调用 DownloadFileFromURL 处理程序以调用 JavaScript (JS) 函数 triggerFileDownload。
如果组件采用静态服务器端呈现(静态 SSR),请为按钮(
@page "/file-download-2"
@inject IJSRuntime JS
<PageTitle>File Download 2</PageTitle>
<h1>File Download Example 2</h1>
<button @onclick="DownloadFileFromURL">
Download File From URL
</button>
@code {
private async Task DownloadFileFromURL()
{
var fileName = "quote.txt";
var fileURL = "/files/quote.txt";
await JS.InvokeVoidAsync("triggerFileDownload", fileName, fileURL);
}
}
对于交互式组件,前面示例中的按钮调用 DownloadFileFromURL 处理程序以调用 JavaScript (JS) 函数 triggerFileDownload。
如果组件采用静态服务器端呈现(静态 SSR),请为按钮(
@page "/file-download-2"
@inject IJSRuntime JS
<h1>File Download Example 2</h1>
<button @onclick="DownloadFileFromURL">
Download File From URL
</button>
@code {
private async Task DownloadFileFromURL()
{
var fileName = "quote.txt";
var fileURL = "https://localhost:5001/files/quote.txt";
await JS.InvokeVoidAsync("triggerFileDownload", fileName, fileURL);
}
}
更改前面示例中的端口,使其与环境中的 localhost 开发端口匹配。
@page "/file-download-2"
@inject IJSRuntime JS
<h1>File Download Example 2</h1>
<button @onclick="DownloadFileFromURL">
Download File From URL
</button>
@code {
private async Task DownloadFileFromURL()
{
var fileName = "quote.txt";
var fileURL = "https://localhost:5001/files/quote.txt";
await JS.InvokeVoidAsync("triggerFileDownload", fileName, fileURL);
}
}
更改前面示例中的端口,使其与环境中的 localhost 开发端口匹配。
跨源资源共享 (CORS)
如果不采取更多步骤为源不同于应用的文件启用跨域资源共享 (CORS),下载文件将无法通过浏览器进行的 CORS 检查。
若要详细了解 ASP.NET Core 应用以及托管文件以供下载的其他 Microsoft 产品和服务的 CORS,请参阅以下资源:
- 在 ASP.NET Core 中启用跨源请求 (CORS)
- 将 Azure CDN 与 CORS 配合使用(Azure 文档)
- Azure 存储的跨域资源共享 (CORS) 支持(REST 文档)
- 核心云服务 - 为网站和存储资产设置 CORS(Learn 模块)
- IIS CORS 模块配置参考(IIS 文档)