ASP.NET Core Blazor 文件下载

注意

此版本不是本文的最新版本。 对于当前版本,请参阅此文的 .NET 8 版本

重要

此信息与预发布产品相关,相应产品在商业发布之前可能会进行重大修改。 Microsoft 对此处提供的信息不提供任何明示或暗示的保证。

对于当前版本,请参阅此文的 .NET 8 版本

本文说明了如何在 Blazor 应用中下载文件。

文件下载

可从应用自己的静态资产或从任何其他位置下载文件:

  • ASP.NET Core 应用使用静态文件中间件向服务器侧应用的客户端提供文件。
  • 本文中的指南也适用于不使用 .NET 的其他类型的文件服务器,例如内容分发网络 (CDN)。

本文介绍针对以下场景的方法,即文件不应被浏览器打开,但需要下载并保存在客户端上:

从不同于应用的来源下载文件时,可以考虑跨域资源共享 (CORS)。 有关详细信息,请参阅跨域资源共享 (CORS) 部分。

安全注意事项

向用户提供从服务器下载文件的功能时,必须格外小心。 攻击者可能会执行拒绝服务 (DOS) 攻击、API 利用攻击,或尝试以其他方式破坏网络和服务器。

降低成功攻击可能性的安全措施如下:

  • 从服务器上的专用文件下载区域下载文件,最好是非系统驱动器。 使用专用位置便于对下载的文件实施安全限制。 禁用对文件下载区域的执行权限。
  • 客户端安全检查很容易被恶意用户规避。 在服务器上也要始终执行客户端安全检查。
  • 不要从用户或其他不受信任的源接收文件,然后在不对文件执行安全检查的情况下,让这些文件可立即下载。 有关详细信息,请参阅在 ASP.NET Core 中上传文件

从流下载

本部分适用于通常最大为 250 MB 的文件。

下载相对较小的文件 (< 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 CoreBlazor 应用中的 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

<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()
{
    return File.OpenRead(@"{PATH}");
}

在前面的示例中,{PATH} 占位符是文件路径。 前缀 @ 指示字符串是逐字字符串字面量,它允许在 Windows OS 路径中使用反斜杠 (\),并在路径中为单个引号使用嵌入的双引号 ("")。 或者,请避免使用字符串字面量 (@) 并使用以下任一方法:

  • 使用转义反斜杠 (\\) 和引号 (\")。
  • 在路径中使用正斜杠 (/)(在 ASP.NET Core 应用中跨平台支持这些斜杠),以及转义引号 (\")。

从 URL 下载

本部分适用于相对较大(通常为 250 MB 或更大)的文件。

本部分中的示例使用名为 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/p00pq2gc)
  ©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/p00pq2gc)
  ©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/p00pq2gc)
  ©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 CoreBlazor 应用中的 JavaScript 位置

以下示例组件从应用使用的相同源下载文件。 如果尝试从其他源下载文件,请配置跨域资源共享 (CORS)。 有关详细信息,请参阅跨域资源共享 (CORS) 部分。

更改以下示例中的端口以匹配实际环境的 localhost 开发端口。

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

跨源资源共享 (CORS)

如果不采取更多步骤为源不同于应用的文件启用跨域资源共享 (CORS),下载文件将无法通过浏览器进行的 CORS 检查。

若要详细了解 ASP.NET Core 应用以及托管文件以供下载的其他 Microsoft 产品和服务的 CORS,请参阅以下资源:

其他资源