ASP.NET Core Blazor l’interopérabilité JavaScriptASP.NET Core Blazor JavaScript interop

Par Javier Calvarro Nelson, Daniel Rothet Luke LathamBy Javier Calvarro Nelson, Daniel Roth, and Luke Latham

Important

Blazor webassembly en préversionBlazor WebAssembly in preview

Blazor Server est pris en charge dans ASP.net Core 3,0.Blazor Server is supported in ASP.NET Core 3.0. Blazor Webassembly est en version préliminaire pour ASP.net Core 3,1.Blazor WebAssembly is in preview for ASP.NET Core 3.1.

Une application Blazor peut appeler des fonctions JavaScript à partir de méthodes .NET et .NET à partir de code JavaScript.A Blazor app can invoke JavaScript functions from .NET and .NET methods from JavaScript code.

Affichez ou téléchargez l’exemple de code (procédure de téléchargement)View or download sample code (how to download)

Appeler des fonctions JavaScript à partir de méthodes .NETInvoke JavaScript functions from .NET methods

Il arrive parfois que le code .NET soit requis pour appeler une fonction JavaScript.There are times when .NET code is required to call a JavaScript function. Par exemple, un appel JavaScript peut exposer des fonctionnalités de navigateur ou des fonctionnalités d’une bibliothèque JavaScript à l’application.For example, a JavaScript call can expose browser capabilities or functionality from a JavaScript library to the app. Ce scénario s’appelle l’interopérabilité de JavaScript (js Interop).This scenario is called JavaScript interoperability (JS interop).

Pour appeler JavaScript à partir de .NET, utilisez l’abstraction IJSRuntime.To call into JavaScript from .NET, use the IJSRuntime abstraction. Pour émettre des appels d’interopérabilité JS, injectez le IJSRuntime abstraction dans votre composant.To issue JS interop calls, inject the IJSRuntime abstraction in your component. La méthode InvokeAsync<T> accepte un identificateur pour la fonction JavaScript que vous souhaitez appeler, ainsi qu’un nombre quelconque d’arguments sérialisables JSON.The InvokeAsync<T> method takes an identifier for the JavaScript function that you wish to invoke along with any number of JSON-serializable arguments. L’identificateur de fonction est relatif à la portée globale (window).The function identifier is relative to the global scope (window). Si vous souhaitez appeler window.someScope.someFunction, l’identificateur est someScope.someFunction.If you wish to call window.someScope.someFunction, the identifier is someScope.someFunction. Il n’est pas nécessaire d’inscrire la fonction avant qu’elle ne soit appelée.There's no need to register the function before it's called. Le type de retour T doit également être sérialisable JSON.The return type T must also be JSON serializable. T doit correspondre au type .NET qui correspond le mieux au type JSON retourné.T should match the .NET type that best maps to the JSON type returned.

Pour les applications Blazor Server avec le prérendu activé, l’appel à JavaScript n’est pas possible pendant le prérendu initial.For Blazor Server apps with prerendering enabled, calling into JavaScript isn't possible during the initial prerendering. Les appels Interop JavaScript doivent être différés jusqu’à ce que la connexion avec le navigateur soit établie.JavaScript interop calls must be deferred until after the connection with the browser is established. Pour plus d’informations, consultez la section détecter le prérendu d’une application Blazor .For more information, see the Detect when a Blazor app is prerendering section.

L’exemple suivant est basé sur TextDecoder, un décodeur basé sur JavaScript expérimental.The following example is based on TextDecoder, an experimental JavaScript-based decoder. L’exemple montre comment appeler une fonction JavaScript à partir d' C# une méthode.The example demonstrates how to invoke a JavaScript function from a C# method. La fonction JavaScript accepte un tableau d’octets d' C# une méthode, décode le tableau et retourne le texte au composant pour l’affichage.The JavaScript function accepts a byte array from a C# method, decodes the array, and returns the text to the component for display.

À l’intérieur de l’élément <head> de wwwroot/index.html (Blazor webassembly) ou de pages/_Host. cshtml (serveurBlazor), fournissez une fonction JavaScript qui utilise TextDecoder pour décoder un tableau passé et retourner la valeur décodée :Inside the <head> element of wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server), provide a JavaScript function that uses TextDecoder to decode a passed array and return the decoded value:

<script>
  window.convertArray = (win1251Array) => {
    var win1251decoder = new TextDecoder('windows-1251');
    var bytes = new Uint8Array(win1251Array);
    var decodedArray = win1251decoder.decode(bytes);
    console.log(decodedArray);
    return decodedArray;
  };
</script>

Du code JavaScript, tel que le code illustré dans l’exemple précédent, peut également être chargé à partir d’un fichier JavaScript ( . js) avec une référence au fichier de script :JavaScript code, such as the code shown in the preceding example, can also be loaded from a JavaScript file (.js) with a reference to the script file:

<script src="exampleJsInterop.js"></script>

Le composant suivant :The following component:

  • Appelle la fonction JavaScript convertArray à l’aide de JSRuntime lorsqu’un bouton de composant (convertir un tableau) est sélectionné.Invokes the convertArray JavaScript function using JSRuntime when a component button (Convert Array) is selected.
  • Une fois la fonction JavaScript appelée, le tableau passé est converti en chaîne.After the JavaScript function is called, the passed array is converted into a string. La chaîne est retournée au composant pour l’affichage.The string is returned to the component for display.
@page "/call-js-example"
@inject IJSRuntime JSRuntime;

<h1>Call JavaScript Function Example</h1>

<button type="button" class="btn btn-primary" @onclick="ConvertArray">
    Convert Array
</button>

<p class="mt-2" style="font-size:1.6em">
    <span class="badge badge-success">
        @_convertedText
    </span>
</p>

@code {
    // Quote (c)2005 Universal Pictures: Serenity
    // https://www.uphe.com/movies/serenity
    // David Krumholtz on IMDB: https://www.imdb.com/name/nm0472710/

    private MarkupString _convertedText =
        new MarkupString("Select the <b>Convert Array</b> button.");

    private uint[] _quoteArray = new uint[]
        {
            60, 101, 109, 62, 67, 97, 110, 39, 116, 32, 115, 116, 111, 112, 32,
            116, 104, 101, 32, 115, 105, 103, 110, 97, 108, 44, 32, 77, 97,
            108, 46, 60, 47, 101, 109, 62, 32, 45, 32, 77, 114, 46, 32, 85, 110,
            105, 118, 101, 114, 115, 101, 10, 10,
        };

    private async Task ConvertArray()
    {
        var text =
            await JSRuntime.InvokeAsync<string>("convertArray", _quoteArray);

        _convertedText = new MarkupString(text);

        StateHasChanged();
    }
}

