Volání funkcí JavaScriptu z metod .NET v ASP.NET Core Blazor

Tento článek vysvětluje, jak vyvolat funkce JavaScriptu (JS) z .NET.

Informace o volání metod .NET z JS, naleznete v tématu Volání metod .NET z funkcí JavaScriptu v ASP.NET Core Blazor.

IJSRuntime je registrován v Blazor rámci. Pokud chcete volat JS z .NET, vložte IJSRuntime abstrakci a zavolejte jednu z následujících metod:

Pro předchozí metody .NET, které volají JS funkce:

  • Identifikátor funkce (String) je relativní k globálnímu oboru (window). Zavolat window.someScope.someFunction, identifikátor je someScope.someFunction. Před zavolání není nutné funkci zaregistrovat.
  • Předejte funkci libovolný počet argumentů JSObject[]JS typu ON-serializable.
  • TokenCancellationToken zrušení () šíří oznámení, že operace by se měly zrušit.
  • TimeSpan představuje časový limit pro JS operaci.
  • Návratový TValue typ musí být JStaké zapnutý serializovatelný. TValue měl by odpovídat typu .NET, který se nejlépe mapuje na vrácený JStyp ON.
  • Funkce A JS Promise je vrácena pro InvokeAsync metody. InvokeAsync rozbalí Promise a vrátí hodnotu očekávanou Promisehodnotou .

U Blazor Server aplikací s povoleným prerenderingem není volání JS během počátečního předkreslování možné. JS volání interop musí být odložena, dokud se připojení k prohlížeči nenaváže. Další informace najdete v části Zjistit, kdy Blazor Server je aplikace předem připravená .

Následující příklad je založený na dekodéru založeném JSna TextDecoderbázi . Příklad ukazuje, jak vyvolat JS funkci z metody jazyka C#, která předčítá požadavek z kódu vývojáře na existující JS rozhraní API. Funkce JS přijímá bajtové pole z metody jazyka C#, dekóduje pole a vrátí text do komponenty pro zobrazení.

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

Poznámka

Obecné pokyny k JS umístění a doporučení pro produkční aplikace najdete v tématu ASP.NET Core Blazor interoperabilita JavaScriptu (JSinterop)..

CallJsExample1 Následující komponenta:

  • convertArrayJS Vyvolá funkci při InvokeAsync výběru tlačítka (Convert Array).
  • JS Po zavolání funkce se předané pole převede na řetězec. Řetězec se vrátí do komponenty pro zobrazení (text).

Pages/CallJsExample1.razor:

@page "/call-js-example-1"
@inject IJSRuntime JS

<h1>Call JS <code>convertArray</code> Function</h1>

<p>
    <button @onclick="ConvertArray">Convert Array</button>
</p>

<p>
    @text
</p>

<p>
    Quote ©2005 <a href="https://www.uphe.com">Universal Pictures</a>: 
    <a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
    <a href="https://www.imdb.com/name/nm0472710/">David Krumholtz on IMDB</a>
</p>

@code {
    private MarkupString text;

    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()
    {
        text = new(await JS.InvokeAsync<string>("convertArray", quoteArray));
    }
}

Rozhraní JAVAScript API omezené na gesta uživatelů

Tato část platí jenom pro Blazor Server aplikace.

Některá rozhraní JAVAScript (JS) api prohlížeče se dají spustit jenom v kontextu uživatelského gesta, například pomocí Fullscreen API (dokumentace k MDN). Tato rozhraní API nelze volat prostřednictvím JS mechanismu vzájemné komunikace v Blazor Server aplikacích, protože zpracování událostí uživatelského rozhraní se provádí asynchronně a obecně už v kontextu gesta uživatele. Aplikace musí zpracovat událost uživatelského rozhraní zcela v JavaScriptu, takže místo atributu @onclick direktivy použijte onclick (dokumentaciBlazorMDN).

Vyvolání funkcí JavaScriptu bez čtení vrácené hodnoty (InvokeVoidAsync)

Použít InvokeVoidAsync , když:

displayTickerAlert1JS Zadejte funkci. Funkce se volá a InvokeVoidAsync nevrací hodnotu:

<script>
  window.displayTickerAlert1 = (symbol, price) => {
    alert(`${symbol}: $${price}!`);
  };
</script>

Poznámka

Obecné pokyny k JS umístění a doporučení pro produkční aplikace najdete v tématu ASP.NET Core Blazor interoperabilita JavaScriptu (JSinterop)..

Příklad komponenty () (.razorInvokeVoidAsync)

TickerChanged volá metodu handleTickerChanged1 v následující CallJsExample2 komponentě.

Pages/CallJsExample2.razor:

@page "/call-js-example-2"
@inject IJSRuntime JS

<h1>Call JS Example 2</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
    private Random r = new();
    private string? stockSymbol;
    private decimal price;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
    }
}

Příklad třídy (.cs) (InvokeVoidAsync)

JsInteropClasses1.cs:

using Microsoft.JSInterop;

public class JsInteropClasses1
{
    private readonly IJSRuntime js;

    public JsInteropClasses1(IJSRuntime js)
    {
        this.js = js;
    }

    public async ValueTask TickerChanged(string symbol, decimal price)
    {
        await js.InvokeVoidAsync("displayTickerAlert1", symbol, price);
    }

    public void Dispose()
    {
    }
}

TickerChanged volá metodu handleTickerChanged1 v následující CallJsExample3 komponentě.

Pages/CallJsExample3.razor:

@page "/call-js-example-3"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call JS Example 3</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
    private Random r = new();
    private string? stockSymbol;
    private decimal price;
    private JsInteropClasses1? jsClass;

    protected override void OnInitialized()
    {
        jsClass = new(JS);
    }

    private async Task SetStock()
    {
        if (jsClass is not null)
        {
            stockSymbol = 
                $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
            price = r.Next(1, 101);
            await jsClass.TickerChanged(stockSymbol, price);
        }
    }

    public void Dispose() => jsClass?.Dispose();
}

Vyvolání funkcí JavaScriptu a načtení vrácené hodnoty (InvokeAsync)

Použijte InvokeAsync , když by .NET měl přečíst výsledek volání JavaScriptu (JS).

displayTickerAlert2JS Zadejte funkci. Následující příklad vrátí řetězec pro zobrazení volajícím:

<script>
  window.displayTickerAlert2 = (symbol, price) => {
    if (price < 20) {
      alert(`${symbol}: $${price}!`);
      return "User alerted in the browser.";
    } else {
      return "User NOT alerted.";
    }
  };
</script>

Poznámka

Obecné pokyny k JS umístění a doporučení pro produkční aplikace najdete v tématu ASP.NET Core Blazor interoperabilita JavaScriptu (JSinterop)..

Příklad komponenty () (.razorInvokeAsync)

TickerChanged volá metodu handleTickerChanged2 a zobrazí vrácený řetězec v následující CallJsExample4 komponentě.

Pages/CallJsExample4.razor:

@page "/call-js-example-4"
@inject IJSRuntime JS

<h1>Call JS Example 4</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result is not null)
{
    <p>@result</p>
}

@code {
    private Random r = new();
    private string? stockSymbol;
    private decimal price;
    private string? result;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        var interopResult = 
            await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol, price);
        result = $"Result of TickerChanged call for {stockSymbol} at " +
            $"{price.ToString("c")}: {interopResult}";
    }
}

Příklad třídy (.cs) (InvokeAsync)

JsInteropClasses2.cs:

using Microsoft.JSInterop;

public class JsInteropClasses2
{
    private readonly IJSRuntime js;

    public JsInteropClasses2(IJSRuntime js)
    {
        this.js = js;
    }

    public async ValueTask<string> TickerChanged(string symbol, decimal price)
    {
        return await js.InvokeAsync<string>("displayTickerAlert2", symbol, price);
    }

    public void Dispose()
    {
    }
}

TickerChanged volá metodu handleTickerChanged2 a zobrazí vrácený řetězec v následující CallJsExample5 komponentě.

Pages/CallJsExample5.razor:

@page "/call-js-example-5"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call JS Example 5</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result is not null)
{
    <p>@result</p>
}

@code {
    private Random r = new();
    private string? stockSymbol;
    private decimal price;
    private JsInteropClasses2? jsClass;
    private string? result;

    protected override void OnInitialized()
    {
        jsClass = new(JS);
    }

    private async Task SetStock()
    {
        if (jsClass is not null)
        {
            stockSymbol = 
                $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
            price = r.Next(1, 101);
            var interopResult = await jsClass.TickerChanged(stockSymbol, price);
            result = $"Result of TickerChanged call for {stockSymbol} at " +
                $"{price.ToString("c")}: {interopResult}";
        }
    }

    public void Dispose() => jsClass?.Dispose();
}

Scénáře generování dynamického obsahu

Pro dynamické generování obsahu pomocí BuildRenderTree použijte [Inject] atribut:

[Inject]
IJSRuntime JS { get; set; }

Zjištění, kdy Blazor Server je aplikace prerendering

Tato část se vztahuje na Blazor Server a hostované Blazor WebAssembly aplikace, které prerender komponenty Razor . Prerendering je pokryt prerender a integrace ASP.NET Core Razor komponent.

I když je aplikace prerendering, určité akce, jako je volání do JavaScriptu, nejsou možné. Komponenty se můžou při předkreslování vykreslit jinak.

V následujícím příkladu setElementText1 je funkce umístěna uvnitř elementu <head> . Funkce se volá a JSRuntimeExtensions.InvokeVoidAsync nevrací hodnotu.

Poznámka

Obecné pokyny k JS umístění a doporučení pro produkční aplikace najdete v tématu ASP.NET Core Blazor interoperabilita JavaScriptu (JSinterop)..

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

Upozornění

Předchozí příklad upraví objektový model dokumentu (DOM) přímo pro demonstrační účely. Ve většině scénářů se nedoporučuje přímo upravovat DOM pomocí JavaScriptu, protože JavaScript může ovlivnit Blazorsledování změn. Další informace najdete v tématu ASP.NET Core Blazor interoperabilita JavaScriptu (JSinterop).

Pokud chcete zpozdit volání interop JavaScriptu, dokud není zaručeno, že taková volání budou fungovat, přepište OnAfterRender{Async} událost životního cyklu. Tato událost se volá pouze po úplném vykreslení aplikace.

Pages/PrerenderedInterop1.razor:

@page "/prerendered-interop-1"
@using Microsoft.JSInterop
@inject IJSRuntime JS

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

@code {
    private ElementReference divElement;

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

Poznámka

Předchozí příklad znečišťuje klienta globálními metodami. Lepší přístup v produkčních aplikacích najdete v izolaci JavaScriptu v modulech JavaScriptu.

Příklad:

export setElementText1 = (element, text) => element.innerText = text;

Následující komponenta ukazuje, jak používat interop JavaScriptu jako součást logiky inicializace komponenty způsobem, který je kompatibilní s předdeenderingem. Komponenta ukazuje, že je možné aktivovat aktualizaci vykreslování z vnitřní OnAfterRenderAsyncčásti . Vývojář musí být opatrní, abyste se vyhnuli vytváření nekonečné smyčky v tomto scénáři.

V následujícím příkladu setElementText2 je funkce umístěna uvnitř elementu <head> . Funkce je volána s IJSRuntime.InvokeAsync a vrací hodnotu.

Poznámka

Obecné pokyny k JS umístění a doporučení pro produkční aplikace najdete v tématu ASP.NET Core Blazor interoperabilita JavaScriptu (JSinterop)..

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

Upozornění

Předchozí příklad upraví objektový model dokumentu (DOM) přímo pro demonstrační účely. Ve většině scénářů se nedoporučuje přímo upravovat DOM pomocí JavaScriptu, protože JavaScript může ovlivnit Blazorsledování změn. Další informace najdete v tématu ASP.NET Core Blazor interoperabilita JavaScriptu (JSinterop).

Kde JSRuntime.InvokeAsync je volána, se používá pouze v OnAfterRenderAsync a ne v žádné dřívější metodě životního cyklu, ElementReference protože neexistuje žádný javascriptový prvek, dokud se komponenta nevykreslí.

StateHasChangedje volána k opětovnému vykreslení komponenty s novým stavem získaným z volání interop JavaScriptu (další informace najdete v tématu ASP.NET Core Razor vykreslování komponent). Kód nevytvoří nekonečné smyčky, protože StateHasChanged se volá pouze tehdy, když data je null.

Pages/PrerenderedInterop2.razor:

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

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

<p>
    Set value via JS interop call:
</p>

<div id="val-set-by-interop" @ref="divElement"></div>

@code {
    private string? data;
    private ElementReference divElement;

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

            StateHasChanged();
        }
    }
}

Poznámka

Předchozí příklad znečišťuje klienta globálními metodami. Lepší přístup v produkčních aplikacích najdete v izolaci JavaScriptu v modulech JavaScriptu.

Příklad:

export setElementText2 = (element, text) => {
  element.innerText = text;
  return text;
};

Synchronní JS spolupráce v Blazor WebAssembly aplikacích

Tato část platí jenom pro Blazor WebAssembly aplikace.

Volání zprostředkovatele komunikace JS jsou ve výchozím nastavení asynchronní, bez ohledu na to, jestli volaný kód je synchronní nebo asynchronní. Volání jsou ve výchozím nastavení asynchronní, aby se zajistilo, že komponenty jsou kompatibilní jak mezi modely hostování Blazor, tak Blazor Server a Blazor WebAssembly. U Blazor Servervšech JS volání zprostředkovatele komunikace musí být asynchronní, protože se odesílají přes síťové připojení.

Pokud víte, že vaše aplikace běží Blazor WebAssemblyjen někdy, můžete se rozhodnout provádět synchronní JS volání zprostředkovatele komunikace. To má trochu menší režii než provádění asynchronních volání a může vést k menšímu počtu cyklů vykreslování, protože při čekání na výsledky neexistuje žádný přechodný stav.

Pokud chcete v aplikaci provést synchronní volání z .NET do JavaScriptuBlazor WebAssembly, přetypujte IJSRuntime hoJS, aby IJSInProcessRuntime bylo volání zprostředkovatele komunikace:

@inject IJSRuntime JS

...

@code {
    protected override void HandleSomeEvent()
    {
        var jsInProcess = (IJSInProcessRuntime)JS;
        var value = jsInProcess.Invoke<string>("javascriptFunctionIdentifier");
    }
}

Při práci s aplikacemi IJSObjectReference ASP.NET Core 5.0 nebo novějšími Blazor WebAssembly můžete místo toho použít IJSInProcessObjectReference synchronně:

private IJSInProcessObjectReference module;

...

module = await JS.InvokeAsync<IJSInProcessObjectReference>("import", "./scripts.js");

Umístění JavaScriptu

Načtěte kód JavaScriptu (JS) pomocí libovolného přístupu popsaného v článku s přehledem interoperability (JSinterop) JavaScriptu:

Informace o izolování skriptů v modulech najdete v JSčásti o izolaci JavaScriptu v modulech JavaScriptu.

Upozornění

Nevložte <script> značku do souboru komponenty (.razor), protože <script> značku nejde dynamicky aktualizovat.

Izolace JavaScriptu v modulech JavaScriptu

Blazor umožňuje izolaci JavaScriptu (JS) ve standardních modulech JavaScriptu (specifikace ECMAScript).

Izolace JS poskytuje následující výhody:

  • Import JS už znečišťuje globální obor názvů.
  • Uživatelé knihovny a komponent už nemusí importovat související JS.

Následující JS modul například exportuje JS funkci pro zobrazení výzvy okna prohlížeče. Do externího JS souboru umístěte následující JS kód.

wwwroot/scripts.js:

export function showPrompt(message) {
  return prompt(message, 'Type anything here');
}

Přidejte předchozí JS modul do aplikace nebo knihovny tříd jako statický webový prostředek ve wwwroot složce a pak modul naimportujte do kódu .NET voláním InvokeAsync instance IJSRuntime .

IJSRuntime importuje modul jako IJSObjectReferenceobjekt , který představuje odkaz na JS objekt z kódu .NET. IJSObjectReference Použijte k vyvolání exportovaných JS funkcí z modulu.

Pages/CallJsExample6.razor:

@page "/call-js-example-6"
@implements IAsyncDisposable
@inject IJSRuntime JS

<h1>Call JS Example 6</h1>

<p>
    <button @onclick="TriggerPrompt">Trigger browser window prompt</button>
</p>

<p>
    @result
</p>

@code {
    private IJSObjectReference? module;
    private string? result;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            module = await JS.InvokeAsync<IJSObjectReference>("import", 
                "./scripts.js");
        }
    }

    private async Task TriggerPrompt()
    {
        result = await Prompt("Provide some text");
    }

    public async ValueTask<string?> Prompt(string message) =>
        module is not null ? 
            await module.InvokeAsync<string>("showPrompt", message) : null;

    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        if (module is not null)
        {
            await module.DisposeAsync();
        }
    }
}

V předchozím příkladu:

  • Podle konvence import je identifikátor speciální identifikátor, který se používá speciálně pro import JS modulu.
  • Zadejte externí JS soubor modulu pomocí stabilní cesty statického webového prostředku: ./{SCRIPT PATH AND FILENAME (.js)}, kde:
    • K vytvoření správné cesty statického prostředku k souboru JS se vyžaduje segment cesty pro aktuální adresář (./).
    • Zástupný symbol {SCRIPT PATH AND FILENAME (.js)} je cesta a název souboru v části wwwroot.
  • IJSObjectReference Odstraní uvolňování paměti v IAsyncDisposable.DisposeAsync.

Dynamický import modulu vyžaduje síťový požadavek, takže ho lze dosáhnout pouze asynchronně voláním InvokeAsync.

IJSInProcessObjectReference představuje odkaz na JS objekt, jehož funkce lze vyvolat synchronně v Blazor WebAssembly aplikacích. Další informace najdete v části Synchronní JS interoperabilita v Blazor WebAssembly aplikacích .

Poznámka

Pokud externí JS soubor poskytuje Razor knihovna tříd, zadejte soubor modulu JS pomocí jeho stabilní statické cesty k webovému prostředku: ./_content/{PACKAGE ID}/{SCRIPT PATH AND FILENAME (.js)}

  • K vytvoření správné cesty statického prostředku k souboru JS se vyžaduje segment cesty pro aktuální adresář (./).
  • Zástupný symbol {PACKAGE ID} je ID balíčku knihovny. Pokud v souboru projektu není zadaný parametr <PackageId>, jako ID balíčku se ve výchozím nastavení použije název sestavení projektu. V následujícím příkladu je ComponentLibrary název sestavení knihovny a soubor projektu knihovny nezadá <PackageId>.
  • Zástupný symbol {SCRIPT PATH AND FILENAME (.js)} je cesta a název souboru v části wwwroot. V následujícím příkladu se externí JS soubor (script.js) umístí do složky knihovny wwwroot tříd.
var module = await js.InvokeAsync<IJSObjectReference>(
    "import", "./_content/ComponentLibrary/scripts.js");

Další informace najdete v tématu Využívání komponent ASP.NET Core Razor z knihovny tříd Razor (RCL).

Zachycení odkazů na prvky

Některé scénáře vzájemné spolupráce v JavaScriptuJS vyžadují odkazy na elementy HTML. Knihovna uživatelského rozhraní může například vyžadovat odkaz na element pro inicializaci nebo může být nutné volat rozhraní API podobná příkazům na elementu, například click nebo play.

Pomocí následujícího přístupu zachyťte odkazy na elementy HTML v komponentě:

  • @ref Přidejte atribut do elementu HTML.
  • Definujte pole typu ElementReference , jehož název odpovídá hodnotě atributu @ref .

Následující příklad ukazuje zachycení odkazu na username<input> prvek:

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

@code {
    private ElementReference username;
}

Upozornění

Pomocí odkazu na prvek můžete ztlumit obsah prázdného prvku, který nepracuje s Blazor. Tento scénář je užitečný, když rozhraní API třetí strany poskytuje obsah elementu. Vzhledem k tomu Blazor , že s elementem nepracuje, neexistuje možnost konfliktu mezi Blazorreprezentací elementu a modelem DOM (Document Object Model).

V následujícím příkladu je nebezpečné ztlumit obsah neuspořádaného seznamu (ul), protože Blazor interaguje s modelem DOM k naplnění položek seznamu tohoto prvku (<li>) z objektu Todos :

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

Pokud JS ztlumí obsah elementu MyList a Blazor pokusí se použít rozdíly u elementu, rozdíly nebudou odpovídat objektu DOM.

Další informace najdete v tématu ASP.NET Core Blazor interoperabilita JavaScriptu (JSinterop).

Kód ElementReference se předává JS prostřednictvím JS zprostředkovatele komunikace. Kód JS obdrží HTMLElement instanci, kterou může používat s normálními rozhraními API DOM. Například následující kód definuje metodu rozšíření .NET (TriggerClickEvent), která umožňuje odeslat kliknutí myší na prvek.

Funkce JS vytvoří click událost u předaného elementu HTML (element):clickElement

window.interopFunctions = {
  clickElement : function (element) {
    element.click();
  }
}

