在 ASP.NET Core Blazor 中从 .NET 方法调用 JavaScript 函数Call JavaScript functions from .NET methods in ASP.NET Core Blazor

本文内容

作者:Javier Calvarro NelsonDaniel RothLuke Latham

重要

Blazor WebAssembly 为预览版状态

ASP.NET Core 3.0 支持 Blazor Server。Blazor WebAssembly 在 ASP.NET Core 3.1 中为预览版。

Blazor 应用可从 .NET 方法调用 JavaScript 函数,也可从 JavaScript 函数调用 .NET 方法。这被称为 JavaScript 互操作(JS 互操作) 。

本文介绍如何从 .NET 调用 JavaScript 函数。有关如何从 JavaScript 调用 .NET 方法的信息,请参阅 从 ASP.NET Core Blazor 中的 JavaScript 函数调用 .NET 方法

查看或下载示例代码如何下载

若要从 .NET 调入 JavaScript,请使用 IJSRuntime 抽象。若要发出 JS 互操作调用,请在组件中注入 IJSRuntime 抽象。InvokeAsync<T> 方法采用要与任意数量的 JSON 可序列化参数一起调用的 JavaScript 函数的标识符。函数标识符相对于全局范围 (window)。如果要调用 window.someScope.someFunction,则标识符是 someScope.someFunction无需在调用函数之前进行注册。返回类型 T 也必须可进行 JSON 序列化。T 应该与最能映射到所返回 JSON 类型的 .NET 类型匹配。

对于启用了预呈现的 Blazor 服务器应用,初始预呈现期间无法调入 JavaScript。在建立与浏览器的连接之后,必须延迟 JavaScript 互操作调用。有关详细信息,请参阅检测 Blazor 服务器应用进行预呈现的时间部分。

下面的示例基于 TextDecoder(一种基于 JavaScript 的解码器)。该示例演示如何从 C# 方法调用 JavaScript 函数。JavaScript 函数从 C# 方法接受字节数组,对数组进行解码,并将文本返回给组件进行显示。

在 wwwroot/index.html (Blazor WebAssembly) 或 Pages/_Host.cshtml (Blazor 服务器)的 <head> 元素中,提供了一个 JavaScript 函数,该函数使用 TextDecoder 对传递的数组进行解码并返回解码后的值:

  1. <script>
  2. window.convertArray = (win1251Array) => {
  3. var win1251decoder = new TextDecoder('windows-1251');
  4. var bytes = new Uint8Array(win1251Array);
  5. var decodedArray = win1251decoder.decode(bytes);
  6. console.log(decodedArray);
  7. return decodedArray;
  8. };
  9. </script>

JavaScript 代码(如前面示例中所示的代码)也可以通过对脚本文件的引用,从 JavaScript 文件 (.js ) 加载:

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

以下组件:

  • 在选择了组件按钮(“转换数组” )时使用 JSRuntime 调用 convertArray JavaScript 函数。
  • 调用 JavaScript 函数之后,传递的数组会转换为字符串。该字符串会返回给组件进行显示。
  1. @page "/call-js-example"
  2. @inject IJSRuntime JSRuntime;
  3. <h1>Call JavaScript Function Example</h1>
  4. <button type="button" class="btn btn-primary" @onclick="ConvertArray">
  5. Convert Array
  6. </button>
  7. <p class="mt-2" style="font-size:1.6em">
  8. <span class="badge badge-success">
  9. @_convertedText
  10. </span>
  11. </p>
  12. @code {
  13. // Quote (c)2005 Universal Pictures: Serenity
  14. // https://www.uphe.com/movies/serenity
  15. // David Krumholtz on IMDB: https://www.imdb.com/name/nm0472710/
  16. private MarkupString _convertedText =
  17. new MarkupString("Select the <b>Convert Array</b> button.");
  18. private uint[] _quoteArray = new uint[]
  19. {
  20. 60, 101, 109, 62, 67, 97, 110, 39, 116, 32, 115, 116, 111, 112, 32,
  21. 116, 104, 101, 32, 115, 105, 103, 110, 97, 108, 44, 32, 77, 97,
  22. 108, 46, 60, 47, 101, 109, 62, 32, 45, 32, 77, 114, 46, 32, 85, 110,
  23. 105, 118, 101, 114, 115, 101, 10, 10,
  24. };
  25. private async Task ConvertArray()
  26. {
  27. var text =
  28. await JSRuntime.InvokeAsync<string>("convertArray", _quoteArray);
  29. _convertedText = new MarkupString(text);
  30. StateHasChanged();
  31. }
  32. }

IJSRuntimeIJSRuntime