Utilisation de IJSRuntimeUse of IJSRuntime

Pour utiliser l’abstraction IJSRuntime, adoptez l’une des approches suivantes :To use the IJSRuntime abstraction, adopt any of the following approaches:

  • Injecter l’abstraction IJSRuntime dans le composant Razor ( . Razor) :Inject the IJSRuntime abstraction into the Razor component (.razor):

    @inject IJSRuntime JSRuntime
    
    @code {
        protected override void OnInitialized()
        {
            StocksService.OnStockTickerUpdated += stockUpdate =>
            {
                JSRuntime.InvokeVoidAsync("handleTickerChanged",
                    stockUpdate.symbol, stockUpdate.price);
            };
        }
    }
    

    À l’intérieur de l’élément <head> de wwwroot/index.html (Blazor webassembly) ou de pages/_Host. cshtml (serveurBlazor), fournissez une fonction JavaScript handleTickerChanged.Inside the <head> element of wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server), provide a handleTickerChanged JavaScript function. La fonction est appelée avec IJSRuntime.InvokeVoidAsync et ne retourne pas de valeur :The function is called with IJSRuntime.InvokeVoidAsync and doesn't return a value:

    <script>
      window.handleTickerChanged = (symbol, price) => {
        // ... client-side processing/display code ...
      };
    </script>
    
  • Injecter l’abstraction IJSRuntime dans une classe ( . cs) :Inject the IJSRuntime abstraction into a class (.cs):

    public class JsInteropClasses
    {
        private readonly IJSRuntime _jsRuntime;
    
        public JsInteropClasses(IJSRuntime jsRuntime)
        {
            _jsRuntime = jsRuntime;
        }
    
        public ValueTask<string> TickerChanged(string data)
        {
            return _jsRuntime.InvokeAsync<string>(
                "handleTickerChanged",
                stockUpdate.symbol,
                stockUpdate.price);
        }
    }
    

    À l’intérieur de l’élément <head> de wwwroot/index.html (Blazor webassembly) ou de pages/_Host. cshtml (serveurBlazor), fournissez une fonction JavaScript handleTickerChanged.Inside the <head> element of wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server), provide a handleTickerChanged JavaScript function. La fonction est appelée avec JSRuntime.InvokeAsync et retourne une valeur :The function is called with JSRuntime.InvokeAsync and returns a value:

    <script>
      window.handleTickerChanged = (symbol, price) => {
        // ... client-side processing/display code ...
        return 'Done!';
      };
    </script>
    
  • Pour la génération de contenu dynamique avec BuildRenderTree, utilisez l’attribut [Inject] :For dynamic content generation with BuildRenderTree, use the [Inject] attribute:

    [Inject]
    IJSRuntime JSRuntime { get; set; }
    

Dans l’exemple d’application côté client qui accompagne cette rubrique, deux fonctions JavaScript sont disponibles pour l’application qui interagit avec le DOM pour recevoir l’entrée d’utilisateur et afficher un message d’accueil :In the client-side sample app that accompanies this topic, two JavaScript functions are available to the app that interact with the DOM to receive user input and display a welcome message:

  • showPrompt – génère une invite pour accepter l’entrée d’utilisateur (nom de l’utilisateur) et retourne le nom à l’appelant.showPrompt – Produces a prompt to accept user input (the user's name) and returns the name to the caller.
  • displayWelcome – assigne un message de bienvenue de l’appelant à un objet DOM avec une id de welcome.displayWelcome – Assigns a welcome message from the caller to a DOM object with an id of welcome.

wwwroot/exampleJsInterop. js:wwwroot/exampleJsInterop.js:

window.exampleJsFunctions = {
  showPrompt: function (text) {
    return prompt(text, 'Type your name here');
  },
  displayWelcome: function (welcomeMessage) {
    document.getElementById('welcome').innerText = welcomeMessage;
  },
  returnArrayAsyncJs: function () {
    DotNet.invokeMethodAsync('BlazorSample', 'ReturnArrayAsync')
      .then(data => {
        data.push(4);
          console.log(data);
      });
  },
  sayHello: function (dotnetHelper) {
    return dotnetHelper.invokeMethodAsync('SayHello')
      .then(r => console.log(r));
  }
};

Placez la balise <script> qui fait référence au fichier JavaScript dans le fichier wwwroot/index.html (Blazor assembly) ou le fichier pages/_Host. cshtml (serveurBlazor).Place the <script> tag that references the JavaScript file in the wwwroot/index.html file (Blazor WebAssembly) or Pages/_Host.cshtml file (Blazor Server).

wwwroot/index.html (Blazor webassembly) :wwwroot/index.html (Blazor WebAssembly):

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>Blazor WebAssembly Sample</title>
    <base href="/" />
    <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
    <link href="css/site.css" rel="stylesheet" />
</head>

<body>
    <app>Loading...</app>

    <div id="blazor-error-ui">
        An unhandled error has occurred.
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>
    <script src="_framework/blazor.webassembly.js"></script>
    <script src="exampleJsInterop.js"></script>
</body>

</html>

Pages/_Host. cshtml (serveurBlazor) :Pages/_Host.cshtml (Blazor Server):

@page "/"
@namespace BlazorSample.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
    Layout = null;
}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Blazor Server Sample</title>
    <base href="~/" />
    <link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
    <link href="css/site.css" rel="stylesheet" />
</head>
<body>
    <app>
        <component type="typeof(App)" render-mode="ServerPrerendered" />
    </app>

    <div id="blazor-error-ui">
        <environment include="Staging,Production">
            An error has occurred. This application may no longer respond until reloaded.
        </environment>
        <environment include="Development">
            An unhandled exception has occurred. See browser dev tools for details.
        </environment>
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>

    <script src="_framework/blazor.server.js"></script>
    <script src="exampleJsInterop.js"></script>