Pokud chcete volat JS funkci, která nevrací hodnotu, použijte JSRuntimeExtensions.InvokeVoidAsync. Následující kód aktivuje událost na straně click klienta voláním předchozí JS funkce zachycenou ElementReference:

@inject IJSRuntime JS

<button @ref="exampleButton">Example Button</button>

<button @onclick="TriggerClick">
    Trigger click event on <code>Example Button</code>
</button>

@code {
    private ElementReference exampleButton;

    public async Task TriggerClick()
    {
        await JS.InvokeVoidAsync(
            "interopFunctions.clickElement", exampleButton);
    }
}

Pokud chcete použít metodu rozšíření, vytvořte statickou metodu IJSRuntime rozšíření, která obdrží instanci:

public static async Task TriggerClickEvent(this ElementReference elementRef, 
    IJSRuntime js)
{
    await js.InvokeVoidAsync("interopFunctions.clickElement", elementRef);
}

Metoda clickElement je volána přímo na objektu. Následující příklad předpokládá, že TriggerClickEvent metoda je k dispozici z JsInteropClasses oboru názvů:

@inject IJSRuntime JS
@using JsInteropClasses

<button @ref="exampleButton">Example Button</button>

<button @onclick="TriggerClick">
    Trigger click event on <code>Example Button</code>
</button>

@code {
    private ElementReference exampleButton;

    public async Task TriggerClick()
    {
        await exampleButton.TriggerClickEvent(JS);
    }
}

Důležité

Proměnná exampleButton se naplní pouze po vykreslení komponenty. Pokud se do kódu předá nepřepsaný ElementReference kód, JS obdrží kód hodnotu null.JS Chcete-li manipulovat s odkazy na elementy po dokončení vykreslování komponenty, použijte OnAfterRenderAsync metody životního cyklu součásti.OnAfterRender

Při práci s obecnými typy a vrácení hodnoty použijte ValueTask<TResult>:

public static ValueTask<T> GenericMethod<T>(this ElementReference elementRef, 
    IJSRuntime js)
{
    return js.InvokeAsync<T>("{JAVASCRIPT FUNCTION}", elementRef);
}

Zástupný {JAVASCRIPT FUNCTION} symbol je JS identifikátor funkce.

GenericMethod je volána přímo na objekt s typem. Následující příklad předpokládá, že GenericMethod je k dispozici z JsInteropClasses oboru názvů:

@inject IJSRuntime JS
@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>(JS);
    }
}

Referenční prvky napříč komponentami

Nelze ElementReference předat mezi komponentami, protože:

Aby nadřazená komponenta zpřístupnit prvek jiným komponentám, může nadřazená komponenta:

  • Povolit podřízeným komponentám registraci zpětných volání.
  • Vyvolá zaregistrované zpětné volání během OnAfterRender události s předaným odkazem na element. Tento přístup nepřímo umožňuje podřízeným komponentám pracovat s odkazem na element nadřazeného objektu.
<style>
    .red { color: red }
</style>
<script>
  function setElementClass(element, className) {
    var myElement = element;
    myElement.classList.add(className);
  }
</script>

Poznámka

Obecné pokyny k JS umístění a našim doporučením pro produkční aplikace najdete v tématu ASP.NET Core Blazor interoperabilita JavaScriptu (JSinterop).

Pages/CallJsExample7.razor (nadřazená komponenta):

@page "/call-js-example-7"

<h1>Call JS Example 7</h1>

<h2 @ref="title">Hello, world!</h2>

Welcome to your new app.

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

Pages/CallJsExample7.razor.cs:

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

namespace BlazorSample.Pages
{
    public partial class CallJsExample7 : 
        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, 
                CallJsExample7 self)
            {
                Observer = observer;
                Self = self;
            }

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

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

V předchozím příkladu je BlazorSample obor názvů aplikace s komponentami ve Pages složce. Pokud kód testujete místně, aktualizujte obor názvů.

Shared/SurveyPrompt.razor (podřízená komponenta):

@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:

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();

            subscription?.Dispose();
            subscription = 
                Parent is not null ? Parent.Subscribe(this) : null;
        }

        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();
        }
    }
}

V předchozím příkladu je BlazorSample obor názvů aplikace se sdílenými komponentami ve Shared složce. Pokud kód testujete místně, aktualizujte obor názvů.

Posílení volání zprostředkovatele komunikace v JavaScriptu

Tato část se primárně vztahuje na Blazor Server aplikace, ale Blazor WebAssembly aplikace můžou také nastavit JS časové limity pro interoperabilitu, pokud to podmínky vyžadují.

V Blazor Server aplikacích může spolupráce JavaScriptu (JS) selhat kvůli chybám sítě a měla by být považována za nespolehlivé. Ve výchozím nastavení Blazor Server aplikace používají pro volání zprostředkovatele komunikace časový limit JS o jednu minutu. Pokud aplikace může tolerovat agresivnější časový limit, nastavte časový limit pomocí jednoho z následujících přístupů.

Nastavení globálního časového limitu v :Program.csCircuitOptions.JSInteropDefaultCallTimeout

builder.Services.AddServerSideBlazor(
    options => options.JSInteropDefaultCallTimeout = {TIMEOUT});

Zástupný {TIMEOUT} symbol je TimeSpan (například TimeSpan.FromSeconds(80)).

Nastavte časový limit volání v kódu komponenty. Zadaný časový limit přepíše globální časový limit nastavený pomocí JSInteropDefaultCallTimeout:

var result = await JS.InvokeAsync<string>("{ID}", {TIMEOUT}, new[] { "Arg1" });

V předchozím příkladu:

  • Zástupný {TIMEOUT} symbol je TimeSpan (například TimeSpan.FromSeconds(80)).
  • Zástupný {ID} symbol je identifikátor funkce, který má vyvolat. Například hodnota someScope.someFunction vyvolá funkci window.someScope.someFunction.

I když nejčastější příčinou JS selhání vzájemné komunikace jsou chyby sítě v Blazor Server aplikacích, časové limity volání pro jednotlivé volání je možné nastavit pro JS volání interopu v Blazor WebAssembly aplikacích. I když v Blazor WebAssembly aplikaci neexistuje žádný SignalR okruh, JS volání interopu můžou selhat z jiných důvodů, které platí v Blazor WebAssembly aplikacích.

Další informace o vyčerpání prostředků najdete v tématu Pokyny ke zmírnění rizik hrozeb pro ASP.NET Core Blazor Server.

Vyhněte se cyklovým odkazům na objekty.

Objekty, které obsahují cyklický odkaz, nelze serializovat v klientovi pro následující objekty:

  • Volání metody .NET
  • Volání metod JavaScriptu z jazyka C#, pokud má návratový typ cyklický odkaz.

Knihovny JavaScriptu, které vykreslují uživatelské rozhraní

Někdy můžete chtít použít knihovny JavaScriptu (JS), které vytvářejí viditelné prvky uživatelského rozhraní v rámci modelu DOM (Document Object Model). Na první pohled to může vypadat jako obtížné, protože Blazorsystém diffing spoléhá na kontrolu nad stromem prvků DOM a narazí na chyby, pokud některý externí kód ztlumí strom DOM a zneplatní jeho mechanismus pro použití rozdílů. Nejedná se o Blazorkonkrétní omezení. K stejné výzvě dochází u libovolného rozhraní uživatelského rozhraní založeného na rozdílech.

Naštěstí je snadné spolehlivě vkládat externě generované uživatelské rozhraní do Razor uživatelského rozhraní komponenty. Doporučená technika spočívá v tom, že kód komponenty (.razor soubor) vytvoří prázdný prvek. Pokud Blazorjde o rozdílový systém, je prvek vždy prázdný, takže vykreslovací modul se do prvku nepřipojí a místo toho ponechá jeho obsah sám. Díky tomu je bezpečné naplnit prvek libovolným externě spravovaným obsahem.

Následující příklad ukazuje koncept. V rámci if příkazu, kdy firstRender je , trueinteragujte mimo unmanagedElementBlazor použití JS interopu. Můžete například volat externí JS knihovnu, která naplní prvek. Blazor ponechá obsah prvku sám, dokud se tato komponenta neodebere. Když je komponenta odebrána, odebere se také celý podstrom DOM.

<h1>Hello! This is a Razor component rendered at @DateTime.Now</h1>

<div @ref="unmanagedElement"></div>

@code {
    private ElementReference unmanagedElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            ...
        }
    }
}

Představte si následující příklad, který vykreslí interaktivní mapu pomocí opensourcových rozhraní API Mapboxu.

Následující JS modul se umístí do aplikace nebo je zpřístupněný z Razor knihovny tříd.

Poznámka

Pokud chcete vytvořit mapu Mapboxu , získejte přístupový token z Mapbox Sign in a zadejte ho {ACCESS TOKEN} tam, kde se zobrazí v následujícím kódu.

wwwroot/mapComponent.js:

import 'https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.js';

mapboxgl.accessToken = '{ACCESS TOKEN}';

export function addMapToElement(element) {
  return new mapboxgl.Map({
    container: element,
    style: 'mapbox://styles/mapbox/streets-v11',
    center: [-74.5, 40],
    zoom: 9
  });
}

export function setMapCenter(map, latitude, longitude) {
  map.setCenter([longitude, latitude]);
}

Chcete-li vytvořit správný styl, přidejte na stránku HTML hostitele následující značku šablony stylů.

Přidejte do značky elementu <head> následující <link> prvek (umístění <head> obsahu):

<link href="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css" 
    rel="stylesheet" />

Pages/CallJsExample8.razor:

@page "/call-js-example-8"
@implements IAsyncDisposable
@inject IJSRuntime JS

<h1>Call JS Example 8</h1>

<div @ref="mapElement" style='width:400px;height:300px'></div>

<button @onclick="() => ShowAsync(51.454514, -2.587910)">Show Bristol, UK</button>
<button @onclick="() => ShowAsync(35.6762, 139.6503)">Show Tokyo, Japan</button>

@code
{
    private ElementReference mapElement;
    private IJSObjectReference? mapModule;
    private IJSObjectReference? mapInstance;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            mapModule = await JS.InvokeAsync<IJSObjectReference>(
                "import", "./mapComponent.js");
            mapInstance = await mapModule.InvokeAsync<IJSObjectReference>(
                "addMapToElement", mapElement);
        }
    }

    private async Task ShowAsync(double latitude, double longitude)
    {
        if (mapModule is not null && mapInstance is not null)
        {
            await mapModule.InvokeVoidAsync("setMapCenter", mapInstance, 
                latitude, longitude).AsTask();
        }
    }

    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        if (mapInstance is not null)
        {
            await mapInstance.DisposeAsync();
        }

        if (mapModule is not null)
        {
            await mapModule.DisposeAsync();
        }
    }
}

Předchozí příklad vytvoří interaktivní uživatelské rozhraní mapy. Uživatel:

  • Lze přetáhnout a posunout nebo přiblížit.
  • Výběrem tlačítek přejděte na předdefinovaná umístění.

Mapbox street map of Tokyo, Japan with button to select Bristol, United Kingdom and Tokyo, Japan

V předchozím příkladu:

  • Sousto <div>@ref="mapElement" je prázdné, pokud Blazor se to týká. Skript mapbox-gl.js může bezpečně naplnit prvek a upravit jeho obsah. Tuto techniku použijte s libovolnou JS knihovnou, která vykresluje uživatelské rozhraní. Komponenty můžete vkládat z architektury SPA třetí strany JS uvnitř Razor komponent, pokud se nepokouší kontaktovat a upravovat další části stránky. Externí kód není bezpečný pro JS úpravy prvků, které Blazor se nepovažují za prázdné.
  • Při použití tohoto přístupu mějte na paměti pravidla týkající Blazor se zachování nebo zničení prvků DOM. Komponenta bezpečně zpracovává události kliknutí na tlačítko a aktualizuje existující instanci mapy, protože prvky MODELU DOM se uchovávají tam, kde je to možné ve výchozím nastavení. Pokud jste vykreslovali seznam prvků mapy ze @foreach smyčky, chcete použít @key k zajištění zachování instancí komponent. Jinak by změny v datech seznamu mohly způsobit, že instance komponent zachovávají stav předchozích instancí nežádoucím způsobem. Další informace naleznete v tématu using @key to preserve elements and components.
  • Příklad zapouzdřuje JS logiku a závislosti v modulu ES6 a načte modul dynamicky pomocí identifikátoru import . Další informace najdete v tématu Izolace JavaScriptu v modulech JavaScriptu.

Podpora bajtového pole

Blazor podporuje optimalizovanou bajtůovou matici JavaScriptu (JS), která zabraňuje kódování a dekódování bajtů do Base64. Následující příklad používá JS interop k předání bajtového pole do JavaScriptu.

receiveByteArrayJS Zadejte funkci. Funkce se volá a InvokeVoidAsync nevrací hodnotu:

<script>
  window.receiveByteArray = (bytes) => {
    let utf8decoder = new TextDecoder();
    let str = utf8decoder.decode(bytes);
    return str;
  };
</script>

Poznámka

Obecné pokyny k JS umístění a doporučení pro produkční aplikace najdete v tématu ASP.NET Core Blazor interoperabilita JavaScriptu (JSinterop)..

Pages/CallJsExample9.razor:

@page "/call-js-example-9"
@inject IJSRuntime JS

<h1>Call JS Example 9</h1>

<p>
    <button @onclick="SendByteArray">Send Bytes</button>
</p>

<p>
    @result
</p>

<p>
    Quote &copy;2005 <a href="https://www.uphe.com">Universal Pictures</a>:
    <a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
    <a href="https://www.imdb.com/name/nm0821612/">Jewel Staite on IMDB</a>
</p>

@code {
    private string? result;

    private async Task SendByteArray()
    {
        var bytes = new byte[] { 0x45, 0x76, 0x65, 0x72, 0x79, 0x74, 0x68, 0x69,
            0x6e, 0x67, 0x27, 0x73, 0x20, 0x73, 0x68, 0x69, 0x6e, 0x79, 0x2c,
            0x20, 0x43, 0x61, 0x70, 0x74, 0x69, 0x61, 0x6e, 0x2e, 0x20, 0x4e,
            0x6f, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x66, 0x72, 0x65, 0x74, 0x2e };

        result = await JS.InvokeAsync<string>("receiveByteArray", bytes);
    }
}

Informace o použití bajtového pole při volání .NET z JavaScriptu najdete v tématu Volání metod .NET z funkcí JavaScriptu v ASP.NET Core Blazor.

Omezení velikosti pro volání z interoperability JavaScriptu

Tato část platí jenom pro Blazor Server aplikace. Architektura Blazor WebAssemblyneukládá omezení velikosti vstupů a výstupů z interoperability JavaScriptuJS.

Volání Blazor Serverinteropu jsou omezená velikostí maximální velikosti příchozích SignalR zpráv povolených pro metody centra, která se vynucuje HubOptions.MaximumReceiveMessageSize (výchozí hodnota: 32 kBJS). JS pro zprávy .NET SignalR větší, než MaximumReceiveMessageSize vyvolá chybu. Architektura neukládá omezení velikosti SignalR zprávy z centra na klienta.

Pokud SignalR protokolování není nastavené na Ladění nebo Trasování, zobrazí se chyba velikosti zprávy jenom v konzole vývojářských nástrojů prohlížeče:

Chyba: Připojení se odpojilo s chybou Chyba: Server vrátil chybu při zavření: Připojení se zavřelo s chybou.

Pokud SignalR je protokolování na straně serveru nastavené na Ladění nebo Trasování, protokolování na straně serveru zobrazí InvalidDataException chybu velikosti zprávy.

appsettings.Development.json:

{
  "DetailedErrors": true,
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information",
      "Microsoft.AspNetCore.SignalR": "Debug"
    }
  }
}

Chyba:

System.IO.InvalidDataException: Byla překročena maximální velikost zprávy 32768B. Velikost zprávy je možné nakonfigurovat v AddHubOptions.

Zvyšte limit nastavením MaximumReceiveMessageSize v Program.cs. Následující příklad nastaví maximální velikost zprávy příjmu na 64 kB (64 * 1024):

builder.Services.AddServerSideBlazor()
    .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);

Zvýšení limitu SignalR velikosti příchozích zpráv má náklady na vyžadování dalších prostředků serveru a zpřístupňuje server zvýšeným rizikům od škodlivého uživatele. Kromě toho může čtení velkého množství obsahu do paměti jako řetězce nebo bajtová pole způsobit také přidělení, která fungují špatně s uvolňováním paměti, což vede k dalším sankcím za výkon.

Při vývoji kódu, který přenáší velké množství dat mezi JS aplikacemi a Blazor v Blazor Server aplikacích, zvažte následující pokyny:

  • Využijte nativní podporu komunikace streamování k přenosu dat větších než SignalR limit velikosti příchozích zpráv:
  • Obecné tipy:
    • Nepřidělujte velké objekty v kódu jazyka JS C#.
    • Bezplatná spotřebovaná paměť při dokončení nebo zrušení procesu
    • Vynucujte pro účely zabezpečení následující další požadavky:
      • Deklarujte maximální velikost souboru nebo dat, které lze předat.
      • Deklarujte minimální rychlost nahrávání z klienta na server.
    • Po přijetí dat serverem můžou být tato data:
      • Dočasně uložená v vyrovnávací paměti, dokud se neshromáždí všechny segmenty.
      • Spotřebováno okamžitě. Data se například dají uložit okamžitě v databázi nebo zapisovat na disk při přijetí jednotlivých segmentů.

Unmarshalled JavaScript interop

Blazor WebAssembly komponenty můžou mít nízký výkon, pokud jsou objekty .NET serializovány pro interop (JSJavaScript) a některé z následujících hodnot jsou pravdivé:

  • Velký objem objektů .NET se rychle serializuje. Například nízký výkon může vést k tomu, že JS se volání vzájemné komunikace provádí na základě přesouvání vstupního zařízení, například otáčení kolečka myši.
  • Velké objekty .NET nebo mnoho objektů .NET musí být serializováno pro JS interoperabilitu. Například nízký výkon může vést k tomu, že JS volání vzájemné komunikace vyžadují serializaci desítek souborů.

IJSUnmarshalledObjectReference představuje odkaz na JS objekt, jehož funkce lze vyvolat bez režie serializace dat .NET.

V následujícím příkladu:

  • Struktura obsahující řetězec a celé číslo se předá neserializované .JS
  • JS funkce zpracovávají data a vracejí logické hodnoty nebo řetězec volajícímu.
  • Řetězec JS není přímo převedený na objekt .NET string . Funkce unmarshalledFunctionReturnString volá BINDING.js_string_to_mono_string ke správě převodu JS řetězce.

Poznámka

Následující příklady nejsou typické případy použití pro tento scénář, protože struktura předána JS nemá za následek nízký výkon komponent. Příklad používá malý objekt pouze k předvedení konceptů pro předávání neserializovaných dat .NET.

<script>
  window.returnObjectReference = () => {
    return {
      unmarshalledFunctionReturnBoolean: function (fields) {
        const name = Blazor.platform.readStringField(fields, 0);
        const year = Blazor.platform.readInt32Field(fields, 8);

        return name === "Brigadier Alistair Gordon Lethbridge-Stewart" &&
            year === 1968;
      },
      unmarshalledFunctionReturnString: function (fields) {
        const name = Blazor.platform.readStringField(fields, 0);
        const year = Blazor.platform.readInt32Field(fields, 8);

        return BINDING.js_string_to_mono_string(`Hello, ${name} (${year})!`);
      }
    };
  }
</script>

Poznámka

Obecné pokyny k JS umístění a našim doporučením pro produkční aplikace najdete v tématu ASP.NET Core Blazor interoperabilita JavaScriptu (JSinterop).

Upozornění

Název js_string_to_mono_string funkce, chování a existence se v budoucí verzi .NET může změnit. Příklad:

  • Funkce se pravděpodobně přejmenuje.
  • Samotná funkce může být odebrána ve prospěch automatického převodu řetězců architekturou.

Pages/CallJsExample10.razor:

@page "/call-js-example-10"
@using System.Runtime.InteropServices
@using Microsoft.JSInterop
@inject IJSRuntime JS

<h1>Call JS Example 10</h1>

@if (callResultForBoolean)
{
    <p>JS interop was successful!</p>
}

@if (!string.IsNullOrEmpty(callResultForString))
{
    <p>@callResultForString</p>
}

<p>
    <button @onclick="CallJSUnmarshalledForBoolean">
        Call Unmarshalled JS & Return Boolean
    </button>
    <button @onclick="CallJSUnmarshalledForString">
        Call Unmarshalled JS & Return String
    </button>
</p>

<p>
    <a href="https://www.doctorwho.tv">Doctor Who</a>
    is a registered trademark of the <a href="https://www.bbc.com/">BBC</a>.
</p>

