Compartir a través de


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:

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 downloadFileFromStreamJS:

  • Lee la secuencia proporcionada en ArrayBuffer.
  • Crea un Blob para ajustar ArrayBuffer.
  • 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 a URL.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 downloadFileFromStreamJS para aceptar los datos en el cliente.

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 triggerFileDownloadJS:

  • 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:

Recursos adicionales