</body>
</html>

Ne placez pas de balise <script> dans un fichier de composant, car la balise <script> ne peut pas être mise à jour dynamiquement.Don't place a <script> tag in a component file because the <script> tag can't be updated dynamically.

Les méthodes .NET interagissent avec les fonctions JavaScript dans le fichier exampleJsInterop. js en appelant IJSRuntime.InvokeAsync<T>..NET methods interop with the JavaScript functions in the exampleJsInterop.js file by calling IJSRuntime.InvokeAsync<T>.

L’abstraction IJSRuntime est asynchrone pour permettre les scénarios de serveur Blazor.The IJSRuntime abstraction is asynchronous to allow for Blazor Server scenarios. Si l’application est une application Blazor webassembly et que vous souhaitez appeler une fonction JavaScript de manière synchrone, vous devez effectuer un cast aval pour IJSInProcessRuntime et appeler Invoke<T> à la place.If the app is a Blazor WebAssembly app and you want to invoke a JavaScript function synchronously, downcast to IJSInProcessRuntime and call Invoke<T> instead. Nous recommandons que la plupart des bibliothèques d’interopérabilité JS utilisent les API Async pour s’assurer que les bibliothèques sont disponibles dans tous les scénarios.We recommend that most JS interop libraries use the async APIs to ensure that the libraries are available in all scenarios.

L’exemple d’application comprend un composant pour illustrer l’interopérabilité de JS.The sample app includes a component to demonstrate JS interop. Le composant :The component:

  • Reçoit une entrée d’utilisateur via une invite JavaScript.Receives user input via a JavaScript prompt.
  • Retourne le texte du composant pour traitement.Returns the text to the component for processing.
  • Appelle une deuxième fonction JavaScript qui interagit avec le DOM pour afficher un message d’accueil.Calls a second JavaScript function that interacts with the DOM to display a welcome message.

Pages/JSInterop. Razor:Pages/JSInterop.razor:

@page "/JSInterop"
@using BlazorSample.JsInteropClasses
@inject IJSRuntime JSRuntime

<h1>JavaScript Interop</h1>

<h2>Invoke JavaScript functions from .NET methods</h2>

<button type="button" class="btn btn-primary" @onclick="TriggerJsPrompt">
    Trigger JavaScript Prompt
</button>

<h3 id="welcome" style="color:green;font-style:italic"></h3>

@code {
    public async Task TriggerJsPrompt()
    {
        // showPrompt is implemented in wwwroot/exampleJsInterop.js
        var name = await JSRuntime.InvokeAsync<string>(
                "exampleJsFunctions.showPrompt",
                "What's your name?");
        // displayWelcome is implemented in wwwroot/exampleJsInterop.js
        await JSRuntime.InvokeVoidAsync(
                "exampleJsFunctions.displayWelcome",
                $"Hello {name}! Welcome to Blazor!");
    }
}
  1. Lorsque TriggerJsPrompt est exécuté en sélectionnant le bouton d' invite de commandes JavaScript du composant, la fonction JavaScript showPrompt fournie dans le fichier wwwroot/exampleJsInterop. js est appelée.When TriggerJsPrompt is executed by selecting the component's Trigger JavaScript Prompt button, the JavaScript showPrompt function provided in the wwwroot/exampleJsInterop.js file is called.
  2. La fonction showPrompt accepte l’entrée d’utilisateur (nom de l’utilisateur), qui est codée au format HTML et renvoyée au composant.The showPrompt function accepts user input (the user's name), which is HTML-encoded and returned to the component. Le composant stocke le nom de l’utilisateur dans une variable locale, name.The component stores the user's name in a local variable, name.
  3. La chaîne stockée dans name est incorporée dans un message de bienvenue, qui est transmis à une fonction JavaScript, displayWelcome, qui affiche le message de bienvenue dans une balise d’en-tête.The string stored in name is incorporated into a welcome message, which is passed to a JavaScript function, displayWelcome, which renders the welcome message into a heading tag.

Appeler une fonction JavaScript voidCall a void JavaScript function

Les fonctions JavaScript qui retournent void (0)/void 0 ou non définies sont appelées avec IJSRuntime.InvokeVoidAsync.JavaScript functions that return void(0)/void 0 or undefined are called with IJSRuntime.InvokeVoidAsync.

Détecter quand un Blazor application est prérenduDetect when a Blazor app is prerendering

Bien qu’une application Blazor Server soit prérestitué, certaines actions, telles que l’appel en JavaScript, ne sont pas possibles, car une connexion avec le navigateur n’a pas été établie.While a Blazor Server app is prerendering, certain actions, such as calling into JavaScript, aren't possible because a connection with the browser hasn't been established. Les composants peuvent avoir besoin d’être restitués différemment lorsqu’ils sont prérendus.Components may need to render differently when prerendered.

Pour différer les appels Interop JavaScript jusqu’à ce que la connexion avec le navigateur soit établie, vous pouvez utiliser l' événement du cycle de vie du composant OnAfterRenderAsync.To delay JavaScript interop calls until after the connection with the browser is established, you can use the OnAfterRenderAsync component lifecycle event. Cet événement est appelé uniquement une fois que l’application est entièrement rendue et que la connexion cliente est établie.This event is only called after the app is fully rendered and the client connection is established.

@using Microsoft.JSInterop
@inject IJSRuntime JSRuntime

<div @ref="divElement">Text during render</div>

@code {
    private ElementReference divElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await JSRuntime.InvokeVoidAsync(
                "setElementText", divElement, "Text after render");
        }
    }
}

Pour l’exemple de code précédent, fournissez une fonction JavaScript setElementText à l’intérieur de l’élément <head> de wwwroot/index.html (Blazor webassembly) ou pages/_Host. cshtml (serveurBlazor).For the preceding example code, provide a setElementText JavaScript function inside the <head> element of wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server). La fonction est appelée avec IJSRuntime.InvokeVoidAsync et ne retourne pas de valeur :The function is called with IJSRuntime.InvokeVoidAsync and doesn't return a value:

<script>
  window.setElementText = (element, text) => element.innerText = text;
</script>

Avertissement