@code {
    private bool callResultForBoolean;
    private string? callResultForString;

    private void CallJSUnmarshalledForBoolean()
    {
        var unmarshalledRuntime = (IJSUnmarshalledRuntime)JS;

        var jsUnmarshalledReference = unmarshalledRuntime
            .InvokeUnmarshalled<IJSUnmarshalledObjectReference>(
                "returnObjectReference");

        callResultForBoolean = 
            jsUnmarshalledReference.InvokeUnmarshalled<InteropStruct, bool>(
                "unmarshalledFunctionReturnBoolean", GetStruct());
    }

    private void CallJSUnmarshalledForString()
    {
        var unmarshalledRuntime = (IJSUnmarshalledRuntime)JS;

        var jsUnmarshalledReference = unmarshalledRuntime
            .InvokeUnmarshalled<IJSUnmarshalledObjectReference>(
                "returnObjectReference");

        callResultForString = 
            jsUnmarshalledReference.InvokeUnmarshalled<InteropStruct, string>(
                "unmarshalledFunctionReturnString", GetStruct());
    }

    private InteropStruct GetStruct()
    {
        return new InteropStruct
        {
            Name = "Brigadier Alistair Gordon Lethbridge-Stewart",
            Year = 1968,
        };
    }

    [StructLayout(LayoutKind.Explicit)]
    public struct InteropStruct
    {
        [FieldOffset(0)]
        public string Name;

        [FieldOffset(8)]
        public int Year;
    }
}

IJSUnmarshalledObjectReference Pokud instance není v kódu jazyka C# odstraněna, je možné ji vyhodit .JS Následující dispose funkce odstraní odkaz na objekt při zavolání z JS:

window.exampleJSObjectReferenceNotDisposedInCSharp = () => {
  return {
    dispose: function () {
      DotNet.disposeJSObjectReference(this);
    },

    ...
  };
}

Typy polí lze převést z JS objektů na objekty .NET pomocí js_typed_array_to_array, ale JS pole musí být typové pole. Pole z JS kódu jazyka C# lze číst jako pole objektů .NET (object[]).

Jiné datové typy, například pole řetězců, lze převést, ale vyžadují vytvoření nového objektu pole Mono (mono_obj_array_new) a nastavení jeho hodnoty (mono_obj_array_set).

Upozornění

JS funkce poskytované architekturou Blazor , například js_typed_array_to_array, mono_obj_array_newa mono_obj_array_set, podléhají změnám názvů, změnám chování nebo odebráním v budoucích verzích .NET.

Streamování z .NET do JavaScriptu

Blazor podporuje streamování dat přímo z .NET do JavaScriptu. Datové proudy se vytvářejí pomocí .DotNetStreamReference

DotNetStreamReference představuje datový proud .NET a používá následující parametry:

  • stream: Stream odeslaný do JavaScriptu.
  • leaveOpen: Určuje, zda je datový proud po přenosu otevřený. Pokud není zadaná hodnota, leaveOpen výchozí hodnota je false.

V JavaScriptu můžete k příjmu dat použít vyrovnávací paměť pole nebo čitelný datový proud:

  • Pomocí :ArrayBuffer

    async function streamToJavaScript(streamRef) {
      const data = await streamRef.arrayBuffer();
    }
    
  • Pomocí :ReadableStream

    async function streamToJavaScript(streamRef) {
      const stream = await streamRef.stream();
    }
    

V kódu jazyka C#:

using var streamRef = new DotNetStreamReference(stream: {STREAM}, leaveOpen: false);
await JS.InvokeVoidAsync("streamToJavaScript", streamRef);

V předchozím příkladu:

  • Zástupný {STREAM} symbol představuje odeslanou do JavaScriptu Stream .
  • JS je injektovaná IJSRuntime instance.

Volání metod .NET z funkcí JavaScriptu v ASP.NET Core Blazor pokrývá reverzní operaci streamování z JavaScriptu do .NET.

Blazor ASP.NET Core soubory ke stažení popisuje, jak stáhnout soubor v Blazor.

Zachycení výjimek JavaScriptu

Pokud chcete zachytit JS výjimky, zabalte interoperabilitu JStry-catch do bloku a zachyťte .JSException

V následujícím příkladu nonFunctionJS funkce neexistuje. Pokud se funkce nenajde, JSException je zachycená s chybou Message , která označuje následující chybu:

Could not find 'nonFunction' ('nonFunction' was undefined).

Pages/CallJsExample11.razor:

@page "/call-js-example-11"
@inject IJSRuntime JS

<h1>Call JS Example 11</h1>

<p>
    <button @onclick="CatchUndefinedJSFunction">Catch Exception</button>
</p>

<p>
    @result
</p>

<p>
    @errorMessage
</p>

@code {
    private string? errorMessage;
    private string? result;

    private async Task CatchUndefinedJSFunction()
    {
        try
        {
            result = await JS.InvokeAsync<string>("nonFunction");
        }
        catch (JSException e)
        {
            errorMessage = $"Error Message: {e.Message}";
        }
    }
}

Přerušení dlouhotrvající funkce JavaScriptu

Pomocí AbortControllerJS s komponentou CancellationTokenSource můžete přerušit dlouho běžící funkci JavaScriptu z kódu jazyka C#.

Následující JSHelpers třída obsahuje simulovanou dlouho běžící funkci, která počítá nepřetržitě, longRunningFndokud AbortController.signal není uvedeno, že AbortController.abort byla volána. Funkce sleep je určená pro demonstrační účely k simulaci pomalého spuštění dlouhotrvající funkce, která by nebyla v produkčním kódu. Při volání stopFnkomponenty je longRunningFn signál přerušen prostřednictvím while podmíněné kontroly AbortSignal.abortedsmyčky .

<script>
  class Helpers {
    static #controller = new AbortController();

    static async #sleep(ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }

    static async longRunningFn() {
      var i = 0;
      while (!this.#controller.signal.aborted) {
        i++;
        console.log(`longRunningFn: ${i}`);
        await this.#sleep(1000);
      }
    }

    static stopFn() {
      this.#controller.abort();
      console.log('longRunningFn aborted!');
    }
  }

  window.Helpers = Helpers;
</script>

Poznámka

Obecné pokyny k JS umístění a našim doporučením pro produkční aplikace najdete v tématu ASP.NET Core Blazor interoperabilita JavaScriptu (JSinterop).

Následující CallJsExample12 komponenta:

Pages/CallJsExample12.razor:

@page "/call-js-example-12"
@inject IJSRuntime JS

<h1>Cancel long-running JS interop</h1>

<p>
    <button @onclick="StartTask">Start Task</button>
    <button @onclick="CancelTask">Cancel Task</button>
</p>

@code {
    private CancellationTokenSource? cts;

    private async Task StartTask()
    {
        cts = new CancellationTokenSource();
        cts.Token.Register(() => JS.InvokeVoidAsync("Helpers.stopFn"));

        await JS.InvokeVoidAsync("Helpers.longRunningFn");
    }

    private void CancelTask()
    {
        cts?.Cancel();
    }

    public void Dispose()
    {
        cts?.Cancel();
        cts?.Dispose();
    }
}

Konzola vývojářských nástrojů prohlížeče označuje spuštění dlouhotrvající JS funkce po Start Task výběru tlačítka a při přerušení funkce po Cancel Task výběru tlačítka:

longRunningFn: 1
longRunningFn: 2
longRunningFn: 3
longRunningFn aborted!

Další materiály

Informace o volání metod .NET z JSnaleznete v tématu Volání metod .NET z funkcí Jazyka JavaScript v ASP.NET Core Blazor.

Pokud chcete volat JS z .NET, vložte IJSRuntime abstrakci a zavolejte jednu z následujících metod:

Pro předchozí metody .NET, které volají JS funkce:

  • Identifikátor funkce (String) je relativní k globálnímu oboru (window). Zavolat window.someScope.someFunction, identifikátor je someScope.someFunction. Před zavolání není nutné funkci registrovat.
  • Předejte funkci libovolný počet argumentů Object[]JS, které JSlze serializovat.
  • Token zrušení (CancellationToken) šíří oznámení, že operace by měly být zrušeny.
  • TimeSpan představuje časový limit operace JS .
  • Návratový TValue typ musí být JStaké zapnutý serializovatelný. TValue by se měl shodovat s typem .NET, který se nejlépe mapuje na vrácený JStyp ON.
  • Funkce A JS Promise je vrácena pro InvokeAsync metody. InvokeAsync rozbalí Promise a vrátí hodnotu očekávanou hodnotou Promise.

U Blazor Server aplikací s povoleným předkreslováním není volání JS během počátečního předkreslování možné. JS Volání mezi zprostředkovateli musí být odložena až po navázání připojení k prohlížeči. Další informace najdete v části Zjistit, kdy Blazor Server je aplikace předdeenderingová .

Následující příklad je založen na dekodéru založeném JSna TextDecoderjazyce . Příklad ukazuje, jak vyvolat JS funkci z metody jazyka C#, která snižuje požadavek z vývojářského kódu na existující JS rozhraní API. Funkce JS přijímá bajtové pole z metody jazyka C#, dekóduje pole a vrátí text do komponenty pro zobrazení.

Do koncové značky wwwroot/index.html () nebo Pages/_Host.cshtml (Blazor WebAssemblyBlazor Server) přidejte následující JS</body> kód:

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

Následující CallJsExample1 komponenta:

  • convertArrayJS Vyvolá funkci InvokeAsync při výběru tlačítka (Convert Array).
  • JS Po zavolání funkce se předané pole převede na řetězec. Řetězec se vrátí do komponenty pro zobrazení (text).

Pages/CallJsExample1.razor:

@page "/call-js-example-1"
@inject IJSRuntime JS

<h1>Call JS <code>convertArray</code> Function</h1>

<p>
    <button @onclick="ConvertArray">Convert Array</button>
</p>

<p>
    @text
</p>

<p>
    Quote ©2005 <a href="https://www.uphe.com">Universal Pictures</a>: 
    <a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
    <a href="https://www.imdb.com/name/nm0472710/">David Krumholtz on IMDB</a>
</p>

@code {
    private MarkupString text;

    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()
    {
        text = new(await JS.InvokeAsync<string>("convertArray", quoteArray));
    }
}

JavaScript API omezené na uživatelská gesta

Tato část platí jenom pro Blazor Server aplikace.

Některá rozhraní API (JSJavaScript) prohlížeče se dají spouštět jenom v kontextu gesta uživatele, například pomocí Fullscreen API (dokumentace k MDN). Tato rozhraní API se nedají volat prostřednictvím JS mechanismu vzájemné spolupráce v Blazor Server aplikacích, protože zpracování událostí uživatelského rozhraní se provádí asynchronně a obecně už v kontextu gesta uživatele. Aplikace musí událost uživatelského rozhraní kompletně zpracovat v JavaScriptu, proto místo atributu @onclick direktivy použijte onclick (dokumentaciBlazork MDN).

Vyvolání funkcí JavaScriptu bez čtení vrácené hodnoty (InvokeVoidAsync)

Použít InvokeVoidAsync , když:

Uvnitř koncové </body> značky wwwroot/index.html (Blazor WebAssembly) nebo Pages/_Host.cshtml (Blazor Server) zadejte displayTickerAlert1JS funkci. Funkce se volá s InvokeVoidAsync a nevrací hodnotu:

<script>
  window.displayTickerAlert1 = (symbol, price) => {
    alert(`${symbol}: $${price}!`);
  };
</script>

Příklad komponenty (.razor) (InvokeVoidAsync)

TickerChanged volá metodu handleTickerChanged1 v následující CallJsExample2 komponentě.

Pages/CallJsExample2.razor:

@page "/call-js-example-2"
@inject IJSRuntime JS

<h1>Call JS Example 2</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
    private Random r = new();
    private string stockSymbol;
    private decimal price;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
    }
}

Příklad třídy (.cs) (InvokeVoidAsync)

JsInteropClasses1.cs:

using System.Threading.Tasks;
using Microsoft.JSInterop;

public class JsInteropClasses1
{
    private readonly IJSRuntime js;

    public JsInteropClasses1(IJSRuntime js)
    {
        this.js = js;
    }

    public async ValueTask TickerChanged(string symbol, decimal price)
    {
        await js.InvokeVoidAsync("displayTickerAlert1", symbol, price);
    }

    public void Dispose()
    {
    }
}

TickerChanged volá metodu handleTickerChanged1 v následující CallJsExample3 komponentě.

Pages/CallJsExample3.razor:

@page "/call-js-example-3"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call JS Example 3</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
    private Random r = new();
    private string stockSymbol;
    private decimal price;
    private JsInteropClasses1 jsClass;

    protected override void OnInitialized()
    {
        jsClass = new(JS);
    }

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        await jsClass.TickerChanged(stockSymbol, price);
    }

    public void Dispose() => jsClass?.Dispose();
}

Vyvolání funkcí JavaScriptu a načtení vrácené hodnoty (InvokeAsync)

Použijte InvokeAsync , když by .NET měl přečíst výsledek JS volání.

Uvnitř koncové </body> značky wwwroot/index.html (Blazor WebAssembly) nebo Pages/_Host.cshtml (Blazor Server) zadejte displayTickerAlert2JS funkci. Následující příklad vrátí řetězec pro zobrazení volajícím:

<script>
  window.displayTickerAlert2 = (symbol, price) => {
    if (price < 20) {
      alert(`${symbol}: $${price}!`);
      return "User alerted in the browser.";
    } else {
      return "User NOT alerted.";
    }
  };
</script>

Příklad komponenty (.razor) (InvokeAsync)

TickerChanged volá metodu handleTickerChanged2 a zobrazí vrácený řetězec v následující CallJsExample4 komponentě.

Pages/CallJsExample4.razor:

@page "/call-js-example-4"
@inject IJSRuntime JS

<h1>Call JS Example 4</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result is not null)
{
    <p>@result</p>
}

@code {
    private Random r = new();
    private string stockSymbol;
    private decimal price;
    private string result;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        var interopResult = 
            await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol, price);
        result = $"Result of TickerChanged call for {stockSymbol} at " +
            $"{price.ToString("c")}: {interopResult}";
    }
}

Příklad třídy (.cs) (InvokeAsync)

JsInteropClasses2.cs:

using System.Threading.Tasks;
using Microsoft.JSInterop;

public class JsInteropClasses2
{
    private readonly IJSRuntime js;

    public JsInteropClasses2(IJSRuntime js)
    {
        this.js = js;
    }

    public async ValueTask<string> TickerChanged(string symbol, decimal price)
    {
        return await js.InvokeAsync<string>("displayTickerAlert2", symbol, price);
    }

    public void Dispose()
    {
    }
}

TickerChanged volá metodu handleTickerChanged2 a zobrazí vrácený řetězec v následující CallJsExample5 komponentě.

Pages/CallJsExample5.razor:

@page "/call-js-example-5"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call JS Example 5</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result is not null)
{
    <p>@result</p>
}

@code {
    private Random r = new();
    private string stockSymbol;
    private decimal price;
    private JsInteropClasses2 jsClass;
    private string result;

    protected override void OnInitialized()
    {
        jsClass = new(JS);
    }

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        var interopResult = await jsClass.TickerChanged(stockSymbol, price);
        result = $"Result of TickerChanged call for {stockSymbol} at " +
            $"{price.ToString("c")}: {interopResult}";
    }

    public void Dispose() => jsClass?.Dispose();
}

Scénáře generování dynamického obsahu

Pro dynamické generování obsahu pomocí BuildRenderTree použijte [Inject] atribut:

[Inject]
IJSRuntime JS { get; set; }

Zjištění, kdy Blazor Server je aplikace prerendering

Tato část se vztahuje na Blazor Server a hostované Blazor WebAssembly aplikace, které prerender komponenty Razor . Prerendering je pokryt prerender a integrace ASP.NET Core Razor komponent.

I když je aplikace prerendering, určité akce, jako je volání do JavaScriptu, nejsou možné. Komponenty se můžou při předkreslování vykreslit jinak.

V následujícím příkladu setElementText1 je funkce umístěna uvnitř elementu <head> . Funkce se volá a JSRuntimeExtensions.InvokeVoidAsync nevrací hodnotu.

Poznámka

Obecné pokyny k JS umístění a doporučení pro produkční aplikace najdete v tématu ASP.NET Core Blazor interoperabilita JavaScriptu (JSinterop)..

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

Upozornění

Předchozí příklad upraví objektový model dokumentu (DOM) přímo pro demonstrační účely. Ve většině scénářů se nedoporučuje přímo upravovat DOM pomocí JavaScriptu, protože JavaScript může ovlivnit Blazorsledování změn. Další informace najdete v tématu ASP.NET Core Blazor interoperabilita JavaScriptu (JSinterop).

Pokud chcete zpozdit volání interop JavaScriptu, dokud není zaručeno, že taková volání budou fungovat, přepište OnAfterRender{Async} událost životního cyklu. Tato událost se volá pouze po úplném vykreslení aplikace.

Pages/PrerenderedInterop1.razor:

@page "/prerendered-interop-1"
@using Microsoft.JSInterop
@inject IJSRuntime JS

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

@code {
    private ElementReference divElement;

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

Poznámka

Předchozí příklad znečišťuje klienta globálními metodami. Lepší přístup v produkčních aplikacích najdete v izolaci JavaScriptu v modulech JavaScriptu.

Příklad:

export setElementText1 = (element, text) => element.innerText = text;

Následující komponenta ukazuje, jak používat interop JavaScriptu jako součást logiky inicializace komponenty způsobem, který je kompatibilní s předdeenderingem. Komponenta ukazuje, že je možné aktivovat aktualizaci vykreslování z vnitřní OnAfterRenderAsyncčásti . Vývojář musí být opatrní, abyste se vyhnuli vytváření nekonečné smyčky v tomto scénáři.

V následujícím příkladu setElementText2 je funkce umístěna uvnitř elementu <head> . Funkce je volána s IJSRuntime.InvokeAsync a vrací hodnotu.

Poznámka

Obecné pokyny k JS umístění a doporučení pro produkční aplikace najdete v tématu ASP.NET Core Blazor interoperabilita JavaScriptu (JSinterop)..

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

Upozornění

Předchozí příklad upraví objektový model dokumentu (DOM) přímo pro demonstrační účely. Ve většině scénářů se nedoporučuje přímo upravovat DOM pomocí JavaScriptu, protože JavaScript může ovlivnit Blazorsledování změn. Další informace najdete v tématu ASP.NET Core Blazor interoperabilita JavaScriptu (JSinterop).

Kde JSRuntime.InvokeAsync je volána, se používá pouze v OnAfterRenderAsync a ne v žádné dřívější metodě životního cyklu, ElementReference protože neexistuje žádný javascriptový prvek, dokud se komponenta nevykreslí.

StateHasChangedje volána k opětovnému vykreslení komponenty s novým stavem získaným z volání interop JavaScriptu (další informace najdete v tématu ASP.NET Core Razor vykreslování komponent). Kód nevytvoří nekonečné smyčky, protože StateHasChanged se volá pouze tehdy, když data je null.

Pages/PrerenderedInterop2.razor:

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

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

<p>
    Set value via JS interop call:
</p>

<div id="val-set-by-interop" @ref="divElement"></div>

@code {
    private string? data;
    private ElementReference divElement;

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

            StateHasChanged();
        }
    }
}

Poznámka

Předchozí příklad znečišťuje klienta globálními metodami. Lepší přístup v produkčních aplikacích najdete v izolaci JavaScriptu v modulech JavaScriptu.

Příklad:

export setElementText2 = (element, text) => {
  element.innerText = text;
  return text;
};

Synchronní JS spolupráce v Blazor WebAssembly aplikacích

Tato část platí jenom pro Blazor WebAssembly aplikace.

Volání zprostředkovatele komunikace JS jsou ve výchozím nastavení asynchronní, bez ohledu na to, jestli volaný kód je synchronní nebo asynchronní. Volání jsou ve výchozím nastavení asynchronní, aby se zajistilo, že komponenty jsou kompatibilní jak mezi modely hostování Blazor, tak Blazor Server a Blazor WebAssembly. Všechny Blazor ServerJS volání interopu musí být asynchronní, protože se odesílají přes síťové připojení.

Pokud víte, že vaše aplikace běží Blazor WebAssemblyjenom někdy, můžete se rozhodnout provádět synchronní JS volání interopu. To má mírně menší režii než provádění asynchronních volání a může vést k menšímu počtu cyklů vykreslování, protože během čekání na výsledky neexistuje žádný přechodný stav.

Pokud chcete v aplikaci provést synchronní volání z .NET do JavaScriptu Blazor WebAssembly , přetypujte IJSRuntime ho, aby IJSInProcessRuntime bylo JS volání zprostředkovatele komunikace:

@inject IJSRuntime JS

...

@code {
    protected override void HandleSomeEvent()
    {
        var jsInProcess = (IJSInProcessRuntime)JS;
        var value = jsInProcess.Invoke<string>("javascriptFunctionIdentifier");
    }
}

Při práci s aplikacemi IJSObjectReference ASP.NET Core 5.0 nebo novějšími Blazor WebAssembly můžete místo toho synchronně použítIJSInProcessObjectReference:

private IJSInProcessObjectReference module;

...

module = await JS.InvokeAsync<IJSInProcessObjectReference>("import", "./scripts.js");

Umístění JavaScriptu

Načtěte kód JavaScriptu (JS) pomocí libovolného přístupu popsaného v článku s přehledem interoperability JavaScriptu (JSinterop):

Informace o izolace skriptů v JS modulech najdete v části javascriptové izolace modulů .

Upozornění

Neumisťujte <script> značku do souboru komponenty (.razor), protože <script> značku nelze dynamicky aktualizovat.

