Скачивание файлов ASP.NET Core Blazor

Примечание.

Это не последняя версия этой статьи. В текущем выпуске см . версию .NET 8 этой статьи.

Предупреждение

Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в статье о политике поддержки .NET и .NET Core. В текущем выпуске см . версию .NET 8 этой статьи.

Внимание

Эта информация относится к предварительному выпуску продукта, который может быть существенно изменен до его коммерческого выпуска. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.

В текущем выпуске см . версию .NET 8 этой статьи.

В этой статье объясняется, как скачать файлы в Blazor приложениях.

Скачивание файлов

В этой статье рассматриваются подходы к следующим сценариям, в которых файл не должен быть открыт браузером, а скачан и сохранен на клиенте:

При скачивании файлов из источника, отличного от источника приложения, применяются правила общего доступа к ресурсам независимо от источника (CORS). Дополнительные сведения см. в разделе Общий доступ к ресурсам независимо от источника (CORS).

Вопросы безопасности

Необходимо соблюдать осторожность, если вы предоставляете пользователям возможность скачивать файлы с сервера. Кибератаки могут выполнять атаки типа "отказ в обслуживании" (DoS), атаки на использование API или пытаться скомпрометировать сети и серверы другими способами.

Ниже приведены некоторые действия по обеспечению безопасности, которые снижают вероятность успешных атак.

  • Скачивайте файлы из области, выделенной на сервере специально для скачиваемых файлов, желательно не на системном диске. Использование выделенного расположения упрощает применение мер безопасности к доступным для скачивания файлам. Отключите разрешения на выполнение для расположения отправки файлов.
  • Злоумышленникам легко обойти проверки безопасности на стороне клиента. Всегда выполняйте проверки безопасности на стороне клиента и на сервере.
  • Не получайте файлы от пользователей или других ненадежных источников и не делайте файлы доступными для немедленного скачивания без проверки безопасности файлов. Подробные сведения см. в статье Отправка файлов в ASP.NET Core.

Скачивание из потока

Этот раздел относится к файлам размером до 250 МБ.

Рекомендуемый подход к скачиванию относительно небольших файлов (< 250 МБ) заключается в потоковой передаче содержимого файла в буфер необработанных двоичных данных на клиенте с помощью взаимодействия с JavaScript (JS). Этот подход эффективен для компонентов, которые применяют интерактивный режим отрисовки, но не компоненты, которые принимают статическую отрисовку на стороне сервера (статический SSR).

Рекомендуемый подход к скачиванию относительно небольших файлов (< 250 МБ) заключается в потоковой передаче содержимого файла в буфер необработанных двоичных данных на клиенте с помощью взаимодействия с JavaScript (JS).

Предупреждение

Подход, описанный в этом разделе, позволяет считать содержимое файла в JS ArrayBuffer. При этом весь файл загружается в память клиента, что может снизить производительность. Чтобы скачать относительно большие файлы (>= 250 МБ), рекомендуется выполнить инструкции из раздела Скачивание по URL-адресу.

Следующая функция downloadFileFromStream JS:

  • Чтение предоставленного потока в ArrayBuffer.
  • Создание Blob в качестве оболочки для ArrayBuffer.
  • Создает URL-адрес объекта, который будет служить адресом скачивания файла.
  • HTMLAnchorElement Создает элемент (<a>).
  • Назначает имя файла (fileName) и URL-адрес (url) для скачивания.
  • Активирует скачивание путем запуска click события в элементе привязки.
  • Удаляет элемент привязки.
  • Отменяет URL-адрес объекта (url) путем вызова URL.revokeObjectURL. Это важный шаг, позволяющий избежать утечки памяти на клиенте.
<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 расположению и нашим рекомендациям для рабочих приложений см. в расположении JavaScript в приложениях ASP.NET CoreBlazor.

Приведенный ниже компонент делает следующее.

  • Использует собственный потоковый поток байтов для обеспечения эффективной передачи файла клиенту.
  • Имеет метод GetFileStream для получения файла Stream, скачиваемого на клиенты. Альтернативные подходы включают извлечение файла из хранилища или динамическое создание файла в коде C#. Для нашего демонстрационного примера приложение создает файл случайных данных размером 50 КБ из нового массива байтов (new byte[]). Эти байты упаковываются в MemoryStream, что дает нам пример динамически создаваемого двоичного файла.
  • Метод:DownloadFileFromStream
    • Извлекает Stream из GetFileStream.
    • Указывает имя файла при сохранении файла на компьютере пользователя. В следующем примере файлу присваивается имя 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 и встроенные двойные кавычки ("") для одинарной кавычки в пути. Кроме того, избегайте строкового литерала (@) и используйте любой из следующих подходов:

  • Используйте экранированные обратные косые черты (\\) и кавычки (\").
  • Используйте косые черты (/) в пути, что поддерживаются на разных платформах в приложениях ASP.NET Core, а также экранированные кавычки (\").

Скачивание по URL-адресу

Этот раздел относится к файлам, которые относительно большие, обычно 250 МБ или больше.

Рекомендуемый подход для скачивания относительно больших файлов (>= 250 МБ) с интерактивными компонентами или файлами любого размера для статически отрисованных компонентов — использовать JS для активации элемента привязки с именем и URL-адресом файла.

Рекомендуемый подход к скачиванию относительно больших файлов (>= 250 МБ) заключается в том, чтобы JS активировать элемент привязки с именем файла и URL-адресом.

В примере в этом разделе используется скачиваемый файл quote.txt, который находится в папке files в корневом каталоге веб-приложения (папка wwwroot). Папка files используется только для демонстрации. Вы можете упорядочивать скачиваемые файлы в любом расположении папок внутри корневого веб-сайта (папка 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/)

Следующая функция triggerFileDownload JS:

  • 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 расположению и нашим рекомендациям для рабочих приложений см. в расположении JavaScript в приложениях ASP.NET CoreBlazor.

В следующем примере компонент скачивает файл из источника приложения. Если попытка скачать файл выполняется из другого источника, настройте общий доступ к ресурсам независимо от источника (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 обработчик для вызова функции triggerFileDownloadJavaScript (JS).

Если компонент принимает статическую отрисовку на стороне сервера (статический SSR), добавьте обработчик событий для кнопки (addEventListenerдокументация MDN)), чтобы вызвать triggerFileDownload в ASP.NET Core Blazor JavaScript со статическим отображением на стороне сервера (статический 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 обработчик для вызова функции triggerFileDownloadJavaScript (JS).

Если компонент принимает статическую отрисовку на стороне сервера (статический SSR), добавьте обработчик событий для кнопки (addEventListenerдокументация MDN)), чтобы вызвать triggerFileDownload в ASP.NET Core Blazor JavaScript со статическим отображением на стороне сервера (статический 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, сделанные браузером.

Дополнительные сведения о CORS с приложениями ASP.NET Core и другими продуктами и службами Microsoft, в которых размещаются файлы для загрузки, см. в следующих ресурсах:

Дополнительные ресурсы