L’exemple précédent modifie directement le Document Object Model (DOM) à des fins de démonstration uniquement.The preceding example modifies the Document Object Model (DOM) directly for demonstration purposes only. La modification directe du DOM avec JavaScript n’est pas recommandée dans la plupart des scénarios, car JavaScript peut interférer avec le suivi des modifications de Blazor.Directly modifying the DOM with JavaScript isn't recommended in most scenarios because JavaScript can interfere with Blazor's change tracking.

Le composant suivant montre comment utiliser l’interopérabilité JavaScript dans le cadre de la logique d’initialisation d’un composant d’une manière compatible avec le prérendu.The following component demonstrates how to use JavaScript interop as part of a component's initialization logic in a way that's compatible with prerendering. Le composant montre qu’il est possible de déclencher une mise à jour de rendu depuis l’intérieur de OnAfterRenderAsync.The component shows that it's possible to trigger a rendering update from inside OnAfterRenderAsync. Le développeur doit éviter de créer une boucle infinie dans ce scénario.The developer must avoid creating an infinite loop in this scenario.

Lorsque JSRuntime.InvokeAsync est appelée, ElementRef est utilisé uniquement dans OnAfterRenderAsync et non dans une méthode de cycle de vie antérieure, car il n’y a pas d’élément JavaScript tant que le composant n’est pas rendu.Where JSRuntime.InvokeAsync is called, ElementRef is only used in OnAfterRenderAsync and not in any earlier lifecycle method because there's no JavaScript element until after the component is rendered.

StateHasChanged est appelé pour rerestituer le composant avec le nouvel état obtenu à partir de l’appel JavaScript Interop.StateHasChanged is called to rerender the component with the new state obtained from the JavaScript interop call. Le code ne crée pas de boucle infinie car StateHasChanged est appelé uniquement lorsque infoFromJs est null.The code doesn't create an infinite loop because StateHasChanged is only called when infoFromJs is null.

@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JSRuntime

<p>
    Get value via JS interop call:
    <strong id="val-get-by-interop">@(infoFromJs ?? "No value yet")</strong>
</p>

Set value via JS interop call:
<div id="val-set-by-interop" @ref="divElement"></div>

@code {
    private string infoFromJs;
    private ElementReference divElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && infoFromJs == null)
        {
            infoFromJs = await JSRuntime.InvokeAsync<string>(
                "setElementText", divElement, "Hello from interop call!");

            StateHasChanged();
        }
    }
}

Pour l’exemple de code précédent, fournissez une fonction JavaScript setElementText à l’intérieur de l’élément <head> de wwwroot/index.html (Blazor webassembly) ou pages/_Host. cshtml (serveurBlazor).For the preceding example code, provide a setElementText JavaScript function inside the <head> element of wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server). La fonction est appelée avec IJSRuntime.InvokeAsync et retourne une valeur :The function is called with IJSRuntime.InvokeAsync and returns a value:

<script>
  window.setElementText = (element, text) => {
    element.innerText = text;
    return text;
  };
</script>

Avertissement

L’exemple précédent modifie directement le Document Object Model (DOM) à des fins de démonstration uniquement.The preceding example modifies the Document Object Model (DOM) directly for demonstration purposes only. La modification directe du DOM avec JavaScript n’est pas recommandée dans la plupart des scénarios, car JavaScript peut interférer avec le suivi des modifications de Blazor.Directly modifying the DOM with JavaScript isn't recommended in most scenarios because JavaScript can interfere with Blazor's change tracking.

Capturer des références à des élémentsCapture references to elements

Certains scénarios d’interopérabilité JS requièrent des références à des éléments HTML.Some JS interop scenarios require references to HTML elements. Par exemple, une bibliothèque d’interface utilisateur peut nécessiter une référence d’élément pour l’initialisation, ou vous devrez peut-être appeler des API de type commande sur un élément, par exemple focus ou play.For example, a UI library may require an element reference for initialization, or you might need to call command-like APIs on an element, such as focus or play.

Capturer des références aux éléments HTML dans un composant à l’aide de l’approche suivante :Capture references to HTML elements in a component using the following approach:

  • Ajoutez un attribut @ref à l’élément HTML.Add an @ref attribute to the HTML element.
  • Définissez un champ de type ElementReference dont le nom correspond à la valeur de l’attribut @ref.Define a field of type ElementReference whose name matches the value of the @ref attribute.

L’exemple suivant illustre la capture d’une référence à l’élément username <input> :The following example shows capturing a reference to the username <input> element:

<input @ref="username" ... />

@code {
    ElementReference username;
}

Avertissement

Utilisez uniquement une référence d’élément pour muter le contenu d’un élément vide qui n’interagit pas avec Blazor.Only use an element reference to mutate the contents of an empty element that doesn't interact with Blazor. Ce scénario est utile quand une API tierce fournit du contenu à l’élément.This scenario is useful when a 3rd party API supplies content to the element. Étant donné que Blazor n’interagit pas avec l’élément, il n’existe aucun risque de conflit entre la représentation de Blazorde l’élément et le DOM.Because Blazor doesn't interact with the element, there's no possibility of a conflict between Blazor's representation of the element and the DOM.

Dans l’exemple suivant, il est dangereux de faire muter le contenu de la liste non triée (ul), car Blazor interagit avec le DOM pour remplir les éléments de liste de cet élément (<li>) :In the following example, it's dangerous to mutate the contents of the unordered list (ul) because Blazor interacts with the DOM to populate this element's list items (<li>):

<ul ref="MyList">
    @foreach (var item in Todos)
    {
        <li>@item.Text</li>
    }
</ul>

Si l’interopérabilité JS muter le contenu de l’élément MyList et Blazor tente d’appliquer des différences à l’élément, les différences ne correspondent pas au DOM.If JS interop mutates the contents of element MyList and Blazor attempts to apply diffs to the element, the diffs won't match the DOM.

En ce qui concerne le code .NET, un ElementReference est un handle opaque.As far as .NET code is concerned, an ElementReference is an opaque handle. La seule chose que vous pouvez faire avec ElementReference est de la passer au code JavaScript via l’interopérabilité js.The only thing you can do with ElementReference is pass it through to JavaScript code via JS interop. Dans ce cas, le code JavaScript reçoit une instance HTMLElement, qu’il peut utiliser avec les API DOM normales.When you do so, the JavaScript-side code receives an HTMLElement instance, which it can use with normal DOM APIs.

