Erweiterte ASP.NET Core-[Blazor-SzenariosASP.NET Core [Blazor advanced scenarios

Von Luke Latham und Daniel RothBy Luke Latham and Daniel Roth

[Blazor Server-Verbindungshandler[Blazor Server circuit handler

In [Blazor Server kann mithilfe von Code ein Verbindungshandler definiert werden, mit dem Code für Zustandsänderungen einer Benutzerverbindung ausgeführt werden kann.[Blazor Server allows code to define a circuit handler, which allows running code on changes to the state of a user's circuit. Ein Verbindungshandler wird durch das Ableiten von CircuitHandler und Registrieren der Klasse im Dienstcontainer der App implementiert.A circuit handler is implemented by deriving from CircuitHandler and registering the class in the app's service container. Im folgenden Beispiel eines Verbindungshandlers werden geöffnete [SignalR-Verbindungen nachverfolgt:The following example of a circuit handler tracks open [SignalR connections:

using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Server.Circuits;

public class TrackingCircuitHandler : CircuitHandler
{
    private HashSet<Circuit> circuits = new HashSet<Circuit>();

    public override Task OnConnectionUpAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        circuits.Add(circuit);

        return Task.CompletedTask;
    }

    public override Task OnConnectionDownAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        circuits.Remove(circuit);

        return Task.CompletedTask;
    }

    public int ConnectedCircuits => circuits.Count;
}

Verbindungshandler werden mithilfe von DI registriert.Circuit handlers are registered using DI. Bereichsbezogene Instanzen werden pro Verbindungsinstanz erstellt.Scoped instances are created per instance of a circuit. Mithilfe von TrackingCircuitHandler aus dem vorherigen Beispiel wird ein Singletondienst erstellt, da der Zustand aller Verbindungen nachverfolgt werden muss:Using the TrackingCircuitHandler in the preceding example, a singleton service is created because the state of all circuits must be tracked:

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddSingleton<CircuitHandler, TrackingCircuitHandler>();
}

Wenn die Methoden eines benutzerdefinierten Verbindungshandlers einen Ausnahmefehler auslösen, ist dieser Ausnahmefehler für die [Blazor Server-Verbindung schwerwiegend.If a custom circuit handler's methods throw an unhandled exception, the exception is fatal to the [Blazor Server circuit. Umschließen Sie den Code in einer oder mehreren try-catch-Anweisungen mit Fehlerbehandlung und -protokollierung, um Ausnahmen im Code oder in aufgerufenen Methoden eines Handlers zu tolerieren.To tolerate exceptions in a handler's code or called methods, wrap the code in one or more try-catch statements with error handling and logging.

Wenn eine Verbindung beendet wird, weil ein Benutzer die Verbindung getrennt hat und das Framework den Verbindungsstatus bereinigt, gibt das Framework den DI-Bereich der Verbindung frei.When a circuit ends because a user has disconnected and the framework is cleaning up the circuit state, the framework disposes of the circuit's DI scope. Wenn der Bereich freigegeben wird, werden alle Dienste im Verbindungsbereich verworfen, die System.IDisposable implementieren.Disposing the scope disposes any circuit-scoped DI services that implement System.IDisposable. Wenn ein DI-Dienst während der Freigabe einen Ausnahmefehler auslöst, protokolliert das Framework die Ausnahme.If any DI service throws an unhandled exception during disposal, the framework logs the exception.

Manuelle RenderTreeBuilder-LogikManual RenderTreeBuilder logic

RenderTreeBuilder stellt Methoden zum Bearbeiten von Komponenten und Elementen bereit. Dazu gehört auch das manuelle Erstellen von Komponenten in C#-Code.RenderTreeBuilder provides methods for manipulating components and elements, including building components manually in C# code.

Hinweis

Die Verwendung von RenderTreeBuilder zum Erstellen von Komponenten ist ein erweitertes Szenario.Use of RenderTreeBuilder to create components is an advanced scenario. Eine falsch formatierte Komponente (z. B. ein nicht geschlossenes Markuptag) kann zu undefiniertem Verhalten führen.A malformed component (for example, an unclosed markup tag) can result in undefined behavior.

Beachten Sie die folgende PetDetails-Komponente, die manuell in eine andere Komponente integriert werden kann.Consider the following PetDetails component, which can be manually built into another component:

<h2>Pet Details Component</h2>

<p>@PetDetailsQuote</p>

@code
{
    [Parameter]
    public string PetDetailsQuote { get; set; }
}

Im folgenden Beispiel generiert die Schleife in der CreateComponent-Methode drei PetDetails-Komponenten.In the following example, the loop in the CreateComponent method generates three PetDetails components. Wenn Sie RenderTreeBuilder-Methoden aufrufen, um die Komponenten (OpenComponent und AddAttribute) zu erstellen, sind die Sequenznummern Zeilennummern des Quellcodes.When calling RenderTreeBuilder methods to create the components (OpenComponent and AddAttribute), sequence numbers are source code line numbers. Der diff-Algorithmus von [Blazor basiert auf den Sequenznummern, die unterschiedlichen Codezeilen und nicht unterschiedlichen Aufrufen entsprechen.The [Blazor difference algorithm relies on the sequence numbers corresponding to distinct lines of code, not distinct call invocations. Hartcodieren Sie die Argumente für Sequenznummern, wenn Sie eine Komponente mit RenderTreeBuilder-Methoden erstellen.When creating a component with RenderTreeBuilder methods, hardcode the arguments for sequence numbers. Die Verwendung einer Berechnung oder eines Leistungszählers zum Generieren der Sequenznummer kann zu schlechter Leistung führen.Using a calculation or counter to generate the sequence number can lead to poor performance. Weitere Informationen finden Sie im Abschnitt Auf Codezeilennummern und nicht auf die Ausführungsreihenfolge bezogene Sequenznummern.For more information, see the Sequence numbers relate to code line numbers and not execution order section.

BuiltContent-Komponente:BuiltContent component:

@page "/BuiltContent"

<h1>Build a component</h1>

@CustomRender

<button type="button" @onclick="RenderComponent">
    Create three Pet Details components
</button>

@code {
    private RenderFragment CustomRender { get; set; }
    
    private RenderFragment CreateComponent() => builder =>
    {
        for (var i = 0; i < 3; i++) 
        {
            builder.OpenComponent(0, typeof(PetDetails));
            builder.AddAttribute(1, "PetDetailsQuote", "Someone's best friend!");
            builder.CloseComponent();
        }
    };    
    
    private void RenderComponent()
    {
        CustomRender = CreateComponent();
    }
}

Warnung

Die Typen in Microsoft.AspNetCore.Components.RenderTree ermöglichen die Verarbeitung der Ergebnisse von Renderingvorgängen.The types in Microsoft.AspNetCore.Components.RenderTree allow processing of the results of rendering operations. Hierbei handelt es sich um interne Details der [Blazor-Frameworkimplementierung.These are internal details of the [Blazor framework implementation. Diese Typen sollten als instabil betrachtet werden und in zukünftigen Versionen geändert werden.These types should be considered unstable and subject to change in future releases.

Auf Codezeilennummern und nicht auf die Ausführungsreihenfolge bezogene SequenznummernSequence numbers relate to code line numbers and not execution order

[Razor-Komponentendateien (.razor) werden stets kompiliert.[Razor component files (.razor) are always compiled. Die Kompilierung stellt gegenüber der Interpretation von Code einen Vorteil dar, da der Kompilierungsschritt zum Einfügen von Informationen verwendet werden kann, die die App-Leistung zur Laufzeit erhöhen können.Compilation is a potential advantage over interpreting code because the compile step can be used to inject information that improves app performance at runtime.

Ein wichtiges Beispiel für diese Verbesserungen sind Sequenznummern.A key example of these improvements involves sequence numbers. Sequenznummern geben der Laufzeit an, welche Ausgaben aus den unterschiedlichen und geordneten Codezeilen stammen.Sequence numbers indicate to the runtime which outputs came from which distinct and ordered lines of code. Diese Informationen werden von der Laufzeit verwendet, um effiziente und zeitlich lineare Strukturunterschiede zu generieren. Diese Methode ist viel schneller als es für einen allgemeinen diff-Strukturalgorithmus normalerweise möglich ist.The runtime uses this information to generate efficient tree diffs in linear time, which is far faster than is normally possible for a general tree diff algorithm.

Betrachten Sie die Datei der folgenden [Razor-Komponente (.razor):Consider the following [Razor component (.razor) file:

@if (someFlag)
{
    <text>First</text>
}

Second

Der vorangehende Code wird in etwa wie folgt kompiliert:The preceding code compiles to something like the following:

if (someFlag)
{
    builder.AddContent(0, "First");
}

builder.AddContent(1, "Second");

Wenn der Code zum ersten Mal ausgeführt wird, erhält der Generator Folgendes, wenn someFlag true ist:When the code executes for the first time, if someFlag is true, the builder receives:

SequenzSequence TypType DatenData
00 TextknotenText node FirstFirst
11 TextknotenText node SecondSecond

Stellen Sie sich vor, dass someFlag false wird und das Markup wieder gerendert wird.Imagine that someFlag becomes false, and the markup is rendered again. Dieses Mal empfängt der Generator Folgendes:This time, the builder receives:

SequenzSequence TypType DatenData
11 TextknotenText node SecondSecond

Wenn die Laufzeit einen diff-Algorithmus ausführt, wird angezeigt, dass das Element bei Sequenz 0 entfernt wurde. Daraufhin wird das folgende triviale Bearbeitungsskript generiert:When the runtime performs a diff, it sees that the item at sequence 0 was removed, so it generates the following trivial edit script:

  • Entfernen Sie den ersten Textknoten.Remove the first text node.

Problem beim programmgesteuerten Generieren von SequenznummernThe problem with generating sequence numbers programmatically

Angenommen, Sie haben stattdessen die folgende Buildlogik für die Renderstruktur geschrieben:Imagine instead that you wrote the following render tree builder logic:

var seq = 0;

if (someFlag)
{
    builder.AddContent(seq++, "First");
}

builder.AddContent(seq++, "Second");

Die erste Ausgabe lautet nun wie folgt:Now, the first output is:

SequenzSequence TypType DatenData
00 TextknotenText node FirstFirst
11 TextknotenText node SecondSecond

Dieses Ergebnis ist mit dem des vorherigen Falls identisch, sodass keine negativen Probleme aufgetreten sind.This outcome is identical to the prior case, so no negative issues exist. someFlag ist beim zweiten Rendering false, und die Ausgabe lautet wie folgt:someFlag is false on the second rendering, and the output is:

SequenzSequence TypType DatenData
00 TextknotenText node SecondSecond

Dieses Mal zeigt der diff-Algorithmus an, dass zwei Änderungen aufgetreten sind, und der Algorithmus generiert das folgende Bearbeitungsskript:This time, the diff algorithm sees that two changes have occurred, and the algorithm generates the following edit script:

  • Ändern Sie den Wert des ersten Textknotens in Second.Change the value of the first text node to Second.
  • Entfernen Sie den zweiten Textknoten.Remove the second text node.

Durch das Erzeugen der Sequenznummern sind alle nützlichen Informationen dazu verloren gegangen, wo die if/else-Branches und -Schleifen im ursprünglichen Code vorhanden waren.Generating the sequence numbers has lost all the useful information about where the if/else branches and loops were present in the original code. Dies führt zu einem doppelt so langen diff wie zuvor.This results in a diff twice as long as before.

Dies ist ein triviales Beispiel.This is a trivial example. In realistischeren Fällen mit komplexen und tief geschachtelten Strukturen und vor allem mit Schleifen sind die Leistungskosten in der Regel höher.In more realistic cases with complex and deeply nested structures, and especially with loops, the performance cost is usually higher. Anstatt sofort festzustellen, welche Schleifenblöcke oder Branches eingefügt oder entfernt wurden, muss der diff-Algorithmus eine tiefe Rekursion der Renderstrukturen durchführen.Instead of immediately identifying which loop blocks or branches have been inserted or removed, the diff algorithm has to recurse deeply into the render trees. Dies führt in der Regel dazu, dass längere Bearbeitungsskripts erstellt werden müssen, da der diff-Algorithmus falsch informiert ist, wie sich die alten und neuen Strukturen aufeinander beziehen.This usually results in having to build longer edit scripts because the diff algorithm is misinformed about how the old and new structures relate to each other.

Anleitungen und SchlussfolgerungenGuidance and conclusions

  • Die App-Leistung leidet, wenn Sequenznummern dynamisch generiert werden.App performance suffers if sequence numbers are generated dynamically.
  • Das Framework kann zur Laufzeit automatisch keine eigenen Sequenznummern erstellen, da die erforderlichen Informationen nicht vorhanden sind, es sei denn, sie werden zur Kompilierzeit aufgezeichnet.The framework can't create its own sequence numbers automatically at runtime because the necessary information doesn't exist unless it's captured at compile time.
  • Schreiben Sie keine langen Blöcke manuell implementierter RenderTreeBuilder-Logik.Don't write long blocks of manually-implemented RenderTreeBuilder logic. Bevorzugen Sie .razor-Dateien, und ermöglichen Sie es dem Compiler, die Sequenznummern zu verarbeiten.Prefer .razor files and allow the compiler to deal with the sequence numbers. Wenn Sie manuelle RenderTreeBuilder-Logik nicht vermeiden können, teilen Sie lange Codeblöcke in kleinere Teile auf, die in OpenRegion/CloseRegion-Aufrufe eingebunden werden.If you're unable to avoid manual RenderTreeBuilder logic, split long blocks of code into smaller pieces wrapped in OpenRegion/CloseRegion calls. Jede Region verfügt über einen eigenen separaten Bereich von Sequenznummern, sodass Sie in jeder Region von 0 (Null; oder von jeder anderen beliebigen Zahl) aus starten können.Each region has its own separate space of sequence numbers, so you can restart from zero (or any other arbitrary number) inside each region.
  • Wenn Sequenznummern hartcodiert sind, erfordert der diff-Algorithmus nur, dass die Sequenznummern den Wert erhöhen.If sequence numbers are hardcoded, the diff algorithm only requires that sequence numbers increase in value. Die Anfangswerte und Lücken sind irrelevant.The initial value and gaps are irrelevant. Eine legitime Option besteht darin, die Codezeilennummer als Sequenznummer zu verwenden oder von 0 (Null) aus zu starten und Werte in Einer- oder Hunderterschritten (oder einem beliebigen bevorzugten Intervall) zu erhöhen.One legitimate option is to use the code line number as the sequence number, or start from zero and increase by ones or hundreds (or any preferred interval).
  • [Blazor verwendet Sequenznummern, während andere Benutzeroberflächenframeworks diese nicht für Verzeichnisvergleiche verwenden.[Blazor uses sequence numbers, while other tree-diffing UI frameworks don't use them. Das Vergleichen ist weitaus schneller, wenn Sequenznummern verwendet werden. Außerdem hat [Blazor den Vorteil eines Kompilierungsschritts, der Sequenznummern automatisch für Entwickler verarbeitet, die .razor-Dateien erstellen.Diffing is far faster when sequence numbers are used, and [Blazor has the advantage of a compile step that deals with sequence numbers automatically for developers authoring .razor files.

Ausführen umfangreicher Datenübertragungen in [Blazor Server-AppsPerform large data transfers in [Blazor Server apps

In einigen Szenarios müssen große Datenmengen zwischen JavaScript und [Blazorübertragen werden.In some scenarios, large amounts of data must be transferred between JavaScript and [Blazor. In der Regel erfolgen große Datenübertragungen in den folgenden Fällen:Typically, large data transfers occur when:

  • Dateisystem-APIs des Browsers werden für das Hochladen oder Herunterladen einer Datei verwendet.Browser file system APIs are used to upload or download a file.
  • Es ist eine Interaktion mit einer Drittanbieterbibliothek erforderlich.Interop with a third party library is required.

In [Blazor Server gibt es eine Einschränkung, um zu verhindern, dass einzelne große Nachrichten übergeben werden, die zu Leistungsproblemen führen können.In [Blazor Server, a limitation is in place to prevent passing single large messages that may result in performance issues.

Beachten Sie die folgenden Anleitungen, wenn Sie Code zum Übertragen von Daten zwischen JavaScript und [Blazor entwickeln:Consider the following guidance when developing code that transfers data between JavaScript and [Blazor:

  • Segmentieren Sie die Daten in kleinere Teile, und senden Sie die Datensegmente sequenziell, bis alle Daten vom Server empfangen wurden.Slice the data into smaller pieces, and send the data segments sequentially until all of the data is received by the server.
  • Ordnen Sie in JavaScript- und C#-Code keine großen Objekte zu.Don't allocate large objects in JavaScript and C# code.
  • Blockieren Sie den hauptsächlichen Benutzeroberflächenthread nicht für lange Zeiträume, wenn Sie Daten senden oder empfangen.Don't block the main UI thread for long periods when sending or receiving data.
  • Geben Sie belegten Arbeitsspeicher frei, wenn der Prozess abgeschlossen oder abgebrochen wird.Free any memory consumed when the process is completed or cancelled.
  • Erzwingen Sie die folgenden zusätzlichen Anforderungen aus Sicherheitsgründen:Enforce the following additional requirements for security purposes:
    • Deklarieren Sie die maximale Datei- oder Datengröße, die übermittelt werden kann.Declare the maximum file or data size that can be passed.
    • Deklarieren Sie die minimale Uploadrate vom Client an den Server.Declare the minimum upload rate from the client to the server.
  • Nachdem die Daten vom Server empfangen wurden, ist mit den Daten Folgendes möglich:After the data is received by the server, the data can be:
    • Sie können temporär in einem Speicherpuffer gespeichert werden, bis alle Segmente gesammelt wurden.Temporarily stored in a memory buffer until all of the segments are collected.
    • Sie können sofort verarbeitet werden.Consumed immediately. Beispielsweise können die Daten sofort in einer Datenbank gespeichert oder auf den Datenträger geschrieben werden, wenn die einzelnen Segmente empfangen werden.For example, the data can be stored immediately in a database or written to disk as each segment is received.

Die folgende Dateiuploaderklasse verarbeitet JS Interop mit dem Client.The following file uploader class handles JS interop with the client. Die Uploaderklasse verwendet JS Interop für Folgendes:The uploader class uses JS interop to:

  • Abrufen des Clients, um ein Datensegment zu sendenPoll the client to send a data segment.
  • Abbrechen der Transaktion, wenn ein Timeout für den Abruf auftrittAbort the transaction if polling times out.
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Microsoft.JSInterop;

public class FileUploader : IDisposable
{
    private readonly IJSRuntime jsRuntime;
    private readonly int segmentSize = 6144;
    private readonly int maxBase64SegmentSize = 8192;
    private readonly DotNetObjectReference<FileUploader> thisReference;
    private List<IMemoryOwner<byte>> uploadedSegments = 
        new List<IMemoryOwner<byte>>();

    public FileUploader(IJSRuntime jsRuntime)
    {
        this.jsRuntime = jsRuntime;
    }

    public async Task<Stream> ReceiveFile(string selector, int maxSize)
    {
        var fileSize = 
            await jsRuntime.InvokeAsync<int>("getFileSize", selector);

        if (fileSize > maxSize)
        {
            return null;
        }

        var numberOfSegments = Math.Floor(fileSize / (double)segmentSize) + 1;
        var lastSegmentBytes = 0;
        string base64EncodedSegment;

        for (var i = 0; i < numberOfSegments; i++)
        {
            try
            {
                base64EncodedSegment = 
                    await jsRuntime.InvokeAsync<string>(
                        "receiveSegment", i, selector);

                if (base64EncodedSegment.Length < maxBase64SegmentSize && 
                    i < numberOfSegments - 1)
                {
                    return null;
                }
            }
            catch
            {
                return null;
            }

          var current = MemoryPool<byte>.Shared.Rent(segmentSize);

          if (!Convert.TryFromBase64String(base64EncodedSegment, 
              current.Memory.Slice(0, segmentSize).Span, out lastSegmentBytes))
          {
              return null;
          }

          uploadedSegments.Add(current);
        }

        var segments = uploadedSegments;
        uploadedSegments = null;

        return new SegmentedStream(segments, segmentSize, lastSegmentBytes);
    }

    public void Dispose()
    {
        if (uploadedSegments != null)
        {
            foreach (var segment in uploadedSegments)
            {
                segment.Dispose();
            }
        }
    }
}

Im vorherigen Beispiel:In the preceding example:

  • maxBase64SegmentSize wird auf 8192 festgelegt. Dies wird folgendermaßen berechnet: maxBase64SegmentSize = segmentSize * 4 / 3.The maxBase64SegmentSize is set to 8192, which is calculated from maxBase64SegmentSize = segmentSize * 4 / 3.
  • Die .NET Core-APIs für die Speicherverwaltung auf niedriger Ebene werden zum Speichern der Speichersegmente auf dem Server in uploadedSegments verwendet.Low-level .NET Core memory management APIs are used to store the memory segments on the server in uploadedSegments.
  • Eine ReceiveFile-Methode wird verwendet, um den Upload über JS Interop zu verarbeiten:A ReceiveFile method is used to handle the upload through JS interop:
    • Die Dateigröße wird in Bytes über JS Interop mit jsRuntime.InvokeAsync<FileInfo>('getFileSize', selector) angegeben.The file size is determined in bytes through JS interop with jsRuntime.InvokeAsync<FileInfo>('getFileSize', selector).
    • Die Anzahl der zu empfangenden Segmente wird berechnet und in numberOfSegmentsgespeichert.The number of segments to receive are calculated and stored in numberOfSegments.
    • Die Segmente werden in einer for-Schleife durch JS Interop mit jsRuntime.InvokeAsync<string>('receiveSegment', i, selector) angefordert.The segments are requested in a for loop through JS interop with jsRuntime.InvokeAsync<string>('receiveSegment', i, selector). Alle Segmente bis auf das letzte müssen vor dem Decodieren eine Größe von 8.192 Bytes haben.All segments but the last must be 8,192 bytes before decoding. Der Client wird gezwungen, die Daten auf effiziente Weise zu senden.The client is forced to send the data in an efficient manner.
    • Für jedes empfangene Segment werden vor der Decodierung mit TryFromBase64String Überprüfungen ausgeführt.For each segment received, checks are performed before decoding with TryFromBase64String.
    • Ein Datenstrom mit den Daten wird nach Abschluss des Uploads als neuer Stream (SegmentedStream) zurückgegeben.A stream with the data is returned as a new Stream (SegmentedStream) after the upload is complete.

Die segmentierte Streamklasse macht die Liste der Segmente als schreibgeschützten, nicht suchbaren Stream verfügbar:The segmented stream class exposes the list of segments as a readonly non-seekable Stream:

using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;

public class SegmentedStream : Stream
{
    private readonly ReadOnlySequence<byte> sequence;
    private long currentPosition = 0;

    public SegmentedStream(IList<IMemoryOwner<byte>> segments, int segmentSize, 
        int lastSegmentSize)
    {
        if (segments.Count == 1)
        {
            sequence = new ReadOnlySequence<byte>(
                segments[0].Memory.Slice(0, lastSegmentSize));
            return;
        }

        var sequenceSegment = new BufferSegment<byte>(
            segments[0].Memory.Slice(0, segmentSize));
        var lastSegment = sequenceSegment;

        for (int i = 1; i < segments.Count; i++)
        {
            var isLastSegment = i + 1 == segments.Count;
            lastSegment = lastSegment.Append(segments[i].Memory.Slice(
                0, isLastSegment ? lastSegmentSize : segmentSize));
        }

        sequence = new ReadOnlySequence<byte>(
            sequenceSegment, 0, lastSegment, lastSegmentSize);
    }

    public override long Position
    {
        get => throw new NotImplementedException();
        set => throw new NotImplementedException();
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        var bytesToWrite = (int)(currentPosition + count < sequence.Length ? 
            count : sequence.Length - currentPosition);
        var data = sequence.Slice(currentPosition, bytesToWrite);
        data.CopyTo(buffer.AsSpan(offset, bytesToWrite));
        currentPosition += bytesToWrite;

        return bytesToWrite;
    }

    private class BufferSegment<T> : ReadOnlySequenceSegment<T>
    {
        public BufferSegment(ReadOnlyMemory<T> memory)
        {
            Memory = memory;
        }

        public BufferSegment<T> Append(ReadOnlyMemory<T> memory)
        {
            var segment = new BufferSegment<T>(memory)
            {
                RunningIndex = RunningIndex + Memory.Length
            };

            Next = segment;

            return segment;
        }
    }

    public override bool CanRead => true;

    public override bool CanSeek => false;

    public override bool CanWrite => false;

    public override long Length => throw new NotImplementedException();

    public override void Flush() => throw new NotImplementedException();

    public override long Seek(long offset, SeekOrigin origin) => 
        throw new NotImplementedException();

    public override void SetLength(long value) => 
        throw new NotImplementedException();

    public override void Write(byte[] buffer, int offset, int count) => 
        throw new NotImplementedException();
}

Der folgende Code implementiert JavaScript-Funktionen, um die Daten zu empfangen:The following code implements JavaScript functions to receive the data:

function getFileSize(selector) {
  const file = getFile(selector);
  return file.size;
}

async function receiveSegment(segmentNumber, selector) {
  const file = getFile(selector);
  var segments = getFileSegments(file);
  var index = segmentNumber * 6144;
  return await getNextChunk(file, index);
}

function getFile(selector) {
  const element = document.querySelector(selector);
  if (!element) {
    throw new Error('Invalid selector');
  }
  const files = element.files;
  if (!files || files.length === 0) {
    throw new Error(`Element ${elementId} doesn't contain any files.`);
  }
  const file = files[0];
  return file;
}

function getFileSegments(file) {
  const segments = Math.floor(size % 6144 === 0 ? size / 6144 : 1 + size / 6144);
  return segments;
}

async function getNextChunk(file, index) {
  const length = file.size - index <= 6144 ? file.size - index : 6144;
  const chunk = file.slice(index, index + length);
  index += length;
  const base64Chunk = await this.base64EncodeAsync(chunk);
  return { base64Chunk, index };
}

async function base64EncodeAsync(chunk) {
  const reader = new FileReader();
  const result = new Promise((resolve, reject) => {
    reader.addEventListener('load',
      () => {
        const base64Chunk = reader.result;
        const cleanChunk = 
          base64Chunk.replace('data:application/octet-stream;base64,', '');
        resolve(cleanChunk);
      },
      false);
    reader.addEventListener('error', reject);
  });
  reader.readAsDataURL(chunk);
  return result;
}