Cargas de archivos de Blazor de ASP.NET Core

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

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 Blazor Server y aplicaciones hospedadas Blazor WebAssembly.
  • 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).

En este artículo se tratan estrategias para los escenarios siguientes:

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 realiza los pasos siguientes:

  • Lea la secuencia proporcionada en ArrayBuffer.
  • Cree un Blob para ajustar ArrayBuffer.
  • Cree una dirección URL de objeto que sirva como la dirección de descarga del archivo.
  • Cree un objeto HTMLAnchorElement (elemento <a>).
  • Asigne 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.
  • Quite el elemento delimitador.
  • Revoque 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 una guía general sobre la ubicación de JS y nuestras recomendaciones para aplicaciones de producción, consulte Interoperabilidad de ASP.NET Core Blazor JavaScript (Interoperabilidad de JS).

En el ejemplo de 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 los pasos siguientes:
    • Recupere Stream de GetFileStream.
    • Especifique un nombre de archivo cuando el archivo se guarde en la máquina del usuario. En el siguiente ejemplo se nombra el archivo quote.txt.
    • Ajuste la clase Stream en DotNetStreamReference, lo que permite el streaming de los datos de archivo al cliente.
    • Invoque la downloadFileFromStreamJS función para aceptar los datos en el cliente.
@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 Blazor Server 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/)

La siguiente función triggerFileDownloadJS realiza los pasos siguientes:

  • Cree un objeto HTMLAnchorElement (elemento <a>).
  • Asigne 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.
  • Quite 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 una guía general sobre la ubicación de JS y nuestras recomendaciones para aplicaciones de producción, consulte Interoperabilidad de ASP.NET Core Blazor JavaScript (Interoperabilidad de JS).

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).

@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

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 Blazor Server y aplicaciones hospedadas Blazor WebAssembly.
  • 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).

En este artículo se tratan estrategias para los escenarios siguientes:

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 realiza los pasos siguientes:

  • Lea la secuencia proporcionada en ArrayBuffer.
  • Cree un Blob para ajustar ArrayBuffer.
  • Cree una dirección URL de objeto que sirva como la dirección de descarga del archivo.
  • Cree un objeto HTMLAnchorElement (elemento <a>).
  • Asigne 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.
  • Quite el elemento delimitador.
  • Revoque 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 una guía general sobre la ubicación de JS y nuestras recomendaciones para aplicaciones de producción, consulte Interoperabilidad de ASP.NET Core Blazor JavaScript (Interoperabilidad de JS).

En el ejemplo de 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 los pasos siguientes:
    • Recupere Stream de GetFileStream.
    • Especifique un nombre de archivo cuando el archivo se guarde en la máquina del usuario. En el siguiente ejemplo se nombra el archivo quote.txt.
    • Ajuste la clase Stream en DotNetStreamReference, lo que permite el streaming de los datos de archivo al cliente.
    • Invoque la downloadFileFromStreamJS función para aceptar los datos en el cliente.
@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 Blazor Server 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/)

La siguiente función triggerFileDownloadJS realiza los pasos siguientes:

  • Cree un objeto HTMLAnchorElement (elemento <a>).
  • Asigne 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.
  • Quite 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 una guía general sobre la ubicación de JS y nuestras recomendaciones para aplicaciones de producción, consulte Interoperabilidad de ASP.NET Core Blazor JavaScript (Interoperabilidad de JS).

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).

@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