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 .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 .NET 8 de este artículo.

En este artículo se explica cómo descargar archivos en aplicaciones Blazor.

Descargas de archivos

Los archivos se pueden descargar desde los propios recursos estáticos de la aplicación o desde cualquier otra ubicación:

  • Las aplicaciones de ASP.NET Core usan middleware de archivos estáticos para servir archivos a clientes de aplicaciones de lado servidor.
  • Las instrucciones de este artículo también se aplican a otros tipos de servidores de archivos que no usan .NET, como Content Delivery Networks (CDN).

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 atacantes pueden ejecutar ataques por denegación de servicio, 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).

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.
  • Desencadene 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, consulte 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

<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()
{
    return 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, evite el literal de cadena (@) y use cualquiera de los enfoques siguientes:

  • Use barras diagonales inversas de escape (\\) y comillas (\").
  • Use 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.

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. Puede organizar los archivos descargables en cualquier disposición de carpetas dentro de la raíz web (carpeta wwwroot) que prefiera, 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/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/)

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.
  • Desencadene 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, consulte 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, configure el 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).

Cambie el puerto del ejemplo siguiente para que coincida con el puerto de desarrollo localhost del entorno.

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

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