Par exemple, le code suivant définit une méthode d’extension .NET qui permet de définir le focus sur un élément :For example, the following code defines a .NET extension method that enables setting the focus on an element:

exampleJsInterop. js:exampleJsInterop.js:

window.exampleJsFunctions = {
  focusElement : function (element) {
    element.focus();
  }
}

Pour appeler une fonction JavaScript qui ne retourne pas de valeur, utilisez IJSRuntime.InvokeVoidAsync.To call a JavaScript function that doesn't return a value, use IJSRuntime.InvokeVoidAsync. Le code suivant définit le focus sur l’entrée de nom d’utilisateur en appelant la fonction JavaScript précédente avec l' ElementReferencecapturée :The following code sets the focus on the username input by calling the preceding JavaScript function with the captured ElementReference:

@inject IJSRuntime JSRuntime

<input @ref="_username" />
<button @onclick="SetFocus">Set focus on username</button>

@code {
    private ElementReference _username;

    public async Task SetFocus()
    {
        await JSRuntime.InvokeVoidAsync(
            "exampleJsFunctions.focusElement", _username);
    }
}

Pour utiliser une méthode d’extension, créez une méthode d’extension statique qui reçoit l’instance IJSRuntime :To use an extension method, create a static extension method that receives the IJSRuntime instance:

public static async Task Focus(this ElementReference elementRef, IJSRuntime jsRuntime)
{
    await jsRuntime.InvokeVoidAsync(
        "exampleJsFunctions.focusElement", elementRef);
}

La méthode Focus est appelée directement sur l’objet.The Focus method is called directly on the object. L’exemple suivant suppose que la méthode Focus est disponible à partir de l’espace de noms JsInteropClasses :The following example assumes that the Focus method is available from the JsInteropClasses namespace:

@inject IJSRuntime JSRuntime
@using JsInteropClasses

<input @ref="_username" />
<button @onclick="SetFocus">Set focus on username</button>

@code {
    private ElementReference _username;

    public async Task SetFocus()
    {
        await _username.Focus(JSRuntime);
    }
}

Important

La variable username n’est remplie qu’après le rendu du composant.The username variable is only populated after the component is rendered. Si un ElementReference non rempli est passé au code JavaScript, le code JavaScript reçoit la valeur null.If an unpopulated ElementReference is passed to JavaScript code, the JavaScript code receives a value of null. Pour manipuler des références d’élément après que le composant a terminé le rendu (pour définir le focus initial sur un élément), utilisez les méthodes de cycle de vie des composants OnAfterRenderAsync ou OnAfterRender.To manipulate element references after the component has finished rendering (to set the initial focus on an element) use the OnAfterRenderAsync or OnAfterRender component lifecycle methods.

Quand vous utilisez des types génériques et que vous retournez une valeur, utilisez ValueTask<t >:When working with generic types and returning a value, use ValueTask<T>:

public static ValueTask<T> GenericMethod<T>(this ElementReference elementRef, 
    IJSRuntime jsRuntime)
{
    return jsRuntime.InvokeAsync<T>(
        "exampleJsFunctions.doSomethingGeneric", elementRef);
}

GenericMethod est appelé directement sur l’objet avec un type.GenericMethod is called directly on the object with a type. L’exemple suivant suppose que la GenericMethod est disponible à partir de l’espace de noms JsInteropClasses :The following example assumes that the GenericMethod is available from the JsInteropClasses namespace:

@inject IJSRuntime JSRuntime
@using JsInteropClasses

<input @ref="_username" />
<button @onclick="OnClickMethod">Do something generic</button>

<p>
    _returnValue: @_returnValue
</p>

@code {
    private ElementReference _username;
    private string _returnValue;

    private async Task OnClickMethod()
    {
        _returnValue = await _username.GenericMethod<string>(JSRuntime);
    }
}

Éléments de référence sur les composantsReference elements across components

Un ElementReference est garanti uniquement valide dans la méthode OnAfterRender d’un composant (et une référence d’élément est un struct), de sorte qu’une référence d’élément ne peut pas être transmise entre les composants.An ElementReference is only guaranteed valid in a component's OnAfterRender method (and an element reference is a struct), so an element reference can't be passed between components.

Pour qu’un composant parent rende une référence d’élément disponible pour d’autres composants, le composant parent peut :For a parent component to make an element reference available to other components, the parent component can:

  • Autorisez les composants enfants à inscrire des rappels.Allow child components to register callbacks.
  • Appelez les rappels inscrits pendant l’événement OnAfterRender avec la référence d’élément passée.Invoke the registered callbacks during the OnAfterRender event with the passed element reference. Indirectement, cette approche permet aux composants enfants d’interagir avec la référence d’élément du parent.Indirectly, this approach allows child components to interact with the parent's element reference.

L’exemple de Blazor webassembly suivant illustre l’approche.The following Blazor WebAssembly example illustrates the approach.

Dans le <head> de wwwroot/index.html:In the <head> of wwwroot/index.html:

<style>
    .red { color: red }
</style>

Dans le <body> de wwwroot/index.html:In the <body> of wwwroot/index.html:

<script>
    function setElementClass(element, className) {
        /** @type {HTMLElement} **/
        var myElement = element;
        myElement.classList.add(className);
    }
</script>

Pages/index. Razor (composant parent) :Pages/Index.razor (parent component):

@page "/"

<h1 @ref="_title">Hello, world!</h1>

Welcome to your new app.

<SurveyPrompt Parent="this" Title="How is Blazor working for you?" />

Pages/index. Razor. cs:Pages/Index.razor.cs:

using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;

