C#JavaScriptHTML

Blazor ◀▶ JavaScript: JSON serialisieren und als Datei auf einem lokalen Datenträger speichern.

In diesem Artikel gehe ich darauf ein wie man aus dem WebAssembly Kontext aus eine JavaScript Funktion mit C# aufruft und ihr dabei Parameter übergibt.

Blazor ist ein von Microsoft entwickeltes Framework zur Erstellung von Web Applikationen, die entweder großteilig serverseitig mithilfe einer .Net Laufzeitumgebung oder aber vollständig auf dem Client mittels WebAssembly ausgeführt werden. Dabei wird das HTML Markup mit C# gemixt.

Diesen Ansatz finde ich spannend, da es den Einstieg in WebAssembly unglaublich erleichtert. Für die Entwicklung etwas Vergleichbarem müsste man dann schon die Entsprechenden Code-Abschnitte in C / C++ oder Rust schreiben.


Wir möchten JSON serialisieren und als Datei auf einem lokalen Datenträger speichern. Ein fast schon klassischer Lösungsansatz wäre das Erstellen der JSON - Datei vom Server erledigen zulassen. Man sendet die Eingaben vom Formular in einem HTTP POST Request (Anfrage) zum Server. Der erstellt ein Objekt daraus, serialisiert es und sendet es als Response (Antwort) zum Client zurück. Wie der Browser darauf reagiert ist abhängig vom JavaScript des Frontends und der im Header mit gesendeten Kodierung des Response.

Allerdings möchte ich das nach Möglichkeit komplett vom Browser erledigen lassen und vergessen dabei auch kurz, dass wir sowas auch nur in JavaScript schreiben könnten.

Es soll beispielhaft zur Erläuterung der Schnittstelle zwischen den beiden Sprachen dienen.

Das Setup

Ich habe als Projekt - Template "Blazor-Webassembly-App" mit der Option "ASP .NET gehostet" gewählt. Rein prinzipiell könnt ihr auch auf diese Option verzichten.

Der Blazor - Part

Pages/Json.razor
@page "/json"

@inject IJSRuntime JSRuntime
@using System.Text.Json

<div>
    <label for="name">Name</label>
    <input id="name" @bind="user.Name" placeholder="Ihr Name" />
    <button @onclick="DownloadJSON">Download</button>
</div>

Sollte das Injecten von IJSRuntime Probleme bereiten, dann überprüfe bitte ob das TargetFramework in der Datei Client/[PROJEKTNAME].Client.csproj auf "net5.0" eingestellt ist.

Pages/Json.razor
@code {
    class User
    {
        public string Name { get; set; }
    }

    User user = new User() { Name = "" };
    /*
        Die Schlüsselwörter *async/await* implizieren
        das Ausführen in einem anderen Thread. 

        Blazor setzt auf eine Event-Driven-Architecture.
        Du hast einen Main - Thread + einen Pool von n Worker.
        
        Worker = Task
    */
    async Task DownloadJSON()
    {
        /*
            Das User Objekt ins JSON - Format serialisieren.
        */
        String json = System.Text.Json.JsonSerializer.Serialize(user);
        /*
            Die JS Funktion aus dem WASM Kontext heraus aufrufen.            
        */
        await JSRuntime.InvokeVoidAsync("saveAs", "deine.json", json);
    }
}

Und das JavaScript

Relevant sind nur die beiden Script - Elemente. Das mit der nötigen Funktion muss vor _framework/blazor.webassembly.js eingebunden werden. Und beide werden an das Ende vom Body Element geparkt. Das hat den Hintergrund dass das DOM (Document Object Model | Die Struktur der Elemente des HTML Dokuments) geladen und gerendert wurde bevor JavaScript ausgeführt wird. Dadurch wird das potenzielle initiale Blockieren vom Rendervorgang verhindert.

wwwroot/index.html
<script>        
    function saveAs(filename, data, options) {
        /*                
            Prüfen ob die übergebenen Parameter brauchbar sind.         
        */
        if (typeof filename !== 'string')
            throw new Error('expecting filename as string');

        if (typeof data !== 'string')
            throw new Error('expecting data as string');
        /*                
            Optionale Parameter     
        */
        const opts = {
            /*
                Standard definieren ...
            */
            type: 'application/json',
            /*
                ... und gegebenfalls mit Optionen
                    überschreiben
            */
            ...options,
        };
        /*
            * Ein Blob (dateiähnliches) Objekt erstellen
            * Dem Blob Objekt eine neue URL erzeugen
            * Ein A (Link: <a href=""> ...  </a>) Element erstellen
        */
        const blob = new Blob([data], { ...opts.type });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        /*
            Properties für Dateinamen und URL werden dem Link
            zugewiesen.

            ---------------------------------------------------------
            Die URL ist vom Browser intern auf unser erstelltes 
            Blob Objekt gemapped.
            ---------------------------------------------------------
        */
        a.download = filename;
        a.href = url;
        /*
            Wir lösen das "click" Event vom Link aus
        */
        a.click();
        /*
            Zur Sicherheit den Speicher vom URL Objekt freigeeben.
            Sollte aber eigentlich auch von der Garbage Collection 
            automatisch gehandled werden, wenn ich mich nicht irre.
        */
        URL.revokeObjectURL(url)
    }
</script>
<script src="_framework/blazor.webassembly.js"></script>

Manfred Michaelis / blazor
JSON serialisieren und als Datei auf einem lokalen Datenträger speichern.
image