Izolace JavaScriptu v modulech JavaScriptu

Blazor umožňuje izolaci JavaScriptu (JS) ve standardních modulech JavaScriptu (specifikace ECMAScript).

Izolace JS poskytuje následující výhody:

  • Import JS už znečišťuje globální obor názvů.
  • Uživatelé knihovny a komponent už nemusí importovat související JS.

Například následující JS modul exportuje JS funkci pro zobrazení výzvy okna prohlížeče. Do externího JS souboru umístěte následující JS kód.

wwwroot/scripts.js:

export function showPrompt(message) {
  return prompt(message, 'Type anything here');
}

Přidejte předchozí JS modul do aplikace nebo knihovny tříd jako statický webový prostředek ve wwwroot složce a pak modul naimportujte do kódu .NET voláním InvokeAsyncIJSRuntime instance.

IJSRuntime importuje modul jako IJSObjectReferenceobjekt, který představuje odkaz na JS objekt z kódu .NET. Použijte k IJSObjectReference vyvolání exportovaných JS funkcí z modulu.

Pages/CallJsExample6.razor:

@page "/call-js-example-6"
@implements IAsyncDisposable
@inject IJSRuntime JS

<h1>Call JS Example 6</h1>

<p>
    <button @onclick="TriggerPrompt">Trigger browser window prompt</button>
</p>

<p>
    @result
</p>

@code {
    private IJSObjectReference module;
    private string result;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            module = await JS.InvokeAsync<IJSObjectReference>("import", 
                "./scripts.js");
        }
    }

    private async Task TriggerPrompt()
    {
        result = await Prompt("Provide some text");
    }

    public async ValueTask<string> Prompt(string message)
    {
        return await module.InvokeAsync<string>("showPrompt", message);
    }

    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        if (module is not null)
        {
            await module.DisposeAsync();
        }
    }
}

V předchozím příkladu:

  • Podle konvence import je identifikátor speciálním identifikátorem používaným speciálně pro import JS modulu.
  • Zadejte externí JS soubor modulu pomocí stabilní cesty statického webového prostředku: ./{SCRIPT PATH AND FILENAME (.js)}, kde:
    • K vytvoření správné cesty statického prostředku k souboru JS se vyžaduje segment cesty pro aktuální adresář (./).
    • Zástupný symbol {SCRIPT PATH AND FILENAME (.js)} je cesta a název souboru v části wwwroot.

Dynamické importování modulu vyžaduje požadavek na síť, takže jej lze dosáhnout pouze asynchronním voláním InvokeAsync.

IJSInProcessObjectReference představuje odkaz na JS objekt, jehož funkce lze v aplikacích volat synchronně Blazor WebAssembly . Další informace najdete v části Synchronní JS spolupráce v Blazor WebAssembly aplikacích .

Poznámka

JS Pokud externí soubor poskytuje Razor knihovna tříd, zadejte soubor modulu JS pomocí stabilní cesty statického webového prostředku: ./_content/{PACKAGE ID}/{SCRIPT PATH AND FILENAME (.js)}

  • K vytvoření správné cesty statického prostředku k souboru JS se vyžaduje segment cesty pro aktuální adresář (./).
  • Zástupný symbol {PACKAGE ID} je ID balíčku knihovny. Pokud v souboru projektu není zadaný parametr <PackageId>, jako ID balíčku se ve výchozím nastavení použije název sestavení projektu. V následujícím příkladu je ComponentLibrary název sestavení knihovny a soubor projektu knihovny nezadává <PackageId>.
  • Zástupný symbol {SCRIPT PATH AND FILENAME (.js)} je cesta a název souboru v části wwwroot. V následujícím příkladu se externí JS soubor (script.js) umístí do složky knihovny tříd wwwroot .
var module = await js.InvokeAsync<IJSObjectReference>(
    "import", "./_content/ComponentLibrary/scripts.js");

Další informace najdete v tématu Využívání komponent ASP.NET Core Razor z knihovny tříd Razor (RCL).

Zachycení odkazů na prvky

Některé scénáře vzájemné spolupráce JavaScriptuJS vyžadují odkazy na elementy HTML. Knihovna uživatelského rozhraní může například vyžadovat odkaz na element pro inicializaci nebo možná budete muset volat rozhraní API podobná příkazům prvku, například click nebo play.

Zachytávání odkazů na elementy HTML v komponentě pomocí následujícího přístupu:

  • @ref Přidejte atribut do elementu HTML.
  • Definujte pole typu ElementReference , jehož název odpovídá hodnotě atributu @ref .

Následující příklad ukazuje zachycení odkazu na username<input> prvek:

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

@code {
    private ElementReference username;
}

Upozornění

Pomocí odkazu na prvek můžete ztlumit obsah prázdného prvku, který nepracuje s Blazor. Tento scénář je užitečný, když rozhraní API třetí strany poskytuje obsah prvku. Vzhledem k tomu Blazor , že s prvkem nepracuje, neexistuje možnost konfliktu mezi Blazorreprezentací prvku a modelem DOM (Document Object Model).

V následujícím příkladu je nebezpečné ztlumit obsah neuspořádaného seznamu (ul), protože Blazor komunikuje s objektem DOM a naplní položky seznamu tohoto prvku (<li>) z objektu Todos :

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

Pokud JS ztlumí obsah elementu MyList a Blazor pokusí se použít rozdíly u elementu, rozdíly nebudou odpovídat dom.

Další informace najdete v tématu ASP.NET Core Blazor interoperabilita JavaScriptu (JSinterop).

Kód ElementReference se předává JS prostřednictvím JS zprostředkovatele komunikace. Kód JS obdrží HTMLElement instanci, kterou může používat s normálními rozhraními API DOM. Například následující kód definuje metodu rozšíření .NET (TriggerClickEvent), která umožňuje odeslat kliknutí myší na prvek.

Funkce JS vytvoří click událost u předaného elementu HTML (element):clickElement

window.interopFunctions = {
  clickElement : function (element) {
    element.click();
  }
}

Pokud chcete volat JS funkci, která nevrací hodnotu, použijte JSRuntimeExtensions.InvokeVoidAsync. Následující kód aktivuje událost na straně click klienta voláním předchozí JS funkce zachycenou ElementReference:

@inject IJSRuntime JS

<button @ref="exampleButton">Example Button</button>

<button @onclick="TriggerClick">
    Trigger click event on <code>Example Button</code>
</button>

@code {
    private ElementReference exampleButton;

    public async Task TriggerClick()
    {
        await JS.InvokeVoidAsync(
            "interopFunctions.clickElement", exampleButton);
    }
}

Pokud chcete použít metodu rozšíření, vytvořte statickou metodu IJSRuntime rozšíření, která obdrží instanci:

public static async Task TriggerClickEvent(this ElementReference elementRef, 
    IJSRuntime js)
{
    await js.InvokeVoidAsync("interopFunctions.clickElement", elementRef);
}

Metoda clickElement je volána přímo na objektu. Následující příklad předpokládá, že TriggerClickEvent metoda je k dispozici z JsInteropClasses oboru názvů:

@inject IJSRuntime JS
@using JsInteropClasses

<button @ref="exampleButton">Example Button</button>

<button @onclick="TriggerClick">
    Trigger click event on <code>Example Button</code>
</button>

@code {
    private ElementReference exampleButton;

    public async Task TriggerClick()
    {
        await exampleButton.TriggerClickEvent(JS);
    }
}

Důležité

Proměnná exampleButton se naplní pouze po vykreslení komponenty. Pokud se do kódu předá nepřepsaný ElementReference kód, JS obdrží kód hodnotu null.JS Chcete-li manipulovat s odkazy na elementy po dokončení vykreslování komponenty, použijte OnAfterRenderAsync metody životního cyklu součásti.OnAfterRender

Při práci s obecnými typy a vrácení hodnoty použijte ValueTask<TResult>:

public static ValueTask<T> GenericMethod<T>(this ElementReference elementRef, 
    IJSRuntime js)
{
    return js.InvokeAsync<T>("{JAVASCRIPT FUNCTION}", elementRef);
}

Zástupný {JAVASCRIPT FUNCTION} symbol je JS identifikátor funkce.

GenericMethod je volána přímo na objekt s typem. Následující příklad předpokládá, že GenericMethod je k dispozici z JsInteropClasses oboru názvů:

@inject IJSRuntime JS
@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>(JS);
    }
}

Referenční prvky napříč komponentami

Nelze ElementReference předat mezi komponentami, protože:

Aby nadřazená komponenta zpřístupnit prvek jiným komponentám, může nadřazená komponenta:

  • Povolit podřízeným komponentám registraci zpětných volání.
  • Vyvolá zaregistrované zpětné volání během OnAfterRender události s předaným odkazem na element. Tento přístup nepřímo umožňuje podřízeným komponentám pracovat s odkazem na element nadřazeného objektu.

Přidejte následující styl do <head> (Blazor WebAssemblywwwroot/index.html) nebo Pages/_Host.cshtml (Blazor Server):

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

Do uzavírací </body> značky wwwroot/index.html () nebo (Blazor ServerBlazor WebAssembly) přidejte Pages/_Host.cshtml následující skript:

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

Pages/CallJsExample7.razor (nadřazená komponenta):

@page "/call-js-example-7"

<h1>Call JS Example 7</h1>

<h2 @ref="title">Hello, world!</h2>

Welcome to your new app.

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

Pages/CallJsExample7.razor.cs:

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

namespace BlazorSample.Pages
{
    public partial class CallJsExample7 : 
        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, 
                CallJsExample7 self)
            {
                Observer = observer;
                Self = self;
            }

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

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

V předchozím příkladu je BlazorSample obor názvů aplikace s komponentami ve Pages složce. Pokud kód testujete místně, aktualizujte obor názvů.

Shared/SurveyPrompt.razor (podřízená komponenta):

@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:

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();

            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();
        }
    }
}

V předchozím příkladu je BlazorSample obor názvů aplikace se sdílenými komponentami ve Shared složce. Pokud kód testujete místně, aktualizujte obor názvů.

Posílení volání zprostředkovatele komunikace v JavaScriptu

Tato část se primárně vztahuje na Blazor Server aplikace, ale Blazor WebAssembly aplikace můžou také nastavit JS časové limity pro interoperabilitu, pokud to podmínky vyžadují.

V Blazor Server aplikacích může spolupráce JavaScriptu (JS) selhat kvůli chybám sítě a měla by být považována za nespolehlivé. Ve výchozím nastavení Blazor Server aplikace používají pro volání zprostředkovatele komunikace časový limit JS o jednu minutu. Pokud aplikace může tolerovat agresivnější časový limit, nastavte časový limit pomocí jednoho z následujících přístupů.

Nastavte globální časový limit v Startup.ConfigureServices metoděStartup.cs:CircuitOptions.JSInteropDefaultCallTimeout

services.AddServerSideBlazor(
    options => options.JSInteropDefaultCallTimeout = {TIMEOUT});

Zástupný {TIMEOUT} symbol je TimeSpan (například TimeSpan.FromSeconds(80)).

Nastavte časový limit volání v kódu komponenty. Zadaný časový limit přepíše globální časový limit nastavený pomocí JSInteropDefaultCallTimeout:

var result = await JS.InvokeAsync<string>("{ID}", {TIMEOUT}, new[] { "Arg1" });

V předchozím příkladu:

  • Zástupný {TIMEOUT} symbol je TimeSpan (například TimeSpan.FromSeconds(80)).
  • Zástupný {ID} symbol je identifikátor funkce, který má vyvolat. Například hodnota someScope.someFunction vyvolá funkci window.someScope.someFunction.

I když běžnou příčinou JS selhání vzájemné spolupráce jsou selhání sítě v Blazor Server aplikacích, časové limity volání pro jednotlivé volání je možné nastavit pro JS volání zprostředkovatele v Blazor WebAssembly aplikacích. I když v Blazor WebAssembly aplikaci neexistuje žádný SignalR okruh, JS volání interoperability můžou selhat z jiných důvodů, které platí v Blazor WebAssembly aplikacích.

Další informace o vyčerpání prostředků najdete v doprovodných materiálech ke zmírnění hrozeb pro ASP.NET Core Blazor Server.

Vyhněte se cyklických odkazům na objekty.

Objekty, které obsahují cyklické odkazy, nelze serializovat v klientovi pro jednu z těchto objektů:

  • Volání metody .NET
  • Volání javascriptové metody z jazyka C#, pokud má návratový typ cyklický odkaz.

Knihovny JavaScriptu, které vykreslují uživatelské rozhraní

Někdy můžete chtít použít knihovny JavaScriptu (JS), které vytvářejí viditelné prvky uživatelského rozhraní v prohlížeči Document Object Model (DOM). Na první pohled to může vypadat jako obtížné, protože Blazorsystém rozdílů spoléhá na kontrolu nad stromem prvků DOM a narazí na chyby, pokud nějaký externí kód ztlumí strom DOM a zneplatní jeho mechanismus použití rozdílů. Nejedná se o Blazorkonkrétní omezení. Ke stejné výzvě dochází u jakékoli architektury uživatelského rozhraní založené na rozdílech.

Naštěstí je snadné spolehlivě vložit externě generované uživatelské rozhraní do Razor uživatelského rozhraní komponenty. Doporučeným postupem je mít kód komponenty (.razor soubor) vytvoření prázdného prvku. Pokud jde o Blazorrozdílový systém, prvek je vždy prázdný, takže vykreslovací modul se nespustí do prvku a místo toho ponechá jeho obsah sám. Díky tomu je bezpečné naplnit prvek libovolným externě spravovaným obsahem.

Následující příklad ukazuje koncept. V rámci if příkazu, kdy firstRender je true, interagujte mimo unmanagedElementBlazor použití JS zprostředkovatele komunikace. Voláním externí JS knihovny například naplníte prvek. Blazor ponechá obsah prvku sám, dokud se tato komponenta neodebere. Když je komponenta odebrána, odebere se také celý podstrom DOM komponenty.

<h1>Hello! This is a Razor component rendered at @DateTime.Now</h1>

<div @ref="unmanagedElement"></div>

@code {
    private ElementReference unmanagedElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            ...
        }
    }
}

Podívejte se na následující příklad, který vykreslí interaktivní mapu pomocí opensourcových rozhraní API Mapboxu.

Následující JS modul se umístí do aplikace nebo zpřístupní z Razor knihovny tříd.

Poznámka

Pokud chcete vytvořit mapu Mapboxu , získejte přístupový token z mapboxu Sign in a zadejte ho tam, kde {ACCESS TOKEN} se zobrazí v následujícím kódu.

wwwroot/mapComponent.js:

import 'https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.js';

mapboxgl.accessToken = '{ACCESS TOKEN}';

export function addMapToElement(element) {
  return new mapboxgl.Map({
    container: element,
    style: 'mapbox://styles/mapbox/streets-v11',
    center: [-74.5, 40],
    zoom: 9
  });
}

export function setMapCenter(map, latitude, longitude) {
  map.setCenter([longitude, latitude]);
}

Chcete-li vytvořit správný styl, přidejte na stránku HTML hostitele následující značku šablony stylů.

V wwwroot/index.html (Blazor WebAssembly) nebo () nebo Pages/_Host.cshtml (Blazor Server) přidejte do značky elementu <head> následující <link> prvek:

<link href="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css" 
    rel="stylesheet" />

Pages/CallJsExample8.razor:

@page "/call-js-example-8"
@implements IAsyncDisposable
@inject IJSRuntime JS

<h1>Call JS Example 8</h1>

<div @ref="mapElement" style='width:400px;height:300px'></div>

<button @onclick="() => ShowAsync(51.454514, -2.587910)">Show Bristol, UK</button>
<button @onclick="() => ShowAsync(35.6762, 139.6503)">Show Tokyo, Japan</button>

@code
{
    private ElementReference mapElement;
    private IJSObjectReference mapModule;
    private IJSObjectReference mapInstance;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            mapModule = await JS.InvokeAsync<IJSObjectReference>(
                "import", "./mapComponent.js");
            mapInstance = await mapModule.InvokeAsync<IJSObjectReference>(
                "addMapToElement", mapElement);
        }
    }

    private async Task ShowAsync(double latitude, double longitude)
        => await mapModule.InvokeVoidAsync("setMapCenter", mapInstance, latitude, 
            longitude).AsTask();

    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        if (mapInstance is not null)
        {
            await mapInstance.DisposeAsync();
        }

        if (mapModule is not null)
        {
            await mapModule.DisposeAsync();
        }
    }
}

Předchozí příklad vytvoří interaktivní mapové uživatelské rozhraní. Uživatel:

  • Lze přetáhnout a posunout nebo přiblížit.
  • Výběrem tlačítek přejděte na předdefinovaná umístění.

Mapbox street map of Tokyo, Japan with buttons to select Bristol, United Kingdom and Tokyo, Japan

V předchozím příkladu:

  • Co se týče, je <div> sousu @ref="mapElement" prázdný Blazor . Skript mapbox-gl.js může bezpečně naplnit prvek a upravit jeho obsah. Tuto techniku použijte s libovolnou JS knihovnou, která vykresluje uživatelské rozhraní. Komponenty můžete vložit z architektury SPA třetích stran JS uvnitř Razor komponent, pokud se nepokouší kontaktovat a upravit další části stránky. Není bezpečné, aby externí JS kód upravil prvky, které Blazor se nepovažují za prázdné.
  • Při použití tohoto přístupu mějte na paměti pravidla týkající Blazor se zachování nebo zničení prvků DOM. Komponenta bezpečně zpracovává události kliknutí na tlačítko a aktualizuje existující instanci mapy, protože prvky MODELU DOM jsou ve výchozím nastavení zachovány. Pokud jste vykreslovali seznam prvků mapování ze @foreach smyčky, chcete použít @key k zajištění zachování instancí komponent. V opačném případě by změny v datech seznamu mohly způsobit, že instance součástí zachovají stav předchozích instancí nežádoucím způsobem. Další informace naleznete v tématu using @key to preserve elements and components.
  • Příklad zapouzdřuje JS logiku a závislosti v modulu ES6 a načte modul dynamicky pomocí identifikátoru import . Další informace najdete v tématu Izolace JavaScriptu v modulech JavaScriptu.

Omezení velikosti u volání interoperability JavaScriptu

Tato část platí jenom pro Blazor Server aplikace. V Blazor WebAssemblyarchitektuře se neomezuje velikost vstupů a výstupů interoperability JavaScriptuJS.

Volání Blazor Serverzprostředkovatele komunikace jsou omezená velikostí maximální velikosti příchozích SignalR zpráv povolených pro metody centra, která se vynucuje HubOptions.MaximumReceiveMessageSize (výchozí hodnota: 32 kBJS). JS zprávy .NET SignalR větší než MaximumReceiveMessageSize vyvolá chybu. Architektura neukládá omezení velikosti SignalR zprávy z centra na klienta.

Pokud SignalR protokolování není nastavené na Ladění nebo Trasování, zobrazí se chyba velikosti zprávy pouze v konzole vývojářských nástrojů prohlížeče:

Chyba: Připojení bylo odpojeno s chybou Chyba: Server vrátil chybu při zavření: Připojení se ukončilo s chybou.

Pokud SignalR je protokolování na straně serveru nastaveno na Ladění nebo Trasování, zobrazí se protokolování InvalidDataException na straně serveru kvůli chybě velikosti zprávy.

appsettings.Development.json:

{
  "DetailedErrors": true,
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information",
      "Microsoft.AspNetCore.SignalR": "Debug"
    }
  }
}

Chyba:

System.IO.InvalidDataException: Byla překročena maximální velikost zprávy 32768B. Velikost zprávy je možné nakonfigurovat v AddHubOptions.

Zvyšte limit nastavením MaximumReceiveMessageSize v Startup.ConfigureServices:

services.AddServerSideBlazor()
    .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);

Zvýšení limitu SignalR velikosti příchozích zpráv má náklady na vyžadování dalších prostředků serveru a zpřístupňuje server zvýšeným rizikům od škodlivého uživatele. Kromě toho může čtení velkého množství obsahu do paměti jako řetězce nebo bajtová pole způsobit také přidělení, která fungují špatně s uvolňováním paměti, což vede k dalším sankcím za výkon.

Jednou z možností čtení velkých datových částí je odeslat obsah v menších blocích a zpracovat datovou část jako Stream. To se dá použít při čtení velkých JSdatových částí ON nebo pokud jsou data k dispozici jako JS nezpracované bajty. Příklad, který ukazuje odesílání velkých binárních datových částí, Blazor Server které používají techniky podobné InputFile komponentě, najdete v ukázkové aplikaci Binary Submit.

Poznámka