若要使用 IJSRuntime 抽象,请采用以下任何方法:

  • IJSRuntime 抽象注入 Razor 组件 (.razor ) 中:
  1. @inject IJSRuntime JSRuntime
  2. @code {
  3. protected override void OnInitialized()
  4. {
  5. StocksService.OnStockTickerUpdated += stockUpdate =>
  6. {
  7. JSRuntime.InvokeVoidAsync("handleTickerChanged",
  8. stockUpdate.symbol, stockUpdate.price);
  9. };
  10. }
  11. }

在 wwwroot/index.html (Blazor WebAssembly) 或 Pages/_Host.cshtml (Blazor 服务器)的 <head> 元素中,提供了一个 handleTickerChanged JavaScript 函数。该函数通过 IJSRuntime.InvokeVoidAsync 进行调用,不返回值:

  1. <script>
  2. window.handleTickerChanged = (symbol, price) => {
  3. // ... client-side processing/display code ...
  4. };
  5. </script>
  • IJSRuntime 抽象注入一个类 (.cs ):
  1. public class JsInteropClasses
  2. {
  3. private readonly IJSRuntime _jsRuntime;
  4. public JsInteropClasses(IJSRuntime jsRuntime)
  5. {
  6. _jsRuntime = jsRuntime;
  7. }
  8. public ValueTask<string> TickerChanged(string data)
  9. {
  10. return _jsRuntime.InvokeAsync<string>(
  11. "handleTickerChanged",
  12. stockUpdate.symbol,
  13. stockUpdate.price);
  14. }
  15. }

在 wwwroot/index.html (Blazor WebAssembly) 或 Pages/_Host.cshtml (Blazor 服务器)的 <head> 元素中,提供了一个 handleTickerChanged JavaScript 函数。该函数通过 JSRuntime.InvokeAsync 进行调用,会返回值:

  1. <script>
  2. window.handleTickerChanged = (symbol, price) => {
  3. // ... client-side processing/display code ...
  4. return 'Done!';
  5. };
  6. </script>
  • 对于使用 BuildRenderTree 的动态内容生成,请使用 [Inject] 属性:
  1. [Inject]
  2. IJSRuntime JSRuntime { get; set; }

在本主题附带的客户端示例应用中,向应用提供了两个 JavaScript 函数,可与 DOM 交互以接收用户输入并显示欢迎消息:

  • showPrompt – 生成一个提示,以接受用户输入(用户名)并将名称返回给调用方。
  • displayWelcome – 将来自调用方的欢迎消息分配给 idwelcome 的 DOM 对象。

wwwroot/exampleJsInterop.js :

  1. window.exampleJsFunctions = {
  2. showPrompt: function (text) {
  3. return prompt(text, 'Type your name here');
  4. },
  5. displayWelcome: function (welcomeMessage) {
  6. document.getElementById('welcome').innerText = welcomeMessage;
  7. },
  8. returnArrayAsyncJs: function () {
  9. DotNet.invokeMethodAsync('BlazorSample', 'ReturnArrayAsync')
  10. .then(data => {
  11. data.push(4);
  12. console.log(data);
  13. });
  14. },
  15. sayHello: function (dotnetHelper) {
  16. return dotnetHelper.invokeMethodAsync('SayHello')
  17. .then(r => console.log(r));
  18. }
  19. };

将引用 JavaScript 文件的 <script> 标记置于 wwwroot/index.html 文件 (Blazor WebAssembly) 或 Pages/_Host.cshtml 文件(Blazor 服务器)中。

wwwroot/index.html (Blazor WebAssembly):

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8" />
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
  6. <title>Blazor WebAssembly Sample</title>
  7. <base href="/" />
  8. <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
  9. <link href="css/site.css" rel="stylesheet" />
  10. </head>
  11. <body>
  12. <app>Loading...</app>
  13. <div id="blazor-error-ui">
  14. An unhandled error has occurred.
  15. <a href="" class="reload">Reload</a>
  16. <a class="dismiss">🗙</a>
  17. </div>
  18. <script src="_framework/blazor.webassembly.js"></script>
  19. <script src="exampleJsInterop.js"></script>
  20. </body>
  21. </html>

Pages/_Host.cshtml (Blazor 服务器):

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

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

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

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

请勿将 <script> 标记置于组件文件中,因为 <script> 标记无法动态更新。

.NET 方法通过调用 IJSRuntime.InvokeAsync<T> 与 exampleJsInterop.js 文件中的 JavaScript 函数进行互操作。

IJSRuntime 抽象是异步的,以便可以实现 Blazor 服务器方案。如果应用是 Blazor WebAssembly 应用,并且要同步调用 JavaScript 函数,则向下转换为 IJSInProcessRuntime 并改为调用 Invoke<T>建议大多数 JS 互操作库使用异步 API,以确保库在所有方案中都可用。