namespace BlazorSample.Pages
{
    public partial class Index : 
        ComponentBase, IObservable<ElementReference>, IDisposable
    {
        private bool _disposing;
        private IList<IObserver<ElementReference>> _subscriptions = 
            new List<IObserver<ElementReference>>();
        private ElementReference _title;

        protected override void OnAfterRender(bool firstRender)
        {
            base.OnAfterRender(firstRender);

            foreach (var subscription in _subscriptions)
            {
                try
                {
                    subscription.OnNext(_title);
                }
                catch (Exception)
                {
                    throw;
                }
            }
        }

        public void Dispose()
        {
            _disposing = true;

            foreach (var subscription in _subscriptions)
            {
                try
                {
                    subscription.OnCompleted();
                }
                catch (Exception)
                {
                }
            }

            _subscriptions.Clear();
        }

        public IDisposable Subscribe(IObserver<ElementReference> observer)
        {
            if (_disposing)
            {
                throw new InvalidOperationException("Parent being disposed");
            }

            _subscriptions.Add(observer);

            return new Subscription(observer, this);
        }

        private class Subscription : IDisposable
        {
            public Subscription(IObserver<ElementReference> observer, Index self)
            {
                Observer = observer;
                Self = self;
            }

            public IObserver<ElementReference> Observer { get; }
            public Index Self { get; }

            public void Dispose()
            {
                Self._subscriptions.Remove(Observer);
            }
        }
    }
}

Shared/SurveyPrompt. Razor (composant enfant) :Shared/SurveyPrompt.razor (child component):

@inject IJSRuntime JS

<div class="alert alert-secondary mt-4" role="alert">
    <span class="oi oi-pencil mr-2" aria-hidden="true"></span>
    <strong>@Title</strong>

    <span class="text-nowrap">
        Please take our
        <a target="_blank" class="font-weight-bold" 
            href="https://go.microsoft.com/fwlink/?linkid=2109206">brief survey</a>
    </span>
    and tell us what you think.
</div>

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

Shared/SurveyPrompt. Razor. cs:Shared/SurveyPrompt.razor.cs:

using System;
using Microsoft.AspNetCore.Components;

namespace BlazorSample.Shared
{
    public partial class SurveyPrompt : 
        ComponentBase, IObserver<ElementReference>, IDisposable
    {
        private IDisposable _subscription = null;

        [Parameter]
        public IObservable<ElementReference> Parent { get; set; }

        protected override void OnParametersSet()
        {
            base.OnParametersSet();

            if (_subscription != null)
            {
                _subscription.Dispose();
            }

            _subscription = Parent.Subscribe(this);
        }

        public void OnCompleted()
        {
            _subscription = null;
        }

        public void OnError(Exception error)
        {
            _subscription = null;
        }

        public void OnNext(ElementReference value)
        {
            JS.InvokeAsync<object>(
                "setElementClass", new object[] { value, "red" });
        }

        public void Dispose()
        {
            _subscription?.Dispose();
        }
    }
}

Appeler des méthodes .NET à partir de fonctions JavaScriptInvoke .NET methods from JavaScript functions

Appel de méthode .NET statiqueStatic .NET method call

Pour appeler une méthode .NET statique à partir de JavaScript, utilisez les fonctions DotNet.invokeMethod ou DotNet.invokeMethodAsync.To invoke a static .NET method from JavaScript, use the DotNet.invokeMethod or DotNet.invokeMethodAsync functions. Transmettez l’identificateur de la méthode statique que vous souhaitez appeler, le nom de l’assembly contenant la fonction et les arguments éventuels.Pass in the identifier of the static method you wish to call, the name of the assembly containing the function, and any arguments. La version asynchrone est recommandée pour prendre en charge les scénarios de serveur Blazor.The asynchronous version is preferred to support Blazor Server scenarios. La méthode .NET doit être publique, statique et avoir l’attribut [JSInvokable].The .NET method must be public, static, and have the [JSInvokable] attribute. L’appel de méthodes génériques ouvertes n’est pas pris en charge actuellement.Calling open generic methods isn't currently supported.

L’exemple d’application comprend C# une méthode pour retourner un tableau de ints.The sample app includes a C# method to return an array of ints. L’attribut JSInvokable est appliqué à la méthode.The JSInvokable attribute is applied to the method.

Pages/JsInterop. Razor:Pages/JsInterop.razor:

<button type="button" class="btn btn-primary"
        onclick="exampleJsFunctions.returnArrayAsyncJs()">
    Trigger .NET static method ReturnArrayAsync
</button>

@code {
    [JSInvokable]
    public static Task<int[]> ReturnArrayAsync()
    {
        return Task.FromResult(new int[] { 1, 2, 3 });
    }
}

JavaScript traité au client appelle la C# méthode .net.JavaScript served to the client invokes the C# .NET method.

wwwroot/exampleJsInterop. js:wwwroot/exampleJsInterop.js:

window.exampleJsFunctions = {
  showPrompt: function (text) {
    return prompt(text, 'Type your name here');
  },
  displayWelcome: function (welcomeMessage) {
    document.getElementById('welcome').innerText = welcomeMessage;
  },
  returnArrayAsyncJs: function () {
    DotNet.invokeMethodAsync('BlazorSample', 'ReturnArrayAsync')
      .then(data => {
        data.push(4);
          console.log(data);
      });
  },
  sayHello: function (dotnetHelper) {
    return dotnetHelper.invokeMethodAsync('SayHello')
      .then(r => console.log(r));
  }
};

Quand le bouton déclencher l’ReturnArrayAsync de la méthode statique .net est sélectionné, examinez la sortie de la console dans les outils de développement Web du navigateur.When the Trigger .NET static method ReturnArrayAsync button is selected, examine the console output in the browser's web developer tools.

La sortie de la console est la suivante :The console output is:

Array(4) [ 1, 2, 3, 4 ]

La quatrième valeur de tableau fait l’objet d’un push dans le tableau (data.push(4);) retourné par ReturnArrayAsync.The fourth array value is pushed to the array (data.push(4);) returned by ReturnArrayAsync.

Par défaut, l’identificateur de méthode est le nom de la méthode, mais vous pouvez spécifier un identificateur différent à l’aide du constructeur JSInvokableAttribute :By default, the method identifier is the method name, but you can specify a different identifier using the JSInvokableAttribute constructor:

@code {
    [JSInvokable("DifferentMethodName")]
    public static Task<int[]> ReturnArrayAsync()
    {
        return Task.FromResult(new int[] { 1, 2, 3 });
    }
}

Dans le fichier JavaScript côté client :In the client-side JavaScript file:

returnArrayAsyncJs: function () {
  DotNet.invokeMethodAsync('BlazorSample', 'DifferentMethodName')
    .then(data => {
      data.push(4);
      console.log(data);
    });
}

Appel de méthode d’instanceInstance method call