Odkazy na dokumentaci k referenčnímu zdroji .NET obvykle načítají výchozí větev úložiště, která představuje aktuální vývoj pro příští verzi .NET. Pokud chcete vybrat značku pro konkrétní verzi, použijte rozevírací seznam pro přepnutí větví nebo značek. Další informace najdete v tématu Jak vybrat značku verze zdrojového kódu ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Při vývoji kódu, který přenáší velké množství dat mezi JS aplikacemi a Blazor v Blazor Server aplikacích, zvažte následující pokyny:

  • Rozdělte data na menší části a posílejte datové segmenty postupně, dokud server neobdrží všechna data.
  • Nepřidělujte velké objekty v kódu jazyka JS C#.
  • Při odesílání nebo příjmu dat neblokujte hlavní vlákno uživatelského rozhraní po dlouhou dobu.
  • Bezplatná spotřebovaná paměť při dokončení nebo zrušení procesu
  • Vynucujte pro účely zabezpečení následující další požadavky:
    • Deklarujte maximální velikost souboru nebo dat, které lze předat.
    • Deklarujte minimální rychlost nahrávání z klienta na server.
  • Po přijetí dat serverem můžou být tato data:
    • Dočasně uložená v vyrovnávací paměti, dokud se neshromáždí všechny segmenty.
    • Spotřebováno okamžitě. Data se například dají uložit okamžitě v databázi nebo zapisovat na disk při přijetí jednotlivých segmentů.

Unmarshalled JavaScript interop

Blazor WebAssembly komponenty můžou mít nízký výkon, pokud jsou objekty .NET serializovány pro interop (JSJavaScript) a některé z následujících hodnot jsou pravdivé:

  • Velký objem objektů .NET se rychle serializuje. Například nízký výkon může vést k tomu, že JS se volání vzájemné komunikace provádí na základě přesouvání vstupního zařízení, například otáčení kolečka myši.
  • Velké objekty .NET nebo mnoho objektů .NET musí být serializováno pro JS interoperabilitu. Například nízký výkon může vést k tomu, že JS volání vzájemné komunikace vyžadují serializaci desítek souborů.

IJSUnmarshalledObjectReference představuje odkaz na JS objekt, jehož funkce lze vyvolat bez režie serializace dat .NET.

V následujícím příkladu:

  • Struktura obsahující řetězec a celé číslo se předá neserializované .JS
  • JS funkce zpracovávají data a vracejí logické hodnoty nebo řetězec volajícímu.
  • Řetězec JS není přímo převedený na objekt .NET string . Funkce unmarshalledFunctionReturnString volá BINDING.js_string_to_mono_string ke správě převodu JS řetězce.

Poznámka

Následující příklady nejsou typické případy použití pro tento scénář, protože struktura předaná JS není výsledkem nízkého výkonu komponent. Příklad používá malý objekt pouze k předvedení konceptů pro předávání neserializovaných dat .NET.

Do (Blazor WebAssembly) nebo () nebo Pages/_Host.cshtml (Blazor Server) umístěte následující <script> blok wwwroot/index.html . Alternativně můžete umístit JS do externího JS souboru odkazovaného uvnitř koncové </body> značky, <script src="{SCRIPT PATH AND FILE NAME (.js)}></script>kde {SCRIPT PATH AND FILE NAME (.js)} zástupný symbol je cesta a název souboru skriptu.

<script>
  window.returnObjectReference = () => {
    return {
      unmarshalledFunctionReturnBoolean: function (fields) {
        const name = Blazor.platform.readStringField(fields, 0);
        const year = Blazor.platform.readInt32Field(fields, 8);

        return name === "Brigadier Alistair Gordon Lethbridge-Stewart" &&
            year === 1968;
      },
      unmarshalledFunctionReturnString: function (fields) {
        const name = Blazor.platform.readStringField(fields, 0);
        const year = Blazor.platform.readInt32Field(fields, 8);

        return BINDING.js_string_to_mono_string(`Hello, ${name} (${year})!`);
      }
    };
  }
</script>

Upozornění

Název js_string_to_mono_string funkce, chování a existence se v budoucí verzi .NET může změnit. Příklad:

  • Funkce se pravděpodobně přejmenuje.
  • Samotná funkce může být odebrána ve prospěch automatického převodu řetězců architekturou.

Pages/CallJsExample10.razor:

@page "/call-js-example-10"
@using System.Runtime.InteropServices
@using Microsoft.JSInterop
@inject IJSRuntime JS

<h1>Call JS Example 10</h1>

@if (callResultForBoolean)
{
    <p>JS interop was successful!</p>
}

@if (!string.IsNullOrEmpty(callResultForString))
{
    <p>@callResultForString</p>
}

<p>
    <button @onclick="CallJSUnmarshalledForBoolean">
        Call Unmarshalled JS & Return Boolean
    </button>
    <button @onclick="CallJSUnmarshalledForString">
        Call Unmarshalled JS & Return String
    </button>
</p>

<p>
    <a href="https://www.doctorwho.tv">Doctor Who</a>
    is a registered trademark of the <a href="https://www.bbc.com/">BBC</a>.
</p>

@code {
    private bool callResultForBoolean;
    private string callResultForString;

    private void CallJSUnmarshalledForBoolean()
    {
        var unmarshalledRuntime = (IJSUnmarshalledRuntime)JS;

        var jsUnmarshalledReference = unmarshalledRuntime
            .InvokeUnmarshalled<IJSUnmarshalledObjectReference>(
                "returnObjectReference");

        callResultForBoolean = 
            jsUnmarshalledReference.InvokeUnmarshalled<InteropStruct, bool>(
                "unmarshalledFunctionReturnBoolean", GetStruct());
    }

    private void CallJSUnmarshalledForString()
    {
        var unmarshalledRuntime = (IJSUnmarshalledRuntime)JS;

        var jsUnmarshalledReference = unmarshalledRuntime
            .InvokeUnmarshalled<IJSUnmarshalledObjectReference>(
                "returnObjectReference");

        callResultForString = 
            jsUnmarshalledReference.InvokeUnmarshalled<InteropStruct, string>(
                "unmarshalledFunctionReturnString", GetStruct());
    }

    private InteropStruct GetStruct()
    {
        return new InteropStruct
        {
            Name = "Brigadier Alistair Gordon Lethbridge-Stewart",
            Year = 1968,
        };
    }

    [StructLayout(LayoutKind.Explicit)]
    public struct InteropStruct
    {
        [FieldOffset(0)]
        public string Name;

        [FieldOffset(8)]
        public int Year;
    }
}

Pokud instance IJSUnmarshalledObjectReference není v kódu jazyka C# odstraněna, je možné ji vyhodit JS. Následující dispose funkce odstraní odkaz na objekt při zavolání z JS:

window.exampleJSObjectReferenceNotDisposedInCSharp = () => {
  return {
    dispose: function () {
      DotNet.disposeJSObjectReference(this);
    },

    ...
  };
}

Typy polí lze převést z JS objektů na objekty .NET pomocí js_typed_array_to_array, ale JS pole musí být typové pole. Pole z JS kódu jazyka C# lze číst jako pole objektů .NET (object[]).

Jiné datové typy, například pole řetězců, lze převést, ale vyžadují vytvoření nového objektu monomatice (mono_obj_array_new) a nastavení jeho hodnoty (mono_obj_array_set).

Upozornění

JSfunkce poskytované architekturouBlazor, například js_typed_array_to_array, a mono_obj_array_newmono_obj_array_set, podléhají změnám názvů, změnám chování nebo odebráním v budoucích verzích .NET.

Zachycení výjimek JavaScriptu

Pokud chcete zachytit JS výjimky, zabalte JS interop do try-catch bloku a zachyťte JSException.

V následujícím příkladu nonFunctionJS funkce neexistuje. Pokud funkce není nalezena, JSException je zachytána s chybou Message , která označuje následující chybu:

Could not find 'nonFunction' ('nonFunction' was undefined).

Pages/CallJsExample11.razor:

@page "/call-js-example-11"
@inject IJSRuntime JS

<h1>Call JS Example 11</h1>

<p>
    <button @onclick="CatchUndefinedJSFunction">Catch Exception</button>
</p>

<p>
    @result
</p>

<p>
    @errorMessage
</p>

@code {
    private string errorMessage;
    private string result;

    private async Task CatchUndefinedJSFunction()
    {
        try
        {
            result = await JS.InvokeAsync<string>("nonFunction");
        }
        catch (JSException e)
        {
            errorMessage = $"Error Message: {e.Message}";
        }
    }
}

Další materiály

Informace o volání metod .NET z JS, naleznete v tématu Volání metod .NET z funkcí JavaScriptu v ASP.NET Core Blazor.

Pokud chcete volat JS z .NET, vložte IJSRuntime abstrakci a zavolejte jednu z následujících metod:

Pro předchozí metody .NET, které volají JS funkce:

  • Identifikátor funkce (String) je relativní k globálnímu oboru (window). Zavolat window.someScope.someFunction, identifikátor je someScope.someFunction. Před zavolání není nutné funkci zaregistrovat.
  • Předejte funkci libovolný počet argumentů JSObject[]JS typu ON-serializable.
  • TokenCancellationToken zrušení () šíří oznámení, že operace by se měly zrušit.
  • TimeSpan představuje časový limit pro JS operaci.
  • Návratový TValue typ musí být JStaké zapnutý serializovatelný. TValue měl by odpovídat typu .NET, který se nejlépe mapuje na vrácený JStyp ON.
  • Funkce A JS Promise je vrácena pro InvokeAsync metody. InvokeAsync rozbalí Promise a vrátí hodnotu očekávanou Promisehodnotou .

U Blazor Server aplikací s povoleným prerenderingem není volání JS během počátečního předkreslování možné. JS volání interop musí být odložena, dokud se připojení k prohlížeči nenaváže. Další informace najdete v části Zjistit, kdy Blazor Server je aplikace předem připravená .

Následující příklad je založený na dekodéru založeném JSna TextDecoderbázi . Příklad ukazuje, jak vyvolat JS funkci z metody jazyka C#, která předčítá požadavek z kódu vývojáře na existující JS rozhraní API. Funkce JS přijímá bajtové pole z metody jazyka C#, dekóduje pole a vrátí text do komponenty pro zobrazení.

Do koncové značky wwwroot/index.html () nebo Pages/_Host.cshtml (Blazor WebAssemblyBlazor Server) přidejte následující JS</body> kód:

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

CallJsExample1 Následující komponenta:

  • convertArrayJS Vyvolá funkci při InvokeAsync výběru tlačítka (Convert Array).
  • JS Po zavolání funkce se předané pole převede na řetězec. Řetězec se vrátí do komponenty pro zobrazení (text).

Pages/CallJsExample1.razor:

@page "/call-js-example-1"
@inject IJSRuntime JS

<h1>Call JS <code>convertArray</code> Function</h1>

<p>
    <button @onclick="ConvertArray">Convert Array</button>
</p>

<p>
    @text
</p>

<p>
    Quote ©2005 <a href="https://www.uphe.com">Universal Pictures</a>: 
    <a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
    <a href="https://www.imdb.com/name/nm0472710/">David Krumholtz on IMDB</a>
</p>

@code {
    private MarkupString text;

    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()
    {
        text = new MarkupString(await JS.InvokeAsync<string>("convertArray", 
            quoteArray));
    }
}

Rozhraní JAVAScript API omezené na gesta uživatelů

Tato část platí jenom pro Blazor Server aplikace.

Některá rozhraní JAVAScript (JS) api prohlížeče se dají spustit jenom v kontextu uživatelského gesta, například pomocí Fullscreen API (dokumentace k MDN). Tato rozhraní API nelze volat prostřednictvím JS mechanismu vzájemné komunikace v Blazor Server aplikacích, protože zpracování událostí uživatelského rozhraní se provádí asynchronně a obecně už v kontextu gesta uživatele. Aplikace musí zpracovat událost uživatelského rozhraní zcela v JavaScriptu, takže místo atributu @onclick direktivy použijte onclick (dokumentaciBlazorMDN).

Vyvolání funkcí JavaScriptu bez čtení vrácené hodnoty (InvokeVoidAsync)

Použít InvokeVoidAsync , když:

Uvnitř koncové </body> značky wwwroot/index.html (Blazor WebAssembly) nebo Pages/_Host.cshtml (Blazor Server) zadejte displayTickerAlert1JS funkci. Funkce se volá a InvokeVoidAsync nevrací hodnotu:

<script>
  window.displayTickerAlert1 = (symbol, price) => {
    alert(`${symbol}: $${price}!`);
  };
</script>

Příklad komponenty () (.razorInvokeVoidAsync)

TickerChanged volá metodu handleTickerChanged1 v následující CallJsExample2 komponentě.

Pages/CallJsExample2.razor:

@page "/call-js-example-2"
@inject IJSRuntime JS

<h1>Call JS Example 2</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol != null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
    private Random r = new Random();
    private string stockSymbol;
    private decimal price;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
    }
}

Příklad třídy (.cs) (InvokeVoidAsync)

JsInteropClasses1.cs:

using System.Threading.Tasks;
using Microsoft.JSInterop;

public class JsInteropClasses1
{
    private readonly IJSRuntime js;

    public JsInteropClasses1(IJSRuntime js)
    {
        this.js = js;
    }

    public async ValueTask TickerChanged(string symbol, decimal price)
    {
        await js.InvokeVoidAsync("displayTickerAlert1", symbol, price);
    }

    public void Dispose()
    {
    }
}

TickerChanged volá metodu handleTickerChanged1 v následující CallJsExample3 komponentě.

Pages/CallJsExample3.razor:

@page "/call-js-example-3"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call JS Example 3</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol != null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
    private Random r = new Random();
    private string stockSymbol;
    private decimal price;
    private JsInteropClasses1 jsClass;

    protected override void OnInitialized()
    {
        jsClass = new JsInteropClasses1(JS);
    }

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        await jsClass.TickerChanged(stockSymbol, price);
    }

    public void Dispose() => jsClass?.Dispose();
}

Vyvolání funkcí JavaScriptu a načtení vrácené hodnoty (InvokeAsync)

Použije se InvokeAsync , když by .NET měl přečíst výsledek JS volání.

Uvnitř koncové </body> značky wwwroot/index.html (Blazor WebAssembly) nebo Pages/_Host.cshtml (Blazor Server) zadejte displayTickerAlert2JS funkci. Následující příklad vrátí řetězec pro zobrazení volajícím:

<script>
  window.displayTickerAlert2 = (symbol, price) => {
    if (price < 20) {
      alert(`${symbol}: $${price}!`);
      return "User alerted in the browser.";
    } else {
      return "User NOT alerted.";
    }
  };
</script>

Příklad komponenty () (.razorInvokeAsync)

TickerChanged volá metodu handleTickerChanged2 a zobrazí vrácený řetězec v následující CallJsExample4 komponentě.

Pages/CallJsExample4.razor:

@page "/call-js-example-4"
@inject IJSRuntime JS

<h1>Call JS Example 4</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol != null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result != null)
{
    <p>@result</p>
}

@code {
    private Random r = new Random();
    private string stockSymbol;
    private decimal price;
    private string result;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        var interopResult = 
            await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol, price);
        result = $"Result of TickerChanged call for {stockSymbol} at " +
            $"{price.ToString("c")}: {interopResult}";
    }
}

Příklad třídy (.cs) (InvokeAsync)

JsInteropClasses2.cs:

using System.Threading.Tasks;
using Microsoft.JSInterop;

public class JsInteropClasses2
{
    private readonly IJSRuntime js;

    public JsInteropClasses2(IJSRuntime js)
    {
        this.js = js;
    }

    public async ValueTask<string> TickerChanged(string symbol, decimal price)
    {
        return await js.InvokeAsync<string>("displayTickerAlert2", symbol, price);
    }

    public void Dispose()
    {
    }
}

TickerChanged volá metodu handleTickerChanged2 a zobrazí vrácený řetězec v následující CallJsExample5 komponentě.

Pages/CallJsExample5.razor:

@page "/call-js-example-5"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call JS Example 5</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol != null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result != null)
{
    <p>@result</p>
}

@code {
    private Random r = new Random();
    private string stockSymbol;
    private decimal price;
    private JsInteropClasses2 jsClass;
    private string result;

    protected override void OnInitialized()
    {
        jsClass = new JsInteropClasses2(JS);
    }

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        var interopResult = await jsClass.TickerChanged(stockSymbol, price);
        result = $"Result of TickerChanged call for {stockSymbol} at " +
            $"{price.ToString("c")}: {interopResult}";
    }

    public void Dispose() => jsClass?.Dispose();
}

Scénáře generování dynamického obsahu

Pro dynamické generování obsahu pomocí BuildRenderTree použijte [Inject] atribut:

[Inject]
IJSRuntime JS { get; set; }

Zjištění, kdy Blazor Server je aplikace prerendering

Tato část se vztahuje na Blazor Server a hostované Blazor WebAssembly aplikace, které prerender komponenty Razor . Prerendering je pokryt prerender a integrace ASP.NET Core Razor komponent.

I když je aplikace prerendering, určité akce, jako je volání do JavaScriptu, nejsou možné. Komponenty se můžou při předkreslování vykreslit jinak.

V následujícím příkladu setElementText1 je funkce umístěna uvnitř elementu <head> . Funkce se volá a JSRuntimeExtensions.InvokeVoidAsync nevrací hodnotu.

Poznámka

Obecné pokyny k JS umístění a doporučení pro produkční aplikace najdete v tématu ASP.NET Core Blazor interoperabilita JavaScriptu (JSinterop)..

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

Upozornění

Předchozí příklad upraví objektový model dokumentu (DOM) přímo pro demonstrační účely. Ve většině scénářů se nedoporučuje přímo upravovat DOM pomocí JavaScriptu, protože JavaScript může ovlivnit Blazorsledování změn. Další informace najdete v tématu ASP.NET Core Blazor interoperabilita JavaScriptu (JSinterop).

Pokud chcete zpozdit volání interop JavaScriptu, dokud není zaručeno, že taková volání budou fungovat, přepište OnAfterRender{Async} událost životního cyklu. Tato událost se volá pouze po úplném vykreslení aplikace.

Pages/PrerenderedInterop1.razor:

@page "/prerendered-interop-1"
@using Microsoft.JSInterop
@inject IJSRuntime JS

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

@code {
    private ElementReference divElement;

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

Poznámka

Předchozí příklad znečišťuje klienta globálními metodami. Lepší přístup v produkčních aplikacích najdete v izolaci JavaScriptu v modulech JavaScriptu.

Příklad:

export setElementText1 = (element, text) => element.innerText = text;

Následující komponenta ukazuje, jak používat interop JavaScriptu jako součást logiky inicializace komponenty způsobem, který je kompatibilní s předdeenderingem. Komponenta ukazuje, že je možné aktivovat aktualizaci vykreslování z vnitřní OnAfterRenderAsyncčásti . Vývojář musí být opatrní, abyste se vyhnuli vytváření nekonečné smyčky v tomto scénáři.

V následujícím příkladu setElementText2 je funkce umístěna uvnitř elementu <head> . Funkce je volána s IJSRuntime.InvokeAsync a vrací hodnotu.

Poznámka

Obecné pokyny k JS umístění a doporučení pro produkční aplikace najdete v tématu ASP.NET Core Blazor interoperabilita JavaScriptu (JSinterop)..

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

Upozornění

Předchozí příklad upraví objektový model dokumentu (DOM) přímo pro demonstrační účely. Ve většině scénářů se nedoporučuje přímo upravovat DOM pomocí JavaScriptu, protože JavaScript může ovlivnit Blazorsledování změn. Další informace najdete v tématu ASP.NET Core Blazor interoperabilita JavaScriptu (JSinterop).

Kde JSRuntime.InvokeAsync je volána, se používá pouze v OnAfterRenderAsync a ne v žádné dřívější metodě životního cyklu, ElementReference protože neexistuje žádný javascriptový prvek, dokud se komponenta nevykreslí.

StateHasChangedje volána k opětovnému vykreslení komponenty s novým stavem získaným z volání interop JavaScriptu (další informace najdete v tématu ASP.NET Core Razor vykreslování komponent). Kód nevytvoří nekonečné smyčky, protože StateHasChanged se volá pouze tehdy, když data je null.

Pages/PrerenderedInterop2.razor:

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

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

<p>
    Set value via JS interop call:
</p>

<div id="val-set-by-interop" @ref="divElement"></div>

@code {
    private string? data;
    private ElementReference divElement;

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

            StateHasChanged();
        }
    }
}

Poznámka

Předchozí příklad znečišťuje klienta globálními metodami. Lepší přístup v produkčních aplikacích najdete v izolaci JavaScriptu v modulech JavaScriptu.

Příklad:

export setElementText2 = (element, text) => {
  element.innerText = text;
  return text;
};

Synchronní JS spolupráce v Blazor WebAssembly aplikacích

Tato část platí jenom pro Blazor WebAssembly aplikace.

Volání zprostředkovatele komunikace JS jsou ve výchozím nastavení asynchronní, bez ohledu na to, jestli volaný kód je synchronní nebo asynchronní. Volání jsou ve výchozím nastavení asynchronní, aby se zajistilo, že komponenty jsou kompatibilní jak mezi modely hostování Blazor, tak Blazor Server a Blazor WebAssembly. Všechny Blazor ServerJS volání interopu musí být asynchronní, protože se odesílají přes síťové připojení.

Pokud víte, že vaše aplikace běží Blazor WebAssemblyjenom někdy, můžete se rozhodnout provádět synchronní JS volání interopu. To má mírně menší režii než provádění asynchronních volání a může vést k menšímu počtu cyklů vykreslování, protože během čekání na výsledky neexistuje žádný přechodný stav.

