Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

18 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
martes, 4 de junio de 2024
Blazor

El espacio de nombres System.IO de .NET proporciona clases para trabajar con archivos y directorios, algo que, a priori, no tendría demasiado sentido en componentes Blazor WebAssembly, ya que éstos se ejecutan en el navegador del cliente y, por motivos de seguridad, no tienen acceso al sistema de archivos del servidor, ni tampoco a los archivos del dispositivo del usuario que está ejecutando la aplicación.

El primero de los casos es salvable: si desde un componente Blazor WebAssembly quisiéramos leer o escribir archivos en el servidor, podríamos hacerlo a través de una API que, ejecutada en el servidor, tuviera acceso a este tipo de recursos.

Sin embargo, técnicamente no hay forma de acceder a los archivos locales del usuario, dado que los componentes Blazor WebAssembly se ejecutan en el sandbox del navegador, que impide acceder a los recursos del sistema.

Pues bien, a pesar de ello, y de forma totalmente contraria a lo que podríamos intuir, desde Blazor WebAssembly sí que se pueden usar los componentes de System.IO (bueno, o muchos de ellos) para leer o escribir archivos y directorios, aunque con algunas peculiaridades que veremos a continuación.

¿Hay sistema de archivos en Blazor WebAssembly?

Sí, aunque no en la forma en que habitualmente entendemos un sistema de archivos persistentes. El entorno de ejecución proporciona un sistema de archivos virtual (MEMFS) que se crea en memoria del dispositivo cliente; todo su contenido, por tanto, se resetea al cerrar la aplicación (por ejemplo, cuando recargamos o salimos de la página).

Y, como sistema de archivos que es, podemos utilizar los componentes de System.IO para trabajar con archivos y directorios almacenados en él.

Para demostrarlo, podemos crear una aplicación Blazor WebApp con componentes interactivos WebAssembly y añadir la siguiente página en un componente llamado Explorer.razor:

@page "/explorer"

<PageTitle>File explorer</PageTitle>
<h2>Content of @CurrentPath</h2>
<ul>
    @if (CurrentPath != "/")
    {
        var targetPath = Path.GetFullPath(
            System.IO.Path.Combine(CurrentPath, "..")
        );
        <li>
            <a href="/explorer?currentPath=@targetPath">
                [..]
            </a>
        </li>
    }
    @foreach (var dir in new DirectoryInfo(CurrentPath).GetDirectories())
    {
        var targetPath = Path.Combine(CurrentPath, dir.Name);
        <li>
            <a href="/explorer?currentPath=@targetPath">[@dir.Name]</a>
        </li>
    }
    @foreach (var file in new DirectoryInfo(CurrentPath).GetFiles())
    {
        <li>@file.Name (@file.Length bytes)</li>
    }
</ul>

@code {
    private string? _path;

    [SupplyParameterFromQuery]
    public string CurrentPath
    {
        get => _path ?? "/";
        set => _path = value;
    }
}

Resumidamente, se trata de una página que recibe a través de la query string el parámetro CurrentPath, que indica la ruta del directorio a explorar. A continuación, se muestran los directorios y archivos que contiene, permitiendo navegar por ellos mediante hipervínculos.

¡Ojo a la prerenderización! Si el componente WebAssembly se ejecuta durante la prerenderización, los archivos mostrados mientras se inicia la interactividad serán los del servidor 😱. De hecho, si ejecutáis este código, durante unos instantes podréis ver los archivos y carpetas del servidor debido a la prerenderización del componente. Si no queréis este efecto, que será lo más normal, podéis desactivar la prerenderización.

Explorador del sistema de archivos en memoria

Fijaos que, además de unas cuantas carpetas, en el sistema de archivos virtual encontramos los ficheros de settings appsettings.json y appsettings.Development.json, que el runtime ha almacenado para poder acceder a ellos fácilmente.

Y ya que están ahí, los aprovecharemos para comprobar que es posible usar métodos de la clase File para leer su contenido, como haríamos si se tratara de archivos físicos. Podemos verlo fácilmente añadiendo este código al componente Explorer que hemos visto anteriormente, que simplemente mostrará el contenido del archivo appsettings.json al pulsar un botón:

<button @onclick="ShowSettings">Show settings</button>
<p>@Settings</p>

@code {
    string Settings { get; set; }
    private async Task ShowSettings()
    {
        Settings = await File.ReadAllTextAsync("appsettings.json");
    }
}

Lectura del archivo appsettings.json

De la misma forma, podríamos usar métodos de System.IO para crear archivos y carpetas. En el siguiente ejemplo, añadimos otro botón que crea una carpeta en el interior del directorio actual, e introduce en ella un archivo de texto con el contenido "Hello, world!":

<button @onclick="Create">Create</button>
@code
{
    private async Task Create()
    {
        var targetPath = Path.Combine(CurrentPath, "test");
        Directory.CreateDirectory(targetPath);
        await File.WriteAllTextAsync(
           Path.Combine(targetPath, "newfile.txt"), 
           "Hello, world!"
        );
    }
}

En resumen: podemos usar  System.IO prácticamente de la forma habitual, aunque los archivos sobre los que opera se encuentran en la memoria del navegador y, por tanto, no persistirán tras el cierre o recarga (por ejemplo, con F5) de la aplicación.

Publicado en Variable not found.

2 Comentarios:

Juan dijo...

Una curiosidad interesante. Pero si al final el almacenamiento no es persistente y queda en memoria yo lo mantendría en cualquier tipo de estructura en memoria, por ejemplo un Dictionary.

Un lujo leerte.

José María Aguilar dijo...

Hola!

Sí, es curiosidad más que otra cosa. Estoy de acuerdo con lo que comentas, de forma natural probablemente no usaría System.IO para nada, porque al final usar memoria probablemente me resultaría más sencillo.

Pero puede haber escenarios; por ejemplo, imagina que tienes una biblioteca de terceros que, para realizar su función, crea un archivo temporal para realizar cálculos... En este caso, podría venir bien saber que en Blazor proporciona esta implementación.

Muchas gracias por tus comentarios!