Vous pouvez également appeler des méthodes d’instance .NET à partir de JavaScript.You can also call .NET instance methods from JavaScript. Pour appeler une méthode d’instance .NET à partir de JavaScript :To invoke a .NET instance method from JavaScript:

  • Transmettez l’instance .NET à JavaScript en l’encapsulant dans une instance de DotNetObjectReference.Pass the .NET instance to JavaScript by wrapping it in a DotNetObjectReference instance. L’instance .NET est passée par référence à JavaScript.The .NET instance is passed by reference to JavaScript.
  • Appeler des méthodes d’instance .NET sur l’instance à l’aide des fonctions invokeMethod ou invokeMethodAsync.Invoke .NET instance methods on the instance using the invokeMethod or invokeMethodAsync functions. L’instance .NET peut également être passée comme argument lors de l’appel d’autres méthodes .NET à partir de JavaScript.The .NET instance can also be passed as an argument when invoking other .NET methods from JavaScript.

Notes

L’exemple d’application enregistre les messages dans la console côté client.The sample app logs messages to the client-side console. Pour les exemples suivants présentés dans l’exemple d’application, examinez la sortie de console du navigateur dans les outils de développement du navigateur.For the following examples demonstrated by the sample app, examine the browser's console output in the browser's developer tools.

Quand le bouton de méthode d’instance .net de déclenchement HelloHelper. SayHello est sélectionné, ExampleJsInterop.CallHelloHelperSayHello est appelé et passe un nom, Blazor, à la méthode.When the Trigger .NET instance method HelloHelper.SayHello button is selected, ExampleJsInterop.CallHelloHelperSayHello is called and passes a name, Blazor, to the method.

Pages/JsInterop. Razor:Pages/JsInterop.razor:

<button type="button" class="btn btn-primary" @onclick="TriggerNetInstanceMethod">
    Trigger .NET instance method HelloHelper.SayHello
</button>

@code {
    public async Task TriggerNetInstanceMethod()
    {
        var exampleJsInterop = new ExampleJsInterop(JSRuntime);
        await exampleJsInterop.CallHelloHelperSayHello("Blazor");
    }
}

CallHelloHelperSayHello appelle la fonction JavaScript sayHello avec une nouvelle instance de HelloHelper.CallHelloHelperSayHello invokes the JavaScript function sayHello with a new instance of HelloHelper.

JsInteropClasses/ExampleJsInterop. cs:JsInteropClasses/ExampleJsInterop.cs:

public class ExampleJsInterop
{
    private readonly IJSRuntime _jsRuntime;

    public ExampleJsInterop(IJSRuntime jsRuntime)
    {
        _jsRuntime = jsRuntime;
    }

    public ValueTask<string> CallHelloHelperSayHello(string name)
    {
        // sayHello is implemented in wwwroot/exampleJsInterop.js
        return _jsRuntime.InvokeAsync<string>(
            "exampleJsFunctions.sayHello",
            DotNetObjectReference.Create(new HelloHelper(name)));
    }
}

wwwroot/exampleJsInterop. js:wwwroot/exampleJsInterop.js:

window.exampleJsFunctions = {
  showPrompt: function (text) {
    return prompt(text, 'Type your name here');
  },
  displayWelcome: function (welcomeMessage) {
    document.getElementById('welcome').innerText = welcomeMessage;
  },
  returnArrayAsyncJs: function () {
    DotNet.invokeMethodAsync('BlazorSample', 'ReturnArrayAsync')
      .then(data => {
        data.push(4);
          console.log(data);
      });
  },
  sayHello: function (dotnetHelper) {
    return dotnetHelper.invokeMethodAsync('SayHello')
      .then(r => console.log(r));
  }
};

Le nom est passé au constructeur de HelloHelper, qui définit la propriété HelloHelper.Name.The name is passed to HelloHelper's constructor, which sets the HelloHelper.Name property. Lorsque la fonction JavaScript sayHello est exécutée, HelloHelper.SayHello retourne le message Hello, {Name}!, qui est écrit dans la console par la fonction JavaScript.When the JavaScript function sayHello is executed, HelloHelper.SayHello returns the Hello, {Name}! message, which is written to the console by the JavaScript function.

JsInteropClasses/HelloHelper. cs:JsInteropClasses/HelloHelper.cs:

public class HelloHelper
{
    public HelloHelper(string name)
    {
        Name = name;
    }

    public string Name { get; set; }

    [JSInvokable]
    public string SayHello() => $"Hello, {Name}!";
}

Sortie de la console dans les outils de développement Web du navigateur :Console output in the browser's web developer tools:

Hello, Blazor!

Partager du code d’interopérabilité dans une bibliothèque de classesShare interop code in a class library

Le code d’interopérabilité JS peut être inclus dans une bibliothèque de classes, ce qui vous permet de partager le code dans un package NuGet.JS interop code can be included in a class library, which allows you to share the code in a NuGet package.

La bibliothèque de classes gère l’incorporation des ressources JavaScript dans l’assembly généré.The class library handles embedding JavaScript resources in the built assembly. Les fichiers JavaScript sont placés dans le dossier wwwroot .The JavaScript files are placed in the wwwroot folder. Les outils s’occupent de l’incorporation des ressources lors de la génération de la bibliothèque.The tooling takes care of embedding the resources when the library is built.

Le package NuGet créé est référencé dans le fichier projet de l’application de la même façon que n’importe quel package NuGet.The built NuGet package is referenced in the app's project file the same way that any NuGet package is referenced. Une fois le package restauré, le code d’application peut appeler JavaScript comme s’il C#avait été.After the package is restored, app code can call into JavaScript as if it were C#.

Pour plus d’informations, consultez Bibliothèques de classes des composants Razor ASP.NET Core.For more information, see Bibliothèques de classes des composants Razor ASP.NET Core.

Sécuriser les appels d’interopérabilité JSHarden JS interop calls