Pokud chcete v aplikaci provést synchronní volání z .NET do JavaScriptu Blazor WebAssembly , přetypujte IJSRuntime ho, aby IJSInProcessRuntime bylo JS volání zprostředkovatele komunikace:

@inject IJSRuntime JS

...

@code {
    protected override void HandleSomeEvent()
    {
        var jsInProcess = (IJSInProcessRuntime)JS;
        var value = jsInProcess.Invoke<string>("javascriptFunctionIdentifier");
    }
}

Při práci s aplikacemi IJSObjectReference ASP.NET Core 5.0 nebo novějšími Blazor WebAssembly můžete místo toho synchronně použítIJSInProcessObjectReference:

private IJSInProcessObjectReference module;

...

module = await JS.InvokeAsync<IJSInProcessObjectReference>("import", "./scripts.js");

Umístění JavaScriptu

Načtěte kód JavaScriptu (JS) pomocí libovolného přístupu popsaného v článku s přehledem interoperability JavaScriptu (JSinterop):

Upozornění

Neumisťujte <script> značku do souboru komponenty (.razor), protože <script> značku nelze dynamicky aktualizovat.

Zachycení odkazů na prvky

Některé scénáře vzájemné spolupráce JavaScriptuJS vyžadují odkazy na elementy HTML. Knihovna uživatelského rozhraní může například vyžadovat odkaz na element pro inicializaci nebo možná budete muset volat rozhraní API podobná příkazům prvku, například click nebo play.

Zachytávání odkazů na elementy HTML v komponentě pomocí následujícího přístupu:

  • @ref Přidejte atribut do elementu HTML.
  • Definujte pole typu ElementReference , jehož název odpovídá hodnotě atributu @ref .

Následující příklad ukazuje zachycení odkazu na username<input> prvek:

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

@code {
    private ElementReference username;
}

Upozornění

Pomocí odkazu na prvek můžete ztlumit obsah prázdného prvku, který nepracuje s Blazor. Tento scénář je užitečný, když rozhraní API třetí strany poskytuje obsah prvku. Vzhledem k tomu Blazor , že s prvkem nepracuje, neexistuje možnost konfliktu mezi Blazorreprezentací prvku a modelem DOM (Document Object Model).

V následujícím příkladu je nebezpečné ztlumit obsah neuspořádaného seznamu (ul), protože Blazor komunikuje s objektem DOM a naplní položky seznamu tohoto prvku (<li>) z objektu Todos :

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

Pokud JS ztlumí obsah elementu MyList a Blazor pokusí se použít rozdíly u elementu, rozdíly nebudou odpovídat dom.

Další informace najdete v tématu ASP.NET Core Blazor interoperabilita JavaScriptu (JSinterop).

Kód ElementReference se předá JS prostřednictvím JS zprostředkovatele komunikace. Kód JS obdrží HTMLElement instanci, kterou může používat s normálními rozhraními API DOM. Například následující kód definuje metodu rozšíření .NET (TriggerClickEvent), která umožňuje odesílání kliknutí myší na prvek.

Funkce JSclickElement vytvoří click událost u předaného elementu HTML (element):

window.interopFunctions = {
  clickElement : function (element) {
    element.click();
  }
}

Pokud chcete volat JS funkci, která nevrací hodnotu, použijte JSRuntimeExtensions.InvokeVoidAsync. Následující kód aktivuje událost na straně click klienta voláním předchozí JS funkce s zachyceným ElementReferencekódem:

@inject IJSRuntime JS

<button @ref="exampleButton">Example Button</button>

<button @onclick="TriggerClick">
    Trigger click event on <code>Example Button</code>
</button>

@code {
    private ElementReference exampleButton;

    public async Task TriggerClick()
    {
        await JS.InvokeVoidAsync(
            "interopFunctions.clickElement", exampleButton);
    }
}

Pokud chcete použít metodu rozšíření, vytvořte statickou metodu IJSRuntime rozšíření, která obdrží instanci:

public static async Task TriggerClickEvent(this ElementReference elementRef, 
    IJSRuntime js)
{
    await js.InvokeVoidAsync("interopFunctions.clickElement", elementRef);
}

Metoda clickElement se volá přímo na objektu. Následující příklad předpokládá, že TriggerClickEvent metoda je k dispozici z JsInteropClasses oboru názvů:

@inject IJSRuntime JS
@using JsInteropClasses

<button @ref="exampleButton">Example Button</button>

<button @onclick="TriggerClick">
    Trigger click event on <code>Example Button</code>
</button>

@code {
    private ElementReference exampleButton;

    public async Task TriggerClick()
    {
        await exampleButton.TriggerClickEvent(JS);
    }
}

Důležité

Proměnná exampleButton se naplní pouze po vykreslení komponenty. Pokud se nepřečtený ElementReference kód předáJS, JS kód obdrží hodnotu .null Pokud chcete manipulovat s odkazy na prvky po dokončení vykreslování komponenty, použijte OnAfterRenderAsync metody životního cyklu komponent nebo OnAfterRender součásti.

Při práci s obecnými typy a vrácení hodnoty použijte ValueTask<TResult>:

public static ValueTask<T> GenericMethod<T>(this ElementReference elementRef, 
    IJSRuntime js)
{
    return js.InvokeAsync<T>("{JAVASCRIPT FUNCTION}", elementRef);
}

Zástupný {JAVASCRIPT FUNCTION} symbol je JS identifikátor funkce.

GenericMethod je volána přímo na objektu s typem. Následující příklad předpokládá, že GenericMethod je k dispozici z JsInteropClasses oboru názvů:

@inject IJSRuntime JS
@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>(JS);
    }
}

Referenční prvky napříč komponentami

Nelze ElementReference předat mezi komponentami, protože:

Aby nadřazená komponenta zpřístupnit prvek jiným komponentám, může nadřazená komponenta:

  • Povolit podřízeným komponentám registraci zpětných volání.
  • Vyvolá zaregistrované zpětné volání během OnAfterRender události s předaným odkazem na element. Tento přístup nepřímo umožňuje podřízeným komponentám pracovat s odkazem na element nadřazeného objektu.

Přidejte následující styl do <head> (Blazor WebAssemblywwwroot/index.html) nebo Pages/_Host.cshtml (Blazor Server):

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

Do uzavírací </body> značky wwwroot/index.html () nebo (Blazor ServerBlazor WebAssembly) přidejte Pages/_Host.cshtml následující skript:

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

Pages/CallJsExample7.razor (nadřazená komponenta):

@page "/call-js-example-7"

<h1>Call JS Example 7</h1>

<h2 @ref="title">Hello, world!</h2>

Welcome to your new app.

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

Pages/CallJsExample7.razor.cs:

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

namespace BlazorSample.Pages
{
    public partial class CallJsExample7 : 
        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, 
                CallJsExample7 self)
            {
                Observer = observer;
                Self = self;
            }

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

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

V předchozím příkladu je BlazorSample obor názvů aplikace s komponentami ve Pages složce. Pokud kód testujete místně, aktualizujte obor názvů.

Shared/SurveyPrompt.razor (podřízená komponenta):

@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:

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();

            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();
        }
    }
}

V předchozím příkladu je BlazorSample obor názvů aplikace se sdílenými komponentami ve Shared složce. Pokud kód testujete místně, aktualizujte obor názvů.

Posílení volání zprostředkovatele komunikace v JavaScriptu

Tato část se primárně vztahuje na Blazor Server aplikace, ale Blazor WebAssembly aplikace můžou také nastavit JS časové limity pro interoperabilitu, pokud to podmínky vyžadují.

V Blazor Server aplikacích může spolupráce JavaScriptu (JS) selhat kvůli chybám sítě a měla by být považována za nespolehlivé. Ve výchozím nastavení Blazor Server aplikace používají pro volání zprostředkovatele komunikace časový limit JS o jednu minutu. Pokud aplikace může tolerovat agresivnější časový limit, nastavte časový limit pomocí jednoho z následujících přístupů.

Nastavte globální časový limit v Startup.ConfigureServices metoděStartup.cs:CircuitOptions.JSInteropDefaultCallTimeout

services.AddServerSideBlazor(
    options => options.JSInteropDefaultCallTimeout = {TIMEOUT});

Zástupný {TIMEOUT} symbol je TimeSpan (například TimeSpan.FromSeconds(80)).

Nastavte časový limit volání v kódu komponenty. Zadaný časový limit přepíše globální časový limit nastavený pomocí JSInteropDefaultCallTimeout:

var result = await JS.InvokeAsync<string>("{ID}", {TIMEOUT}, new[] { "Arg1" });

V předchozím příkladu:

  • Zástupný {TIMEOUT} symbol je TimeSpan (například TimeSpan.FromSeconds(80)).
  • Zástupný {ID} symbol je identifikátor funkce, který má vyvolat. Například hodnota someScope.someFunction vyvolá funkci window.someScope.someFunction.

I když běžnou příčinou JS selhání vzájemné spolupráce jsou selhání sítě v Blazor Server aplikacích, časové limity volání pro jednotlivé volání je možné nastavit pro JS volání zprostředkovatele v Blazor WebAssembly aplikacích. I když v Blazor WebAssembly aplikaci neexistuje žádný SignalR okruh, JS volání interoperability můžou selhat z jiných důvodů, které platí v Blazor WebAssembly aplikacích.

Další informace o vyčerpání prostředků najdete v doprovodných materiálech ke zmírnění hrozeb pro ASP.NET Core Blazor Server.

Vyhněte se cyklických odkazům na objekty.

Objekty, které obsahují cyklické odkazy, nelze serializovat v klientovi pro jednu z těchto objektů:

  • Volání metody .NET
  • Volání javascriptové metody z jazyka C#, pokud má návratový typ cyklický odkaz.

Omezení velikosti u volání interoperability JavaScriptu

Tato část platí jenom pro Blazor Server aplikace. V Blazor WebAssemblyarchitektuře se neomezuje velikost vstupů a výstupů interoperability JavaScriptuJS.

Volání Blazor Serverzprostředkovatele komunikace jsou omezená velikostí maximální velikosti příchozích SignalR zpráv povolených pro metody centra, která se vynucuje HubOptions.MaximumReceiveMessageSize (výchozí hodnota: 32 kBJS). JS zprávy .NET SignalR větší než MaximumReceiveMessageSize vyvolá chybu. Architektura neukládá omezení velikosti SignalR zprávy z centra na klienta.

Pokud SignalR protokolování není nastavené na Ladění nebo Trasování, zobrazí se chyba velikosti zprávy pouze v konzole vývojářských nástrojů prohlížeče:

Chyba: Připojení bylo odpojeno s chybou Chyba: Server vrátil chybu při zavření: Připojení se ukončilo s chybou.

Pokud SignalR je protokolování na straně serveru nastaveno na Ladění nebo Trasování, zobrazí se protokolování InvalidDataException na straně serveru kvůli chybě velikosti zprávy.

appsettings.Development.json:

{
  "DetailedErrors": true,
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information",
      "Microsoft.AspNetCore.SignalR": "Debug"
    }
  }
}

Chyba:

System.IO.InvalidDataException: Byla překročena maximální velikost zprávy 32768B. Velikost zprávy se dá nakonfigurovat v AddHubOptions.

Zvýšení limitu nastavením MaximumReceiveMessageSize v :Startup.ConfigureServices

services.AddServerSideBlazor()
    .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);

Zvýšení limitu SignalR velikosti příchozích zpráv má náklady na vyžadování většího počtu prostředků serveru a zpřístupňuje server zvýšeným rizikům od škodlivého uživatele. Kromě toho může čtení velkého množství obsahu do paměti jako řetězce nebo bajtová pole vést také k přidělení, která pracují špatně s uvolňováním paměti, což vede k dalším sankcím za výkon.

Jednou z možností čtení velkých datových částí je odeslat obsah v menších blocích a zpracovat datovou část jako Stream. Můžete ho použít při čtení velkých JSdatových částí ON nebo při dostupnosti dat v JS nezpracovaných bajtech. Příklad, který ukazuje odesílání velkých binárních datových částí, které Blazor Server používají techniky podobné komponentěInputFile, najdete v ukázkové aplikaci Binary Submit.

Poznámka

Odkazy na dokumentaci k referenčnímu zdroji .NET obvykle načítají výchozí větev úložiště, která představuje aktuální vývoj pro příští verzi .NET. Pokud chcete vybrat značku pro konkrétní verzi, použijte rozevírací seznam pro přepnutí větví nebo značek. Další informace najdete v tématu Jak vybrat značku verze zdrojového kódu ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Při vývoji kódu, který přenáší velké množství dat mezi JS aplikacemi a Blazor v Blazor Server aplikacích, zvažte následující doprovodné materiály:

  • Rozdělte data na menší části a posílejte datové segmenty postupně, dokud server neobdrží všechna data.
  • Nepřidělujte velké objekty v JS kódu jazyka C#.
  • Neblokujte hlavní vlákno uživatelského rozhraní po dlouhou dobu při odesílání nebo přijímání dat.
  • Volné využité paměti při dokončení nebo zrušení procesu
  • Pro účely zabezpečení vynucujte následující další požadavky:
    • Deklarujte maximální velikost souboru nebo dat, které lze předat.
    • Deklarujte minimální rychlost nahrávání z klienta na server.
  • Po přijetí dat serverem můžou být tato data:
    • Dočasně uložené v vyrovnávací paměti, dokud se neshromáždí všechny segmenty.
    • Spotřebováno okamžitě. Data se například dají okamžitě uložit do databáze nebo zapisovat na disk při přijetí jednotlivých segmentů.

Zachycení výjimek JavaScriptu

Pokud chcete zachytit JS výjimky, zabalte interoperabilitu JStry-catch do bloku a zachyťte .JSException

V následujícím příkladu nonFunctionJS funkce neexistuje. Pokud se funkce nenajde, JSException je zachycená s chybou Message , která označuje následující chybu:

Could not find 'nonFunction' ('nonFunction' was undefined).

Pages/CallJsExample11.razor:

@page "/call-js-example-11"
@inject IJSRuntime JS

<h1>Call JS Example 11</h1>

<p>
    <button @onclick="CatchUndefinedJSFunction">Catch Exception</button>
</p>

<p>
    @result
</p>

<p>
    @errorMessage
</p>

@code {
    private string errorMessage;
    private string result;

    private async Task CatchUndefinedJSFunction()
    {
        try
        {
            result = await JS.InvokeAsync<string>("nonFunction");
        }
        catch (JSException e)
        {
            errorMessage = $"Error Message: {e.Message}";
        }
    }
}

Další materiály

Informace o volání metod .NET z JSnaleznete v tématu Volání metod .NET z funkcí Jazyka JavaScript v ASP.NET Core Blazor.

IJSRuntime je registrován v Blazor rámci. Pokud chcete volat JS z .NET, vložte IJSRuntime abstrakci a zavolejte jednu z následujících metod:

Pro předchozí metody .NET, které volají JS funkce:

  • Identifikátor funkce (String) je relativní k globálnímu oboru (window). Zavolat window.someScope.someFunction, identifikátor je someScope.someFunction. Před zavolání není nutné funkci registrovat.
  • Předejte funkci libovolný počet argumentů Object[]JS, které JSlze serializovat.
  • Token zrušení (CancellationToken) šíří oznámení, že operace by měly být zrušeny.
  • TimeSpan představuje časový limit operace JS .
  • Návratový TValue typ musí být JStaké zapnutý serializovatelný. TValue by se měl shodovat s typem .NET, který se nejlépe mapuje na vrácený JStyp ON.
  • Funkce A JS Promise je vrácena pro InvokeAsync metody. InvokeAsync rozbalí Promise a vrátí hodnotu očekávanou hodnotou Promise.

U Blazor Server aplikací s povoleným předkreslováním není volání JS během počátečního předkreslování možné. JS Volání mezi zprostředkovateli musí být odložena až po navázání připojení k prohlížeči. Další informace najdete v části Zjistit, kdy Blazor Server je aplikace předdeenderingová .

Následující příklad je založen na dekodéru založeném JSna TextDecoderjazyce . Příklad ukazuje, jak vyvolat JS funkci z metody jazyka C#, která snižuje požadavek z vývojářského kódu na existující JS rozhraní API. Funkce JS přijímá bajtové pole z metody jazyka C#, dekóduje pole a vrátí text do komponenty pro zobrazení.

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

Poznámka

Obecné pokyny k JS umístění a našim doporučením pro produkční aplikace najdete v tématu ASP.NET Core Blazor interoperabilita JavaScriptu (JSinterop).

Následující CallJsExample1 komponenta:

  • convertArrayJS Vyvolá funkci při InvokeAsync výběru tlačítka (Convert Array).
  • JS Po zavolání funkce se předané pole převede na řetězec. Řetězec se vrátí do komponenty pro zobrazení (text).

Pages/CallJsExample1.razor:

@page "/call-js-example-1"
@inject IJSRuntime JS

<h1>Call JS <code>convertArray</code> Function</h1>

<p>
    <button @onclick="ConvertArray">Convert Array</button>
</p>

<p>
    @text
</p>

<p>
    Quote ©2005 <a href="https://www.uphe.com">Universal Pictures</a>: 
    <a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
    <a href="https://www.imdb.com/name/nm0472710/">David Krumholtz on IMDB</a>
</p>

@code {
    private MarkupString text;

    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()
    {
        text = new(await JS.InvokeAsync<string>("convertArray", quoteArray));
    }
}

Rozhraní JAVAScript API omezené na gesta uživatelů

Tato část platí jenom pro Blazor Server aplikace.

Některá rozhraní JAVAScript (JS) api prohlížeče se dají spustit jenom v kontextu uživatelského gesta, například pomocí Fullscreen API (dokumentace k MDN). Tato rozhraní API nelze volat prostřednictvím JS mechanismu vzájemné komunikace v Blazor Server aplikacích, protože zpracování událostí uživatelského rozhraní se provádí asynchronně a obecně už v kontextu gesta uživatele. Aplikace musí zpracovat událost uživatelského rozhraní zcela v JavaScriptu, takže místo atributu @onclick direktivy použijte onclick (dokumentaciBlazorMDN).

Vyvolání funkcí JavaScriptu bez čtení vrácené hodnoty (InvokeVoidAsync)

Použít InvokeVoidAsync , když:

displayTickerAlert1JS Zadejte funkci. Funkce se volá a InvokeVoidAsync nevrací hodnotu:

<script>
  window.displayTickerAlert1 = (symbol, price) => {
    alert(`${symbol}: $${price}!`);
  };
</script>

Poznámka

Obecné pokyny k JS umístění a doporučení pro produkční aplikace najdete v tématu ASP.NET Core Blazor interoperabilita JavaScriptu (JSinterop)..

Příklad komponenty () (.razorInvokeVoidAsync)

TickerChanged volá metodu handleTickerChanged1 v následující CallJsExample2 komponentě.

Pages/CallJsExample2.razor:

@page "/call-js-example-2"
@inject IJSRuntime JS

<h1>Call JS Example 2</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
    private Random r = new();
    private string? stockSymbol;
    private decimal price;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
    }
}

Příklad třídy (.cs) (InvokeVoidAsync)

JsInteropClasses1.cs:

using Microsoft.JSInterop;

public class JsInteropClasses1
{
    private readonly IJSRuntime js;

    public JsInteropClasses1(IJSRuntime js)
    {
        this.js = js;
    }

    public async ValueTask TickerChanged(string symbol, decimal price)
    {
        await js.InvokeVoidAsync("displayTickerAlert1", symbol, price);
    }

    public void Dispose()
    {
    }
}

TickerChanged volá metodu handleTickerChanged1 v následující CallJsExample3 komponentě.

Pages/CallJsExample3.razor:

@page "/call-js-example-3"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call JS Example 3</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
    private Random r = new();
    private string? stockSymbol;
    private decimal price;
    private JsInteropClasses1? jsClass;

    protected override void OnInitialized()
    {
        jsClass = new(JS);
    }

    private async Task SetStock()
    {
        if (jsClass is not null)
        {
            stockSymbol = 
                $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
            price = r.Next(1, 101);
            await jsClass.TickerChanged(stockSymbol, price);
        }
    }

    public void Dispose() => jsClass?.Dispose();
}

Vyvolání funkcí JavaScriptu a načtení vrácené hodnoty (InvokeAsync)

Použijte InvokeAsync , když by .NET měl přečíst výsledek volání JavaScriptu (JS).

displayTickerAlert2JS Zadejte funkci. Následující příklad vrátí řetězec pro zobrazení volajícím:

<script>
  window.displayTickerAlert2 = (symbol, price) => {
    if (price < 20) {
      alert(`${symbol}: $${price}!`);
      return "User alerted in the browser.";
    } else {
      return "User NOT alerted.";
    }
  };
</script>

Poznámka

Obecné pokyny k JS umístění a doporučení pro produkční aplikace najdete v tématu ASP.NET Core Blazor interoperabilita JavaScriptu (JSinterop)..

Příklad komponenty () (.razorInvokeAsync)

TickerChanged volá metodu handleTickerChanged2 a zobrazí vrácený řetězec v následující CallJsExample4 komponentě.

Pages/CallJsExample4.razor:

@page "/call-js-example-4"
@inject IJSRuntime JS