该示例应用包含一个用于演示 JS 互操作的组件。该组件:

  • 通过 JavaScript 提示接收用户输入。
  • 将文本返回给组件进行处理。
  • 调用第二个 JavaScript 函数,该函数与 DOM 交互以显示欢迎消息。

Pages/JSInterop.razor :

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

<h1>JavaScript Interop</h1>

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

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

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

@code {
    public async Task TriggerJsPrompt()
    {
        var name = await JSRuntime.InvokeAsync<string>(
                "exampleJsFunctions.showPrompt",
                "What's your name?");

        await JSRuntime.InvokeVoidAsync(
                "exampleJsFunctions.displayWelcome",
                $"Hello {name}! Welcome to Blazor!");
    }
}
  • 通过选择组件的“触发 JavaScript 提示符” 按钮来执行 TriggerJsPrompt 时,则会调用在 wwwroot/exampleJsInterop.js 文件中提供的 JavaScript showPrompt 函数。
  • showPrompt 函数接受进行 HTML 编码并返回给组件的用户输入(用户的名称)。组件将用户的名称存储在本地变量 name 中。
  • 存储在 name 中的字符串会合并为欢迎消息,而该消息会传递给 JavaScript 函数 displayWelcome(它将欢迎消息呈现到标题标记中)。

调用 void JavaScript 函数Call a void JavaScript function

返回 void(0)/void 0undefined 的 JavaScript 函数使用 IJSRuntime.InvokeVoidAsync 进行调用。

检测 Blazor 服务器应用进行预呈现的时间Detect when a Blazor Server app is prerendering

在 Blazor 服务器应用进行预呈现时,由于尚未建立与浏览器的连接,无法执行调用 JavaScript 等特定操作。预呈现时,组件可能需要进行不同的呈现。

要将 JavaScript 互操作调用延迟到与浏览器建立连接之后,可使用 OnAfterRenderAsync 组件生命周期事件仅在完成呈现应用并与客户端建立连接后,才会调用此事件。

@using Microsoft.JSInterop
@inject IJSRuntime JSRuntime

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

@code {
    private ElementReference divElement;

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

对于上述示例代码,请在 wwwroot/index.html<head> ( WebAssembly) 或 Pages/Host.cshtmlBlazor (_ 服务器)的 setElementText 元素中,提供了一个 Blazor JavaScript 函数。该函数通过 IJSRuntime.InvokeVoidAsync 进行调用,不返回值:

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

警告

上述示例直接修改文档对象模型 (DOM),以便仅供演示所用。大多数情况下,不建议使用 JavaScript 直接修改 DOM,因为 JavaScript 可能会干扰 Blazor 的更改跟踪。

以下组件展示了如何以一种与预呈现兼容的方式将 JavaScript 互操作用作组件初始化逻辑的一部分。该组件显示可从 OnAfterRenderAsync 内部触发呈现更新。开发人员必须避免在此场景中创建无限循环。

如果调用 JSRuntime.InvokeAsync,则 ElementRef 仅在 OnAfterRenderAsync 中使用,而不在任何更早的生命周期方法中使用,因为呈现组件后才会有 JavaScript 元素。

会调用 StateHasChanged,使用从 JavaScript 互操作调用中获取的新状态重新呈现该组件。此代码不会创建无限循环,因为仅在 infoFromJsnull 时才调用 StateHasChanged

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

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

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

@code {
    private string infoFromJs;
    private ElementReference divElement;

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

            StateHasChanged();
        }
    }
}

对于上述示例代码,请在 wwwroot/index.html<head> ( WebAssembly) 或 Pages/Host.cshtmlBlazor (_ 服务器)的 setElementText 元素中,提供了一个 Blazor JavaScript 函数。该函数通过 IJSRuntime.InvokeAsync 进行调用,会返回值:

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

警告

上述示例直接修改文档对象模型 (DOM),以便仅供演示所用。大多数情况下,不建议使用 JavaScript 直接修改 DOM,因为 JavaScript 可能会干扰 Blazor 的更改跟踪。

捕获对元素的引用Capture references to elements

某些 JS 互操作方案需要引用 HTML 元素。例如,一个 UI 库可能需要用于初始化的元素引用,或者你可能需要对元素调用类似于命令的 API(如 focusplay)。

使用以下方法在组件中捕获对 HTML 元素的引用:

  • 向 HTML 元素添加 @ref 属性。
  • 定义一个类型为 ElementReference 字段,其名称与 @ref 属性的值匹配。

以下示例演示如何捕获对 username <input> 元素的引用:

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

@code {
    ElementReference username;
}

警告

只使用元素引用改变不与 Blazor 交互的空元素的内容。当第三方 API 向元素提供内容时,此方案十分有用。由于 Blazor 不与元素交互,因此在 Blazor 的元素表示形式与 DOM 之间不可能存在冲突。