L’interopérabilité de JS peut échouer en raison d’erreurs réseau et doit être considérée comme non fiable.JS interop may fail due to networking errors and should be treated as unreliable. Par défaut, une application Blazor Server expire les appels d’interopérabilité JS sur le serveur après une minute.By default, a Blazor Server app times out JS interop calls on the server after one minute. Si une application peut tolérer un délai d’expiration plus agressif, par exemple 10 secondes, définissez le délai d’expiration à l’aide de l’une des approches suivantes :If an app can tolerate a more aggressive timeout, such as 10 seconds, set the timeout using one of the following approaches:

  • Globalement dans Startup.ConfigureServices, spécifiez le délai d’expiration :Globally in Startup.ConfigureServices, specify the timeout:

    services.AddServerSideBlazor(
        options => options.JSInteropDefaultCallTimeout = TimeSpan.FromSeconds({SECONDS}));
    
  • Par appel dans le code du composant, un appel unique peut spécifier le délai d’attente :Per-invocation in component code, a single call can specify the timeout:

    var result = await JSRuntime.InvokeAsync<string>("MyJSOperation", 
        TimeSpan.FromSeconds({SECONDS}), new[] { "Arg1" });
    

Pour plus d’informations sur l’épuisement des ressources, consultez Sécuriser les applications ASP.NET Core Blazor Server.For more information on resource exhaustion, see Sécuriser les applications ASP.NET Core Blazor Server.

Effectuer des transferts de données volumineux dans des applications Blazor ServerPerform large data transfers in Blazor Server apps

Dans certains scénarios, de grandes quantités de données doivent être transférées entre JavaScript et Blazor.In some scenarios, large amounts of data must be transferred between JavaScript and Blazor. En général, les transferts de données volumineux se produisent dans les cas suivants :Typically, large data transfers occur when:

  • Les API du système de fichiers du navigateur sont utilisées pour charger ou télécharger un fichier.Browser file system APIs are used to upload or download a file.
  • L’interopérabilité avec une bibliothèque tierce est nécessaire.Interop with a third party library is required.

Dans Blazor Server, une limitation est en place pour empêcher le passage de messages volumineux uniques susceptibles d’entraîner des problèmes de performances.In Blazor Server, a limitation is in place to prevent passing single large messages that may result in performance issues.

Tenez compte des conseils suivants lors du développement de code qui transfère des données entre JavaScript et Blazor:Consider the following guidance when developing code that transfers data between JavaScript and Blazor:

  • Découpez les données en éléments plus petits et envoyez les segments de données de façon séquentielle jusqu’à ce que toutes les données soient reçues par le serveur.Slice the data into smaller pieces, and send the data segments sequentially until all of the data is received by the server.
  • N’allouez pas d’objets volumineux C# en JavaScript et dans du code.Don't allocate large objects in JavaScript and C# code.
  • Ne bloquez pas le thread d’interface utilisateur principal pendant de longues périodes lors de l’envoi ou de la réception de données.Don't block the main UI thread for long periods when sending or receiving data.
  • Libérez la mémoire consommée lorsque le processus est terminé ou annulé.Free any memory consumed when the process is completed or cancelled.
  • Appliquer les exigences supplémentaires suivantes pour des raisons de sécurité :Enforce the following additional requirements for security purposes:
    • Déclarez la taille maximale du fichier ou des données qui peut être passée.Declare the maximum file or data size that can be passed.
    • Déclarez le taux de téléchargement minimal du client au serveur.Declare the minimum upload rate from the client to the server.
  • Une fois les données reçues par le serveur, les données peuvent être :After the data is received by the server, the data can be:
    • Stocké temporairement dans une mémoire tampon jusqu’à ce que tous les segments soient collectés.Temporarily stored in a memory buffer until all of the segments are collected.
    • Consommé immédiatement.Consumed immediately. Par exemple, les données peuvent être stockées immédiatement dans une base de données ou écrites sur le disque à mesure que chaque segment est reçu.For example, the data can be stored immediately in a database or written to disk as each segment is received.

La classe de chargeur de fichiers suivante gère l’interopérabilité de JS avec le client.The following file uploader class handles JS interop with the client. La classe de chargeur utilise l’interopérabilité JS pour :The uploader class uses JS interop to:

  • Interroger le client pour envoyer un segment de données.Poll the client to send a data segment.
  • Abandonner la transaction si le délai d’interrogation est dépassé.Abort 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)
    {
        _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();
            }
        }
    }
}

Dans l'exemple précédent :In the preceding example:

  • Le _maxBase64SegmentSize est défini sur 8192, calculé à partir de _maxBase64SegmentSize = _segmentSize * 4 / 3.The _maxBase64SegmentSize is set to 8192, which is calculated from _maxBase64SegmentSize = _segmentSize * 4 / 3.
  • Les API de gestion de mémoire .NET Core de bas niveau sont utilisées pour stocker les segments de mémoire sur le serveur dans _uploadedSegments.Low level .NET Core memory management APIs are used to store the memory segments on the server in _uploadedSegments.
  • Une méthode ReceiveFile est utilisée pour gérer le téléchargement via l’interopérabilité JS :A ReceiveFile method is used to handle the upload through JS interop:
    • La taille du fichier est déterminée en octets par le biais de l’interopérabilité JS avec _jsRuntime.InvokeAsync<FileInfo>('getFileSize', selector).The file size is determined in bytes through JS interop with _jsRuntime.InvokeAsync<FileInfo>('getFileSize', selector).
    • Le nombre de segments à recevoir est calculé et stocké dans numberOfSegments.The number of segments to receive are calculated and stored in numberOfSegments.
    • Les segments sont demandés dans une boucle for via l’interopérabilité JS avec _jsRuntime.InvokeAsync<string>('receiveSegment', i, selector).The segments are requested in a for loop through JS interop with _jsRuntime.InvokeAsync<string>('receiveSegment', i, selector). Tous les segments, mais le dernier doit être de 8 192 octets avant le décodage.All segments but the last must be 8,192 bytes before decoding. Le client est contraint d’envoyer les données de manière efficace.The client is forced to send the data in an efficient manner.
    • Pour chaque segment reçu, les vérifications sont effectuées avant le décodage avec TryFromBase64String.For each segment received, checks are performed before decoding with TryFromBase64String.
    • Un flux avec les données est retourné sous la forme d’un nouveau Stream (SegmentedStream) une fois le téléchargement terminé.A stream with the data is returned as a new Stream (SegmentedStream) after the upload is complete.

La classe de flux segmentée expose la liste de segments sous la forme d’un StreamReadOnly non identifiable :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();
}

Le code suivant implémente les fonctions JavaScript pour recevoir les données :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;
}

Ressources supplémentairesAdditional resources