<h1>Call JS Example 4</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result is not null)
{
    <p>@result</p>
}

@code {
    private Random r = new();
    private string? stockSymbol;
    private decimal price;
    private string? result;

    private async Task SetStock()
    {
        stockSymbol = 
            $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
        price = r.Next(1, 101);
        var interopResult = 
            await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol, price);
        result = $"Result of TickerChanged call for {stockSymbol} at " +
            $"{price.ToString("c")}: {interopResult}";
    }
}

Příklad třídy (.cs) (InvokeAsync)

JsInteropClasses2.cs:

using Microsoft.JSInterop;

public class JsInteropClasses2
{
    private readonly IJSRuntime js;

    public JsInteropClasses2(IJSRuntime js)
    {
        this.js = js;
    }

    public async ValueTask<string> TickerChanged(string symbol, decimal price)
    {
        return await js.InvokeAsync<string>("displayTickerAlert2", symbol, price);
    }

    public void Dispose()
    {
    }
}

TickerChanged volá metodu handleTickerChanged2 a zobrazí vrácený řetězec v následující CallJsExample5 komponentě.

Pages/CallJsExample5.razor:

@page "/call-js-example-5"
@implements IDisposable
@inject IJSRuntime JS

<h1>Call JS Example 5</h1>

<p>
    <button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)
{
    <p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result is not null)
{
    <p>@result</p>
}

@code {
    private Random r = new();
    private string? stockSymbol;
    private decimal price;
    private JsInteropClasses2? jsClass;
    private string? result;

    protected override void OnInitialized()
    {
        jsClass = new(JS);
    }

    private async Task SetStock()
    {
        if (jsClass is not null)
        {
            stockSymbol = 
                $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
            price = r.Next(1, 101);
            var interopResult = await jsClass.TickerChanged(stockSymbol, price);
            result = $"Result of TickerChanged call for {stockSymbol} at " +
                $"{price.ToString("c")}: {interopResult}";
        }
    }

    public void Dispose() => jsClass?.Dispose();
}

Scénáře generování dynamického obsahu

Pro dynamické generování obsahu pomocí BuildRenderTree použijte [Inject] atribut:

[Inject]
IJSRuntime JS { get; set; }

Zjištění, kdy Blazor Server je aplikace prerendering

Tato část se vztahuje na Blazor Server a hostované Blazor WebAssembly aplikace, které prerender komponenty Razor . Prerendering je pokryt prerender a integrace ASP.NET Core Razor komponent.

I když je aplikace prerendering, určité akce, jako je volání do JavaScriptu, nejsou možné. Komponenty se můžou při předkreslování vykreslit jinak.

V následujícím příkladu setElementText1 je funkce umístěna uvnitř elementu <head> . Funkce se volá a JSRuntimeExtensions.InvokeVoidAsync nevrací hodnotu.

Poznámka

Obecné pokyny k JS umístění a doporučení pro produkční aplikace najdete v tématu ASP.NET Core Blazor interoperabilita JavaScriptu (JSinterop)..

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

Upozornění

Předchozí příklad upraví objektový model dokumentu (DOM) přímo pro demonstrační účely. Ve většině scénářů se nedoporučuje přímo upravovat DOM pomocí JavaScriptu, protože JavaScript může ovlivnit Blazorsledování změn. Další informace najdete v tématu ASP.NET Core Blazor interoperabilita JavaScriptu (JSinterop).

Pokud chcete zpozdit volání interop JavaScriptu, dokud není zaručeno, že taková volání budou fungovat, přepište OnAfterRender{Async} událost životního cyklu. Tato událost se volá pouze po úplném vykreslení aplikace.

Pages/PrerenderedInterop1.razor:

@page "/prerendered-interop-1"
@using Microsoft.JSInterop
@inject IJSRuntime JS

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

@code {
    private ElementReference divElement;

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

Poznámka

Předchozí příklad znečišťuje klienta globálními metodami. Lepší přístup v produkčních aplikacích najdete v izolaci JavaScriptu v modulech JavaScriptu.

Příklad:

export setElementText1 = (element, text) => element.innerText = text;

Následující komponenta ukazuje, jak používat interop JavaScriptu jako součást logiky inicializace komponenty způsobem, který je kompatibilní s předdeenderingem. Komponenta ukazuje, že je možné aktivovat aktualizaci vykreslování z vnitřní OnAfterRenderAsyncčásti . Vývojář musí být opatrní, abyste se vyhnuli vytváření nekonečné smyčky v tomto scénáři.

V následujícím příkladu setElementText2 je funkce umístěna uvnitř elementu <head> . Funkce je volána s IJSRuntime.InvokeAsync a vrací hodnotu.

Poznámka

Obecné pokyny k JS umístění a doporučení pro produkční aplikace najdete v tématu ASP.NET Core Blazor interoperabilita JavaScriptu (JSinterop)..

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

Upozornění

Předchozí příklad upraví objektový model dokumentu (DOM) přímo pro demonstrační účely. Ve většině scénářů se nedoporučuje přímo upravovat DOM pomocí JavaScriptu, protože JavaScript může ovlivnit Blazorsledování změn. Další informace najdete v tématu ASP.NET Core Blazor interoperabilita JavaScriptu (JSinterop).

Kde JSRuntime.InvokeAsync je volána, se používá pouze v OnAfterRenderAsync a ne v žádné dřívější metodě životního cyklu, ElementReference protože neexistuje žádný javascriptový prvek, dokud se komponenta nevykreslí.

StateHasChangedje volána k opětovnému vykreslení komponenty s novým stavem získaným z volání interop JavaScriptu (další informace najdete v tématu ASP.NET Core Razor vykreslování komponent). Kód nevytvoří nekonečné smyčky, protože StateHasChanged se volá pouze tehdy, když data je null.

Pages/PrerenderedInterop2.razor:

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

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

<p>
    Set value via JS interop call:
</p>

<div id="val-set-by-interop" @ref="divElement"></div>

@code {
    private string? data;
    private ElementReference divElement;

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

            StateHasChanged();
        }
    }
}

Poznámka

Předchozí příklad znečišťuje klienta globálními metodami. Lepší přístup v produkčních aplikacích najdete v izolaci JavaScriptu v modulech JavaScriptu.

Příklad:

export setElementText2 = (element, text) => {
  element.innerText = text;
  return text;
};

Synchronní JS spolupráce v Blazor WebAssembly aplikacích

Tato část platí jenom pro Blazor WebAssembly aplikace.

Volání zprostředkovatele komunikace JS jsou ve výchozím nastavení asynchronní, bez ohledu na to, jestli volaný kód je synchronní nebo asynchronní. Volání jsou ve výchozím nastavení asynchronní, aby se zajistilo, že komponenty jsou kompatibilní jak mezi modely hostování Blazor, tak Blazor Server a Blazor WebAssembly. Všechny Blazor ServerJS volání interopu musí být asynchronní, protože se odesílají přes síťové připojení.

Pokud víte, že vaše aplikace běží Blazor WebAssemblyjenom někdy, můžete se rozhodnout provádět synchronní JS volání interopu. To má mírně menší režii než provádění asynchronních volání a může vést k menšímu počtu cyklů vykreslování, protože během čekání na výsledky neexistuje žádný přechodný stav.

Pokud chcete v aplikaci provést synchronní volání z .NET do JavaScriptu Blazor WebAssembly , přetypujte IJSRuntime ho, aby IJSInProcessRuntime bylo JS volání zprostředkovatele komunikace:

@inject IJSRuntime JS

...

@code {
    protected override void HandleSomeEvent()
    {
        var jsInProcess = (IJSInProcessRuntime)JS;
        var value = jsInProcess.Invoke<string>("javascriptFunctionIdentifier");
    }
}

Při práci s aplikacemi IJSObjectReference ASP.NET Core 5.0 nebo novějšími Blazor WebAssembly můžete místo toho synchronně použítIJSInProcessObjectReference:

private IJSInProcessObjectReference module;

...

module = await JS.InvokeAsync<IJSInProcessObjectReference>("import", "./scripts.js");

Umístění JavaScriptu

Načtěte kód JavaScriptu (JS) pomocí libovolného přístupu popsaného v článku s přehledem interoperability JavaScriptu (JSinterop):

Informace o izolace skriptů v JS modulech najdete v části javascriptové izolace modulů .

Upozornění

Neumisťujte <script> značku do souboru komponenty (.razor), protože <script> značku nelze dynamicky aktualizovat.

Izolace JavaScriptu v modulech JavaScriptu

Blazor umožňuje izolaci JavaScriptu (JS) ve standardních modulech JavaScriptu (specifikace ECMAScript).

Izolace JS poskytuje následující výhody:

  • Import JS už znečišťuje globální obor názvů.
  • Uživatelé knihovny a komponent už nemusí importovat související JS.

Například následující JS modul exportuje JS funkci pro zobrazení výzvy okna prohlížeče. Do externího JS souboru umístěte následující JS kód.

wwwroot/scripts.js:

export function showPrompt(message) {
  return prompt(message, 'Type anything here');
}

Přidejte předchozí JS modul do aplikace nebo knihovny tříd jako statický webový prostředek ve wwwroot složce a pak modul naimportujte do kódu .NET voláním InvokeAsyncIJSRuntime instance.

IJSRuntime importuje modul jako IJSObjectReferenceobjekt, který představuje odkaz na JS objekt z kódu .NET. Použijte k IJSObjectReference vyvolání exportovaných JS funkcí z modulu.

Pages/CallJsExample6.razor:

@page "/call-js-example-6"
@implements IAsyncDisposable
@inject IJSRuntime JS

<h1>Call JS Example 6</h1>

<p>
    <button @onclick="TriggerPrompt">Trigger browser window prompt</button>
</p>

<p>
    @result
</p>

@code {
    private IJSObjectReference? module;
    private string? result;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            module = await JS.InvokeAsync<IJSObjectReference>("import", 
                "./scripts.js");
        }
    }

    private async Task TriggerPrompt()
    {
        result = await Prompt("Provide some text");
    }

    public async ValueTask<string?> Prompt(string message) =>
        module is not null ? 
            await module.InvokeAsync<string>("showPrompt", message) : null;

    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        if (module is not null)
        {
            await module.DisposeAsync();
        }
    }
}

V předchozím příkladu:

  • Podle konvence import je identifikátor speciálním identifikátorem používaným speciálně pro import JS modulu.
  • Zadejte externí JS soubor modulu pomocí stabilní cesty statického webového prostředku: ./{SCRIPT PATH AND FILENAME (.js)}, kde:
    • K vytvoření správné cesty statického prostředku k souboru JS se vyžaduje segment cesty pro aktuální adresář (./).
    • Zástupný symbol {SCRIPT PATH AND FILENAME (.js)} je cesta a název souboru v části wwwroot.
  • IJSObjectReference Odstraní pro uvolňování paměti v IAsyncDisposable.DisposeAsync.

Dynamické importování modulu vyžaduje požadavek na síť, takže jej lze dosáhnout pouze asynchronním voláním InvokeAsync.

IJSInProcessObjectReference představuje odkaz na JS objekt, jehož funkce lze v aplikacích volat synchronně Blazor WebAssembly . Další informace najdete v části Synchronní JS spolupráce v Blazor WebAssembly aplikacích .

Poznámka

JS Pokud externí soubor poskytuje Razor knihovna tříd, zadejte soubor modulu JS pomocí stabilní cesty statického webového prostředku: ./_content/{PACKAGE ID}/{SCRIPT PATH AND FILENAME (.js)}

  • K vytvoření správné cesty statického prostředku k souboru JS se vyžaduje segment cesty pro aktuální adresář (./).
  • Zástupný symbol {PACKAGE ID} je ID balíčku knihovny. Pokud v souboru projektu není zadaný parametr <PackageId>, jako ID balíčku se ve výchozím nastavení použije název sestavení projektu. V následujícím příkladu je ComponentLibrary název sestavení knihovny a soubor projektu knihovny nezadává <PackageId>.
  • Zástupný symbol {SCRIPT PATH AND FILENAME (.js)} je cesta a název souboru v části wwwroot. V následujícím příkladu se externí JS soubor (script.js) umístí do složky knihovny tříd wwwroot .
var module = await js.InvokeAsync<IJSObjectReference>(
    "import", "./_content/ComponentLibrary/scripts.js");

Další informace najdete v tématu Využívání komponent ASP.NET Core Razor z knihovny tříd Razor (RCL).

Zachycení odkazů na prvky

Některé scénáře vzájemné spolupráce JavaScriptuJS vyžadují odkazy na elementy HTML. Knihovna uživatelského rozhraní může například vyžadovat odkaz na element pro inicializaci nebo možná budete muset volat rozhraní API podobná příkazům prvku, například click nebo play.

Zachytávání odkazů na elementy HTML v komponentě pomocí následujícího přístupu:

  • @ref Přidejte atribut do elementu HTML.
  • Definujte pole typu ElementReference , jehož název odpovídá hodnotě atributu @ref .

Následující příklad ukazuje zachycení odkazu na username<input> prvek:

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

@code {
    private ElementReference username;
}

Upozornění

Pomocí odkazu na prvek můžete ztlumit obsah prázdného prvku, který nepracuje s Blazor. Tento scénář je užitečný, když rozhraní API třetí strany poskytuje obsah prvku. Vzhledem k tomu Blazor , že s prvkem nepracuje, neexistuje možnost konfliktu mezi Blazorreprezentací prvku a modelem DOM (Document Object Model).

V následujícím příkladu je nebezpečné ztlumit obsah neuspořádaného seznamu (ul), protože Blazor komunikuje s objektem DOM a naplní položky seznamu tohoto prvku (<li>) z objektu Todos :

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

Pokud JS ztlumí obsah elementu MyList a Blazor pokusí se použít rozdíly u elementu, rozdíly nebudou odpovídat dom.

Další informace najdete v tématu ASP.NET Core Blazor interoperabilita JavaScriptu (JSinterop).

Kód ElementReference se předá JS prostřednictvím JS zprostředkovatele komunikace. Kód JS obdrží HTMLElement instanci, kterou může používat s normálními rozhraními API DOM. Například následující kód definuje metodu rozšíření .NET (TriggerClickEvent), která umožňuje odesílání kliknutí myší na prvek.

Funkce JSclickElement vytvoří click událost u předaného elementu HTML (element):

window.interopFunctions = {
  clickElement : function (element) {
    element.click();
  }
}

Pokud chcete volat JS funkci, která nevrací hodnotu, použijte JSRuntimeExtensions.InvokeVoidAsync. Následující kód aktivuje událost na straně click klienta voláním předchozí JS funkce s zachyceným ElementReferencekódem:

@inject IJSRuntime JS

<button @ref="exampleButton">Example Button</button>

<button @onclick="TriggerClick">
    Trigger click event on <code>Example Button</code>
</button>

@code {
    private ElementReference exampleButton;

    public async Task TriggerClick()
    {
        await JS.InvokeVoidAsync(
            "interopFunctions.clickElement", exampleButton);
    }
}

Pokud chcete použít metodu rozšíření, vytvořte statickou metodu IJSRuntime rozšíření, která obdrží instanci:

public static async Task TriggerClickEvent(this ElementReference elementRef, 
    IJSRuntime js)
{
    await js.InvokeVoidAsync("interopFunctions.clickElement", elementRef);
}

Metoda clickElement se volá přímo na objektu. Následující příklad předpokládá, že TriggerClickEvent metoda je k dispozici z JsInteropClasses oboru názvů:

@inject IJSRuntime JS
@using JsInteropClasses

<button @ref="exampleButton">Example Button</button>

<button @onclick="TriggerClick">
    Trigger click event on <code>Example Button</code>
</button>

@code {
    private ElementReference exampleButton;

    public async Task TriggerClick()
    {
        await exampleButton.TriggerClickEvent(JS);
    }
}

Důležité

Proměnná exampleButton se naplní pouze po vykreslení komponenty. Pokud se nepřečtený ElementReference kód předáJS, JS kód obdrží hodnotu .null Pokud chcete manipulovat s odkazy na prvky po dokončení vykreslování komponenty, použijte OnAfterRenderAsync metody životního cyklu komponent nebo OnAfterRender součásti.

Při práci s obecnými typy a vrácení hodnoty použijte ValueTask<TResult>:

public static ValueTask<T> GenericMethod<T>(this ElementReference elementRef, 
    IJSRuntime js)
{
    return js.InvokeAsync<T>("{JAVASCRIPT FUNCTION}", elementRef);
}

Zástupný {JAVASCRIPT FUNCTION} symbol je JS identifikátor funkce.

GenericMethod je volána přímo na objektu s typem. Následující příklad předpokládá, že GenericMethod je k dispozici z JsInteropClasses oboru názvů:

@inject IJSRuntime JS
@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>(JS);
    }
}

Referenční prvky napříč komponentami

Nelze ElementReference předat mezi komponentami, protože:

Aby nadřazená komponenta zpřístupnit element odkaz na jiné komponenty, může nadřazená komponenta:

  • Povolte podřízeným komponentám registraci zpětných volání.
  • Vyvolá registrované zpětné volání během OnAfterRender události s předaným odkazem elementu. Tento přístup nepřímo umožňuje podřízeným komponentám pracovat s odkazem na element nadřazeného objektu.
<style>
    .red { color: red }
</style>
<script>
  function setElementClass(element, className) {
    var myElement = element;
    myElement.classList.add(className);
  }
</script>

Poznámka

Obecné pokyny k JS umístění a doporučení pro produkční aplikace najdete v tématu ASP.NET Core Blazor interoperabilita JavaScriptu (JSinterop)..

Pages/CallJsExample7.razor (nadřazená komponenta):

@page "/call-js-example-7"

<h1>Call JS Example 7</h1>

<h2 @ref="title">Hello, world!</h2>

Welcome to your new app.

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

Pages/CallJsExample7.razor.cs:

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

namespace BlazorSample.Pages
{
    public partial class CallJsExample7 : 
        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, 
                CallJsExample7 self)
            {
                Observer = observer;
                Self = self;
            }

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

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

V předchozím příkladu je BlazorSample obor názvů aplikace s komponentami Pages ve složce. Pokud testujete kód místně, aktualizujte obor názvů.

Shared/SurveyPrompt.razor (podřízená komponenta):

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

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

@code {
    // Demonstrates how a parent component can supply parameters
    [Parameter]
    public string? Title { get; set; }
}

V předchozím příkladu je BlazorSample obor názvů aplikace se sdílenými komponentami ve Shared složce. Pokud testujete kód místně, aktualizujte obor názvů.

Posílení volání interopu v JavaScriptu

Tato část platí především pro Blazor Server aplikace, ale Blazor WebAssembly aplikace můžou také nastavit JS časové limity spolupráce, pokud to podmínky zaručují.

V Blazor Server aplikacích může interop JavaScriptu (JS) selhat kvůli síťovým chybám a měl by být považován za nespolehlivé. Ve výchozím nastavení Blazor Server aplikace používají pro volání interopu časový limit JS o jednu minutu. Pokud aplikace může tolerovat agresivnější časový limit, nastavte časový limit pomocí jednoho z následujících přístupů.

Nastavení globálního časového limitu v :Program.csCircuitOptions.JSInteropDefaultCallTimeout

builder.Services.AddServerSideBlazor(
    options => options.JSInteropDefaultCallTimeout = {TIMEOUT});

Zástupný {TIMEOUT} symbol je TimeSpan (například TimeSpan.FromSeconds(80)).

Nastavte časový limit pro vyvolání v kódu komponenty. Zadaný časový limit přepíše globální časový limit nastavený:JSInteropDefaultCallTimeout

var result = await JS.InvokeAsync<string>("{ID}", {TIMEOUT}, new[] { "Arg1" });

V předchozím příkladu:

  • Zástupný {TIMEOUT} symbol je TimeSpan (například TimeSpan.FromSeconds(80)).
  • Zástupný {ID} symbol je identifikátor funkce, který má vyvolat. Například hodnota someScope.someFunction vyvolá funkci window.someScope.someFunction.

I když nejčastější příčinou JS selhání vzájemné komunikace jsou chyby sítě v Blazor Server aplikacích, časové limity volání pro jednotlivé volání je možné nastavit pro JS volání interopu v Blazor WebAssembly aplikacích. I když v Blazor WebAssembly aplikaci neexistuje žádný SignalR okruh, JS volání interopu můžou selhat z jiných důvodů, které platí v Blazor WebAssembly aplikacích.

Další informace o vyčerpání prostředků najdete v tématu Pokyny ke zmírnění rizik hrozeb pro ASP.NET Core Blazor Server.

Vyhněte se cyklovým odkazům na objekty.

Objekty, které obsahují cyklický odkaz, nelze serializovat v klientovi pro následující objekty:

  • Volání metody .NET
  • Volání metod JavaScriptu z jazyka C#, pokud má návratový typ cyklický odkaz.

Knihovny JavaScriptu, které vykreslují uživatelské rozhraní

