Cargas de archivos de Blazor de ASP.NET Core
Nota
Esta no es la versión más reciente de este artículo. Para la versión actual, consulte la versión de .NET 9 de este artículo.
Advertencia
Esta versión de ASP.NET Core ya no se admite. Para obtener más información, consulta la Directiva de soporte técnico de .NET y .NET Core. Para la versión actual, consulta la versión .NET 8 de este artículo.
Importante
Esta información hace referencia a un producto en versión preliminar, el cual puede sufrir importantes modificaciones antes de que se publique la versión comercial. Microsoft no proporciona ninguna garantía, expresa o implícita, con respecto a la información proporcionada aquí.
Para la versión actual, consulte la versión de .NET 9 de este artículo.
En este artículo se explica cómo descargar archivos en aplicaciones Blazor.
Descargas de archivos
Este artículo aborda los siguientes escenarios, en los que un explorador no debería abrir un archivo, sino descargarlo y guardarlo en el cliente:
- Transmitir contenido de archivo a un búfer de datos binarios sin procesar en el cliente: normalmente, esta estrategia se usa con archivos relativamente pequeños (< 250 MB).
- Descargar un archivo a través de una dirección URL sin transmisión: normalmente, esta estrategia se usa con archivos relativamente grandes (> 250 MB).
Al descargar archivos de un origen diferente al de la aplicación, se aplican consideraciones de uso compartido de recursos entre orígenes (CORS). Para obtener más información, vea la sección Uso compartido de recursos entre orígenes (CORS).
Consideraciones de seguridad
Tenga precaución al proporcionar a los usuarios la capacidad de descargar archivos desde un servidor. Los ciberdelincuentes pueden ejecutar ataques de denegación de servicio (DoS), ataques de explotación de API o intentar poner en riesgo redes y servidores de otras maneras.
Estos son algunos de los pasos de seguridad con los que se reduce la probabilidad de sufrir ataques:
- Descargue archivos desde un área de descarga de archivos dedicada en el servidor, preferiblemente desde una unidad que no sea del sistema. Usar una ubicación dedicada permite imponer de manera más sencilla restricciones de seguridad en los archivos descargables. Deshabilite los permisos de ejecución en el área de descarga de archivos.
- Las comprobaciones de seguridad del lado cliente son fáciles de eludir por parte de los usuarios malintencionados. También, realice siempre comprobaciones de seguridad del lado cliente en el servidor.
- No reciba archivos de usuarios u otros orígenes que no son de confianza y luego permita que los archivos estén disponibles para su descarga inmediata sin realizar comprobaciones de seguridad de estos. Para obtener más información, vea Cargar archivos en ASP.NET Core.
Descarga desde una secuencia
Esta sección se aplica a los archivos que normalmente tienen un tamaño de hasta 250 MB.
El enfoque recomendado para descargar archivos relativamente pequeños (< 250 MB) es transmitir contenido de archivo a un búfer de datos binarios sin procesar en el cliente con interoperabilidad de JavaScript (JS). Este enfoque es eficaz para los componentes que adoptan un modo de representación interactiva, pero no los componentes que adoptan la representación estática del lado servidor (SSR estático).
El enfoque recomendado para descargar archivos relativamente pequeños (< 250 MB) es transmitir contenido de archivo a un búfer de datos binarios sin procesar en el cliente con interoperabilidad de JavaScript (JS).
Advertencia
El enfoque de esta sección lee el contenido del archivo en un elemento JS ArrayBuffer
. Este enfoque carga todo el archivo en la memoria del cliente, lo que puede afectar al rendimiento. Para descargar archivos relativamente grandes (>= 250 MB), se recomienda seguir las instrucciones de la sección Descarga desde una dirección URL.
La siguiente función downloadFileFromStream
JS:
- Lee la secuencia proporcionada en
ArrayBuffer
. - Crea un
Blob
para ajustarArrayBuffer
. - Crea una dirección URL de objeto que sirve como la dirección de descarga del archivo.
- Crea un
HTMLAnchorElement
(elemento<a>
). - Asigna el nombre del archivo (
fileName
) y la dirección URL (url
) para la descarga. - Desencadena la descarga activando un evento
click
en el elemento delimitador. - Quita el elemento delimitador.
- Revoca la dirección URL del objeto (
url
) mediante una llamada aURL.revokeObjectURL
. Este es un paso importante para asegurarse de que no se pierde memoria en el cliente.
<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>
Nota:
Para obtener una guía general sobre la ubicación de JS y nuestras recomendaciones para aplicaciones de producción, consulta Ubicación de JavaScript en aplicaciones Blazor de ASP.NET Core.
El componente siguiente:
- Se usa la interoperabilidad de streaming de bytes nativa para garantizar una transferencia eficaz del archivo al cliente.
- Hay un método denominado
GetFileStream
para recuperar un elemento Stream para el archivo que se descarga en los clientes. Los enfoques alternativos incluyen recuperar un archivo del almacenamiento o generar un archivo dinámicamente en código de C#. Para esta demostración, la aplicación crea un archivo de 50 KB de datos aleatorios a partir de una nueva matriz de bytes (new byte[]
). Los bytes se encapsulan con una clase MemoryStream para que funcione como el archivo binario generado dinámicamente del ejemplo: - El método
DownloadFileFromStream
realiza las acciones siguientes:- Recupera el elemento Stream de
GetFileStream
. - Especifica un nombre de archivo cuando el archivo se guarda en la máquina del usuario. En el siguiente ejemplo se nombra el archivo
quote.txt
. - Ajusta la clase Stream en DotNetStreamReference, lo que permite el streaming de los datos de archivo al cliente.
- Invoca la función
downloadFileFromStream
JS para aceptar los datos en el cliente.
- Recupera el elemento Stream de
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);
}
}
En el caso de un componente de una aplicación de lado servidor que debe devolver una Stream para un archivo físico, el componente puede llamar a File.OpenRead, como se muestra en el ejemplo siguiente:
private Stream GetFileStream() => File.OpenRead(@"{PATH}");
En el ejemplo anterior, el marcador de posición {PATH}
es la ruta de acceso al archivo. El prefijo @
indica que la cadena es un literal de cadena textual, que permite el uso de barras diagonales inversas (\
) en una ruta de acceso del sistema operativo Windows y comillas dobles incrustadas (""
) para un carácter de comillas simples en la ruta de acceso. Como alternativa, evita el literal de cadena (@
) y usa cualquiera de los enfoques siguientes:
- Usa barras diagonales inversas de escape (
\\
) y comillas (\"
). - Usa barras diagonales (
/
) en la ruta de acceso, que se admiten entre plataformas en aplicaciones ASP.NET Core y comillas de escape (\"
).
Descarga desde una dirección URL
Esta sección se aplica a los archivos que son relativamente grandes, normalmente de 250 MB o más.
El enfoque recomendado para descargar archivos relativamente grandes (>= 250 MB) con componentes o archivos representados interactivamente de cualquier tamaño para los componentes representados estáticamente es usar JS para desencadenar un elemento de anclaje con el nombre y la dirección URL del archivo.
El enfoque recomendado para descargar archivos relativamente grandes (>= 250 MB) es usar JS para desencadenar un elemento de anclaje con el nombre y la dirección URL del archivo.
En el ejemplo de esta sección se usa un archivo de descarga denominado quote.txt
, que se coloca en una carpeta denominada files
en la raíz web de la aplicación (carpeta wwwroot
). El uso de la carpeta files
es solo con fines de demostración. Puedes organizar los archivos descargables en cualquier disposición de carpetas dentro de la raíz web (carpeta wwwroot
) que prefieras, incluida la provisión de los archivos directamente desde la carpeta 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/)
La siguiente función triggerFileDownload
JS:
- Crea un
HTMLAnchorElement
(elemento<a>
). - Asigna el nombre del archivo (
fileName
) y la dirección URL (url
) para la descarga. - Desencadena la descarga activando un evento
click
en el elemento delimitador. - Quita el elemento delimitador.
<script>
window.triggerFileDownload = (fileName, url) => {
const anchorElement = document.createElement('a');
anchorElement.href = url;
anchorElement.download = fileName ?? '';
anchorElement.click();
anchorElement.remove();
}
</script>
Nota:
Para obtener una guía general sobre la ubicación de JS y nuestras recomendaciones para aplicaciones de producción, consulta Ubicación de JavaScript en aplicaciones Blazor de ASP.NET Core.
En el siguiente componente de ejemplo se descarga el archivo desde el mismo origen que usa la aplicación. Si se intenta descargar el archivo desde un origen diferente, configura el uso compartido de recursos entre orígenes (CORS). Para obtener más información, consulta la sección Uso compartido de recursos entre orígenes (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);
}
}
En el caso de los componentes interactivos, el botón del ejemplo anterior llama al controlador DownloadFileFromURL
para invocar la función triggerFileDownload
de JavaScript (JS).
Si el componente adopta la representación estática del lado servidor (SSR estático), agrega un controlador de eventos para el botón (addEventListener
(documentación de MDN)) para llamar triggerFileDownload
a las instrucciones de ASP.NET Core Blazor JavaScript con representación estática del lado servidor (SSR estático).
@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);
}
}
En el caso de los componentes interactivos, el botón del ejemplo anterior llama al controlador DownloadFileFromURL
para invocar la función triggerFileDownload
de JavaScript (JS).
Si el componente adopta la representación estática del lado servidor (SSR estático), agrega un controlador de eventos para el botón (addEventListener
(documentación de MDN)) para llamar triggerFileDownload
a las instrucciones de ASP.NET Core Blazor JavaScript con representación estática del lado servidor (SSR estático).
@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);
}
}
Cambia el puerto del ejemplo anterior para que coincida con el puerto de desarrollo localhost de tu entorno.
@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);
}
}
Cambia el puerto del ejemplo anterior para que coincida con el puerto de desarrollo localhost de tu entorno.
Uso compartido de recursos entre orígenes
Si no se siguen los pasos necesarios para habilitar el uso compartido de recursos entre orígenes (CORS) para los archivos que no tienen el mismo origen que la aplicación, la descarga de archivos no pasará las comprobaciones de CORS realizadas por el explorador.
Para obtener más información sobre CORS con aplicaciones ASP.NET Core, y otros productos y servicios de Microsoft que hospedan archivos para su descarga, vea los siguientes recursos:
- Habilitar solicitudes entre orígenes (CORS) en ASP.NET Core
- Uso de Azure CDN con CORS (documentación de Azure)
- Compatibilidad con el uso compartido de recursos entre orígenes (CORS) para Azure Storage (documentación de REST)
- Cloud Services principales: configuración de CORS para el sitio web y los recursos de almacenamiento (módulo de Learn)
- Referencia de configuración del módulo CORS de IIS (documentación de IIS)
Recursos adicionales
- Archivos estáticos Blazor en ASP.NET Core
- Interoperabilidad de ASP.NET Core Blazor JavaScript (interoperabilidad de JS)
- Ubicación de JavaScript en aplicaciones Blazor de ASP.NET Core
- ASP.NET Core Blazor JavaScript con representación estática del lado servidor (SSR estática)
<a>
: el elemento Anchor: seguridad y privacy (documentación de MDN)- Cargas de archivos de Blazor en ASP.NET Core
- Repositorio de GitHub con ejemplos de Blazor (
dotnet/blazor-samples
) (cómo descargar)