在下面的示例中,改变无序列表 (ul) 的内容具有危险性 ,因为 Blazor 会与 DOM 交互以填充此元素的列表项 (<li>):

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

如果 JS 互操作改变元素 MyList 的内容,并且 Blazor 尝试将差异应用于元素,则差异与 DOM 不匹配。

就 .NET 代码而言,ElementReference 是不透明的句柄。可以对 ElementReference 执行的唯一 操作是通过 JS 互操作将它传递给 JavaScript 代码。执行此操作时,JavaScript 端代码会收到一个 HTMLElement 实例,该实例可以与常规 DOM API 一起使用。

例如,以下代码定义一个 .NET 扩展方法,通过该方法可在元素上设置焦点:

exampleJsInterop.js :

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

若要调用不返回值的 JavaScript 函数,请使用 IJSRuntime.InvokeVoidAsync下面的代码通过使用捕获的 ElementReference 调用前面的 JavaScript 函数,在用户名输入上设置焦点:

@inject IJSRuntime JSRuntime

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

@code {
    private ElementReference _username;

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

若要使用扩展方法,请创建接收 IJSRuntime 实例的静态扩展方法:

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

Focus 方法在对象上直接调用。下面的示例假设可从 JsInteropClasses 命名空间使用 Focus 方法:

@inject IJSRuntime JSRuntime
@using JsInteropClasses

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

@code {
    private ElementReference _username;

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

重要

仅在呈现组件后填充 username 变量。如果将未填充的 ElementReference 传递给 JavaScript 代码,则 JavaScript 代码会收到 null 值。若要在组件完成呈现之后操作元素引用(用于对元素设置初始焦点),请使用 OnAfterRenderAsync 或 OnAfterRender 组件生命周期方法

使用泛型类型并返回值时,请使用 ValueTask<T>

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

GenericMethod 在具有类型的对象上直接调用。下面的示例假设可从 JsInteropClasses 命名空间使用 GenericMethod

@inject IJSRuntime JSRuntime
@using JsInteropClasses

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

<p>
    _returnValue: @_returnValue
</p>

@code {
    private ElementReference _username;
    private string _returnValue;

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

跨组件引用元素Reference elements across components

ElementReference 仅保证在组件的 OnAfterRender 方法中有效(并且元素引用为 struct),因此无法在组件之间传递元素引用。

若要使父组件可以向其他组件提供元素引用,父组件可以:

  • 允许子组件注册回调。
  • OnAfterRender 事件期间,通过传递的元素引用调用注册的回调。此方法间接地允许子组件与父级的元素引用交互。

以下 Blazor WebAssembly 示例演示了该方法。

在 wwwroot/index.html 的 <head> 中:

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

在 wwwroot/index.html 的 <body> 中:

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

Pages/Index.razor (父组件):

@page "/"

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

Welcome to your new app.

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

Pages/Index.razor.cs :

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

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

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

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

        public void Dispose()
        {
            _disposing = true;

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

            _subscriptions.Clear();
        }

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

            _subscriptions.Add(observer);

            return new Subscription(observer, this);
        }

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

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

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

Shared/SurveyPrompt.razor (子组件):

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

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

            _subscription = Parent.Subscribe(this);
        }

        public void OnCompleted()
        {
            _subscription = null;
        }

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

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

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

强化 JS 互操作调用Harden JS interop calls

JS 互操作可能会由于网络错误而失败,因此应视为不可靠。默认情况下,Blazor 服务器应用会在一分钟后,使服务器上的 JS 互操作调用超时。如果应用可以容忍更严格的超时(如 10秒),请使用以下方法之一设置超时:

  • Startup.ConfigureServices 中全局指定超时:
services.AddServerSideBlazor(
    options => options.JSInteropDefaultCallTimeout = TimeSpan.FromSeconds({SECONDS}));
  • 对于组件代码中的每个调用,单个调用可以指定超时:
var result = await JSRuntime.InvokeAsync<string>("MyJSOperation", 
    TimeSpan.FromSeconds({SECONDS}), new[] { "Arg1" });

有关资源耗尽的详细信息,请参阅 安全 ASP.NET Core Blazor 服务器应用

在类库中共享互操作代码Share interop code in a class library

可在类库中包含 JS 互操作代码,以便能在 NuGet 包中共享代码。

类库会处理在生成的程序集中嵌入 JavaScript 资源的操作。JavaScript 文件位于 wwwroot 文件夹中 。工具负责在生成库时嵌入资源。

按引用任何其他 NuGet 包的方式在应用的项目文件中引用生成的 NuGet 包。包还原后,应用代码可如同是 C# 一样调入 JavaScript。

有关详细信息,请参阅 ASP.NET Core Razor 组件类库

其他资源Additional resources