Někdy můžete chtít použít knihovny JavaScriptu (JS), které vytvářejí viditelné prvky uživatelského rozhraní v rámci modelu DOM (Document Object Model). Na první pohled to může vypadat jako obtížné, protože Blazorsystém diffing spoléhá na kontrolu nad stromem prvků DOM a narazí na chyby, pokud některý externí kód ztlumí strom DOM a zneplatní jeho mechanismus pro použití rozdílů. Nejedná se o Blazorkonkrétní omezení. K stejné výzvě dochází u libovolného rozhraní uživatelského rozhraní založeného na rozdílech.

Naštěstí je snadné spolehlivě vkládat externě generované uživatelské rozhraní do Razor uživatelského rozhraní komponenty. Doporučená technika spočívá v tom, že kód komponenty (.razor soubor) vytvoří prázdný prvek. Pokud Blazorjde o rozdílový systém, je prvek vždy prázdný, takže vykreslovací modul se do prvku nepřipojí a místo toho ponechá jeho obsah sám. Díky tomu je bezpečné naplnit prvek libovolným externě spravovaným obsahem.

Následující příklad ukazuje koncept. V rámci if příkazu, kdy firstRender je , trueinteragujte mimo unmanagedElementBlazor použití JS interopu. Můžete například volat externí JS knihovnu, která naplní prvek. Blazor ponechá obsah prvku sám, dokud se tato komponenta neodebere. Když je komponenta odebrána, odebere se také celý podstrom DOM.

<h1>Hello! This is a Razor component rendered at @DateTime.Now</h1>

<div @ref="unmanagedElement"></div>

@code {
    private ElementReference unmanagedElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            ...
        }
    }
}

Představte si následující příklad, který vykreslí interaktivní mapu pomocí opensourcových rozhraní API Mapboxu.

Následující JS modul se umístí do aplikace nebo je zpřístupněný z Razor knihovny tříd.

Poznámka

Pokud chcete vytvořit mapu Mapboxu , získejte přístupový token z Mapbox Sign in a zadejte ho {ACCESS TOKEN} tam, kde se zobrazí v následujícím kódu.

wwwroot/mapComponent.js:

import 'https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.js';

mapboxgl.accessToken = '{ACCESS TOKEN}';

export function addMapToElement(element) {
  return new mapboxgl.Map({
    container: element,
    style: 'mapbox://styles/mapbox/streets-v11',
    center: [-74.5, 40],
    zoom: 9
  });
}

export function setMapCenter(map, latitude, longitude) {
  map.setCenter([longitude, latitude]);
}

Chcete-li vytvořit správný styl, přidejte na stránku HTML hostitele následující značku šablony stylů.

Přidejte do značky elementu <head> následující <link> prvek (umístění <head> obsahu):

<link href="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css" 
    rel="stylesheet" />

Pages/CallJsExample8.razor:

@page "/call-js-example-8"
@implements IAsyncDisposable
@inject IJSRuntime JS

<h1>Call JS Example 8</h1>

<div @ref="mapElement" style='width:400px;height:300px'></div>

<button @onclick="() => ShowAsync(51.454514, -2.587910)">Show Bristol, UK</button>
<button @onclick="() => ShowAsync(35.6762, 139.6503)">Show Tokyo, Japan</button>

@code
{
    private ElementReference mapElement;
    private IJSObjectReference? mapModule;
    private IJSObjectReference? mapInstance;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            mapModule = await JS.InvokeAsync<IJSObjectReference>(
                "import", "./mapComponent.js");
            mapInstance = await mapModule.InvokeAsync<IJSObjectReference>(
                "addMapToElement", mapElement);
        }
    }

    private async Task ShowAsync(double latitude, double longitude)
    {
        if (mapModule is not null && mapInstance is not null)
        {
            await mapModule.InvokeVoidAsync("setMapCenter", mapInstance, 
                latitude, longitude).AsTask();
        }
    }

    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        if (mapInstance is not null)
        {
            await mapInstance.DisposeAsync();
        }

        if (mapModule is not null)
        {
            await mapModule.DisposeAsync();
        }
    }
}

Předchozí příklad vytvoří interaktivní uživatelské rozhraní mapy. Uživatel:

  • Lze přetáhnout a posunout nebo přiblížit.
  • Výběrem tlačítek přejděte na předdefinovaná umístění.

Mapbox street map of Tokyo, Japan with button to select Bristol, United Kingdom and Tokyo, Japan

V předchozím příkladu:

  • Sousto <div>@ref="mapElement" je prázdné, pokud Blazor se to týká. Skript mapbox-gl.js může bezpečně naplnit prvek a upravit jeho obsah. Tuto techniku použijte s libovolnou JS knihovnou, která vykresluje uživatelské rozhraní. Komponenty můžete vkládat z architektury SPA třetí strany JS uvnitř Razor komponent, pokud se nepokouší kontaktovat a upravovat další části stránky. Externí kód není bezpečný pro JS úpravy prvků, které Blazor se nepovažují za prázdné.
  • Při použití tohoto přístupu mějte na paměti pravidla týkající Blazor se zachování nebo zničení prvků DOM. Komponenta bezpečně zpracovává události kliknutí na tlačítko a aktualizuje existující instanci mapy, protože prvky MODELU DOM se uchovávají tam, kde je to možné ve výchozím nastavení. Pokud jste vykreslovali seznam prvků mapy ze @foreach smyčky, chcete použít @key k zajištění zachování instancí komponent. Jinak by změny v datech seznamu mohly způsobit, že instance komponent zachovávají stav předchozích instancí nežádoucím způsobem. Další informace naleznete v tématu using @key to preserve elements and components.
  • Příklad zapouzdřuje JS logiku a závislosti v modulu ES6 a načte modul dynamicky pomocí identifikátoru import . Další informace najdete v tématu Izolace JavaScriptu v modulech JavaScriptu.

Podpora bajtového pole

Blazor podporuje optimalizovanou bajtůovou matici JavaScriptu (JS), která zabraňuje kódování a dekódování bajtů do Base64. Následující příklad používá JS interop k předání bajtového pole do JavaScriptu.

receiveByteArrayJS Zadejte funkci. Funkce se volá a InvokeVoidAsync nevrací hodnotu:

<script>
  window.receiveByteArray = (bytes) => {
    let utf8decoder = new TextDecoder();
    let str = utf8decoder.decode(bytes);
    return str;
  };
</script>

Poznámka

Obecné pokyny k JS umístění a doporučení pro produkční aplikace najdete v tématu ASP.NET Core Blazor interoperabilita JavaScriptu (JSinterop)..

Pages/CallJsExample9.razor:

@page "/call-js-example-9"
@inject IJSRuntime JS

<h1>Call JS Example 9</h1>

<p>
    <button @onclick="SendByteArray">Send Bytes</button>
</p>

<p>
    @result
</p>

<p>
    Quote &copy;2005 <a href="https://www.uphe.com">Universal Pictures</a>:
    <a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
    <a href="https://www.imdb.com/name/nm0821612/">Jewel Staite on IMDB</a>
</p>

@code {
    private string? result;

    private async Task SendByteArray()
    {
        var bytes = new byte[] { 0x45, 0x76, 0x65, 0x72, 0x79, 0x74, 0x68, 0x69,
            0x6e, 0x67, 0x27, 0x73, 0x20, 0x73, 0x68, 0x69, 0x6e, 0x79, 0x2c,
            0x20, 0x43, 0x61, 0x70, 0x74, 0x69, 0x61, 0x6e, 0x2e, 0x20, 0x4e,
            0x6f, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x66, 0x72, 0x65, 0x74, 0x2e };

        result = await JS.InvokeAsync<string>("receiveByteArray", bytes);
    }
}

Informace o použití bajtového pole při volání .NET z JavaScriptu najdete v tématu Volání metod .NET z funkcí JavaScriptu v ASP.NET Core Blazor.

Omezení velikosti pro volání z interoperability JavaScriptu

Tato část platí jenom pro Blazor Server aplikace. Architektura Blazor WebAssemblyneukládá omezení velikosti vstupů a výstupů z interoperability JavaScriptuJS.

Volání Blazor Serverinteropu jsou omezená velikostí maximální velikosti příchozích SignalR zpráv povolených pro metody centra, která se vynucuje HubOptions.MaximumReceiveMessageSize (výchozí hodnota: 32 kBJS). JS pro zprávy .NET SignalR větší, než MaximumReceiveMessageSize vyvolá chybu. Architektura neukládá omezení velikosti SignalR zprávy z centra na klienta.

Pokud SignalR protokolování není nastavené na Ladění nebo Trasování, zobrazí se chyba velikosti zprávy jenom v konzole vývojářských nástrojů prohlížeče:

Chyba: Připojení se odpojilo s chybou Chyba: Server vrátil chybu při zavření: Připojení se zavřelo s chybou.

Pokud SignalR je protokolování na straně serveru nastavené na Ladění nebo Trasování, protokolování na straně serveru zobrazí InvalidDataException chybu velikosti zprávy.

appsettings.Development.json:

{
  "DetailedErrors": true,
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information",
      "Microsoft.AspNetCore.SignalR": "Debug"
    }
  }
}

Chyba:

System.IO.InvalidDataException: Byla překročena maximální velikost zprávy 32768B. Velikost zprávy je možné nakonfigurovat v AddHubOptions.

Zvyšte limit nastavením MaximumReceiveMessageSize v Program.cs. Následující příklad nastaví maximální velikost zprávy příjmu na 64 kB (64 * 1024):

builder.Services.AddServerSideBlazor()
    .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);

Zvýšení limitu SignalR velikosti příchozích zpráv má náklady na vyžadování dalších prostředků serveru a zpřístupňuje server zvýšeným rizikům od škodlivého uživatele. Kromě toho může čtení velkého množství obsahu do paměti jako řetězce nebo bajtová pole způsobit také přidělení, která fungují špatně s uvolňováním paměti, což vede k dalším sankcím za výkon.

Při vývoji kódu, který přenáší velké množství dat mezi JS aplikacemi a Blazor v Blazor Server aplikacích, zvažte následující pokyny:

  • Využijte nativní podporu komunikace streamování k přenosu dat větších než SignalR limit velikosti příchozích zpráv:
  • Obecné tipy:
    • Nepřidělujte velké objekty v kódu jazyka JS C#.
    • Bezplatná spotřebovaná paměť při dokončení nebo zrušení procesu
    • Vynucujte pro účely zabezpečení následující další požadavky:
      • Deklarujte maximální velikost souboru nebo dat, které lze předat.
      • Deklarujte minimální rychlost nahrávání z klienta na server.
    • Po přijetí dat serverem můžou být tato data:
      • Dočasně uložená v vyrovnávací paměti, dokud se neshromáždí všechny segmenty.
      • Spotřebováno okamžitě. Data se například dají uložit okamžitě v databázi nebo zapisovat na disk při přijetí jednotlivých segmentů.

Unmarshalled JavaScript interop

Blazor WebAssembly komponenty můžou mít nízký výkon, pokud jsou objekty .NET serializovány pro interop (JSJavaScript) a některé z následujících hodnot jsou pravdivé:

  • Velký objem objektů .NET se rychle serializuje. Například nízký výkon může vést k tomu, že JS se volání vzájemné komunikace provádí na základě přesouvání vstupního zařízení, například otáčení kolečka myši.
  • Velké objekty .NET nebo mnoho objektů .NET musí být serializováno pro JS interoperabilitu. Například nízký výkon může vést k tomu, že JS volání vzájemné komunikace vyžadují serializaci desítek souborů.

IJSUnmarshalledObjectReference představuje odkaz na JS objekt, jehož funkce lze vyvolat bez režie serializace dat .NET.

V následujícím příkladu:

  • Struktura obsahující řetězec a celé číslo se předá neserializované .JS
  • JS funkce zpracovávají data a vracejí logické hodnoty nebo řetězec volajícímu.
  • Řetězec JS není přímo převedený na objekt .NET string . Funkce unmarshalledFunctionReturnString volá BINDING.js_string_to_mono_string ke správě převodu JS řetězce.

Poznámka

Následující příklady nejsou typické případy použití pro tento scénář, protože struktura předaná JS není výsledkem nízkého výkonu komponent. Příklad používá malý objekt pouze k předvedení konceptů pro předávání neserializovaných dat .NET.

<script>
  window.returnObjectReference = () => {
    return {
      unmarshalledFunctionReturnBoolean: function (fields) {
        const name = Blazor.platform.readStringField(fields, 0);
        const year = Blazor.platform.readInt32Field(fields, 8);

        return name === "Brigadier Alistair Gordon Lethbridge-Stewart" &&
            year === 1968;
      },
      unmarshalledFunctionReturnString: function (fields) {
        const name = Blazor.platform.readStringField(fields, 0);
        const year = Blazor.platform.readInt32Field(fields, 8);

        return BINDING.js_string_to_mono_string(`Hello, ${name} (${year})!`);
      }
    };
  }
</script>

Poznámka

Obecné pokyny k JS umístění a doporučení pro produkční aplikace najdete v tématu ASP.NET Core Blazor interoperabilita JavaScriptu (JSinterop)..

Upozornění

Název js_string_to_mono_string funkce, chování a existence se v budoucí verzi .NET může změnit. Příklad:

  • Funkce se pravděpodobně přejmenuje.
  • Samotná funkce může být odebrána ve prospěch automatického převodu řetězců architekturou.

Pages/CallJsExample10.razor:

@page "/call-js-example-10"
@using System.Runtime.InteropServices
@using Microsoft.JSInterop
@inject IJSRuntime JS

<h1>Call JS Example 10</h1>

@if (callResultForBoolean)
{
    <p>JS interop was successful!</p>
}

@if (!string.IsNullOrEmpty(callResultForString))
{
    <p>@callResultForString</p>
}

<p>
    <button @onclick="CallJSUnmarshalledForBoolean">
        Call Unmarshalled JS & Return Boolean
    </button>
    <button @onclick="CallJSUnmarshalledForString">
        Call Unmarshalled JS & Return String
    </button>
</p>

<p>
    <a href="https://www.doctorwho.tv">Doctor Who</a>
    is a registered trademark of the <a href="https://www.bbc.com/">BBC</a>.
</p>

@code {
    private bool callResultForBoolean;
    private string? callResultForString;

    private void CallJSUnmarshalledForBoolean()
    {
        var unmarshalledRuntime = (IJSUnmarshalledRuntime)JS;

        var jsUnmarshalledReference = unmarshalledRuntime
            .InvokeUnmarshalled<IJSUnmarshalledObjectReference>(
                "returnObjectReference");

        callResultForBoolean = 
            jsUnmarshalledReference.InvokeUnmarshalled<InteropStruct, bool>(
                "unmarshalledFunctionReturnBoolean", GetStruct());
    }

    private void CallJSUnmarshalledForString()
    {
        var unmarshalledRuntime = (IJSUnmarshalledRuntime)JS;

        var jsUnmarshalledReference = unmarshalledRuntime
            .InvokeUnmarshalled<IJSUnmarshalledObjectReference>(
                "returnObjectReference");

        callResultForString = 
            jsUnmarshalledReference.InvokeUnmarshalled<InteropStruct, string>(
                "unmarshalledFunctionReturnString", GetStruct());
    }

    private InteropStruct GetStruct()
    {
        return new InteropStruct
        {
            Name = "Brigadier Alistair Gordon Lethbridge-Stewart",
            Year = 1968,
        };
    }

    [StructLayout(LayoutKind.Explicit)]
    public struct InteropStruct
    {
        [FieldOffset(0)]
        public string Name;

        [FieldOffset(8)]
        public int Year;
    }
}

Pokud instance IJSUnmarshalledObjectReference není v kódu jazyka C# odstraněna, je možné ji vyhodit JS. Následující dispose funkce odstraní odkaz na objekt při zavolání z JS:

window.exampleJSObjectReferenceNotDisposedInCSharp = () => {
  return {
    dispose: function () {
      DotNet.disposeJSObjectReference(this);
    },

    ...
  };
}

Typy polí lze převést z JS objektů na objekty .NET pomocí js_typed_array_to_array, ale JS pole musí být typové pole. Pole z JS kódu jazyka C# lze číst jako pole objektů .NET (object[]).

Jiné datové typy, například pole řetězců, lze převést, ale vyžadují vytvoření nového objektu monomatice (mono_obj_array_new) a nastavení jeho hodnoty (mono_obj_array_set).

Upozornění

JSfunkce poskytované architekturouBlazor, například js_typed_array_to_array, a mono_obj_array_newmono_obj_array_set, podléhají změnám názvů, změnám chování nebo odebráním v budoucích verzích .NET.

Streamování z .NET do JavaScriptu

Blazor podporuje streamování dat přímo z .NET do JavaScriptu. Streamy se vytvářejí pomocí DotNetStreamReference.

DotNetStreamReference představuje stream .NET a používá následující parametry:

  • stream: Stream odesílaný do JavaScriptu.
  • leaveOpen: Určuje, jestli je datový proud po přenosu otevřený. Pokud není zadaná hodnota, leaveOpen výchozí hodnota je false.

V JavaScriptu použijte vyrovnávací paměť pole nebo čitelný datový proud pro příjem dat:

  • Pomocí :ArrayBuffer

    async function streamToJavaScript(streamRef) {
      const data = await streamRef.arrayBuffer();
    }
    
  • ReadableStreamPomocí :

    async function streamToJavaScript(streamRef) {
      const stream = await streamRef.stream();
    }
    

V kódu jazyka C#:

using var streamRef = new DotNetStreamReference(stream: {STREAM}, leaveOpen: false);
await JS.InvokeVoidAsync("streamToJavaScript", streamRef);

V předchozím příkladu:

  • Zástupný {STREAM} symbol představuje odeslanou do JavaScriptu Stream .
  • JS je injektovaná IJSRuntime instance.

Volání metod .NET z funkcí JavaScriptu v ASP.NET Core Blazor pokrývá reverzní operaci streamování z JavaScriptu do .NET.

Blazor ASP.NET Core soubory ke stažení popisuje, jak stáhnout soubor v Blazor.

Zachycení výjimek JavaScriptu

Pokud chcete zachytit JS výjimky, zabalte interoperabilitu JStry-catch do bloku a zachyťte .JSException

V následujícím příkladu nonFunctionJS funkce neexistuje. Pokud se funkce nenajde, JSException je zachycená s chybou Message , která označuje následující chybu:

Could not find 'nonFunction' ('nonFunction' was undefined).

Pages/CallJsExample11.razor:

@page "/call-js-example-11"
@inject IJSRuntime JS

<h1>Call JS Example 11</h1>

<p>
    <button @onclick="CatchUndefinedJSFunction">Catch Exception</button>
</p>

<p>
    @result
</p>

<p>
    @errorMessage
</p>

@code {
    private string? errorMessage;
    private string? result;

    private async Task CatchUndefinedJSFunction()
    {
        try
        {
            result = await JS.InvokeAsync<string>("nonFunction");
        }
        catch (JSException e)
        {
            errorMessage = $"Error Message: {e.Message}";
        }
    }
}

Přerušení dlouhotrvající funkce JavaScriptu

Pomocí AbortControllerJS s komponentou CancellationTokenSource můžete přerušit dlouho běžící funkci JavaScriptu z kódu jazyka C#.

Následující JSHelpers třída obsahuje simulovanou dlouho běžící funkci, která počítá nepřetržitě, longRunningFndokud AbortController.signal není uvedeno, že AbortController.abort byla volána. Funkce sleep je určená pro demonstrační účely k simulaci pomalého spuštění dlouhotrvající funkce, která by nebyla v produkčním kódu. Při volání stopFnkomponenty je longRunningFn signál přerušen prostřednictvím while podmíněné kontroly AbortSignal.abortedsmyčky .

<script>
  class Helpers {
    static #controller = new AbortController();

    static async #sleep(ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }

    static async longRunningFn() {
      var i = 0;
      while (!this.#controller.signal.aborted) {
        i++;
        console.log(`longRunningFn: ${i}`);
        await this.#sleep(1000);
      }
    }

    static stopFn() {
      this.#controller.abort();
      console.log('longRunningFn aborted!');
    }
  }

  window.Helpers = Helpers;
</script>

Poznámka

Obecné pokyny k JS umístění a našim doporučením pro produkční aplikace najdete v tématu ASP.NET Core Blazor interoperabilita JavaScriptu (JSinterop).

Následující CallJsExample12 komponenta:

Pages/CallJsExample12.razor:

@page "/call-js-example-12"
@inject IJSRuntime JS

<h1>Cancel long-running JS interop</h1>

<p>
    <button @onclick="StartTask">Start Task</button>
    <button @onclick="CancelTask">Cancel Task</button>
</p>

@code {
    private CancellationTokenSource? cts;

    private async Task StartTask()
    {
        cts = new CancellationTokenSource();
        cts.Token.Register(() => JS.InvokeVoidAsync("Helpers.stopFn"));

        await JS.InvokeVoidAsync("Helpers.longRunningFn");
    }

    private void CancelTask()
    {
        cts?.Cancel();
    }

    public void Dispose()
    {
        cts?.Cancel();
        cts?.Dispose();
    }
}

Konzola vývojářských nástrojů prohlížeče označuje spuštění dlouhotrvající JS funkce po Start Task výběru tlačítka a při přerušení funkce po Cancel Task výběru tlačítka:

longRunningFn: 1
longRunningFn: 2
longRunningFn: 3
longRunningFn aborted!

Další materiály