ASP.NET Core Blazor JavaScript 互通性 (JS interop)
注意
這不是本文的最新版本。 如需目前的版本,請參閱 本文 的 .NET 7 版本。
本文說明如何在 Blazor 應用程式中與 JavaScript 互動的一般概念。
Blazor 應用程式可以從 JS 函式的 .NET 方法和 .NET 方法叫用 JavaScript (JS) 函式。 這些案例稱為 JavaScript 互通性 (JS interop)。
下列文章提供進一步的 JS Interop 指引:
注意
JavaScript [JSImport]
/[JSExport]
Interop API 適用于 ASP.NET Core 7.0 或更新版本中的用戶端元件。
如需詳細資訊,請參閱 JavaScript JS 匯入/ JS 匯出與 ASP.NET Core Blazor 的 Interop。
在本文中,用戶端 / 用戶端 和 伺服器端 / 的詞彙 可用來區分應用程式程式碼執行的位置:
- 用戶端 / 用戶端
- Web 應用程式的互動式用戶端轉譯 Blazor 。 檔案
Program
是Program.cs
用戶端專案 (.Client
)。 Blazor腳本啟動組態位於伺服器專案的元件 (Components/App.razor
) 中App
。 具有 指示詞的可路由 WebAssembly 和自動轉譯模式元件@page
會放在用戶端專案的Pages
資料夾中。 根據元件功能,將不可路由的共用元件放在專案的根.Client
目錄或自訂資料夾中。 - 應用程式 Blazor WebAssembly 。 檔案
Program
為Program.cs
。 Blazor 腳本啟動組態位於 檔案中wwwroot/index.html
。
- Web 應用程式的互動式用戶端轉譯 Blazor 。 檔案
- 伺服器端 / :Web 應用程式的互動式伺服器轉譯。 Blazor 檔案
Program
是Program.cs
伺服器專案的 。 Blazor 腳本啟動組App
態位於 元件 (Components/App.razor
) 中。 只有具有 指示詞的@page
可路由伺服器轉譯模式元件會放在Components/Pages
資料夾中。 無法路由傳送的共用元件會放在伺服器專案的Components
資料夾中。 視需要根據元件功能建立自訂資料夾。
- 用戶端 / 用戶端
- 裝載
Client
Blazor WebAssembly 應用程式的專案。 - 應用程式 Blazor WebAssembly 。
- Blazor 腳本啟動組態位於 檔案中
wwwroot/index.html
。 - 檔案
Program
為Program.cs
。
- 裝載
- 伺服器端 /
- 裝載
Server
Blazor WebAssembly 應用程式的專案。 - 應用程式 Blazor Server 。 Blazor 在 中找到
Pages/_Host.cshtml
腳本啟動組態。 - 檔案
Program
為Program.cs
。
- 裝載
- 用戶端 / 用戶端
- 裝載
Client
Blazor WebAssembly 應用程式的專案。 - 應用程式 Blazor WebAssembly 。
- Blazor 腳本啟動組態位於 檔案中
wwwroot/index.html
。 - 檔案
Program
為Program.cs
。
- 裝載
- 伺服器端 /
- 裝載
Server
Blazor WebAssembly 應用程式的專案。 - 應用程式 Blazor Server 。 Blazor 在 中找到
Pages/_Layout.cshtml
腳本啟動組態。 - 檔案
Program
為Program.cs
。
- 裝載
- 用戶端 / 用戶端
- 裝載
Client
Blazor WebAssembly 應用程式的專案。 - 應用程式 Blazor WebAssembly 。
- Blazor 腳本啟動組態位於 檔案中
wwwroot/index.html
。 - 檔案
Program
為Program.cs
。
- 裝載
- 伺服器端 /
- 裝載
Server
Blazor WebAssembly 應用程式的專案。 - 應用程式 Blazor Server 。 Blazor 在 中找到
Pages/_Host.cshtml
腳本啟動組態。 - 檔案
Program
為Program.cs
。
- 裝載
JavaScript Interop 抽象概念和功能套件
套件 @microsoft/dotnet-js-interop
( npmjs.com
) 提供 .NET 與 JavaScript 程式 JS 代碼之間 Interop 的抽象概念和功能。 GitHub 存放庫 ( /src/JSInterop
資料夾) 中 dotnet/aspnetcore
提供參考來源。 如需詳細資訊,請參閱 GitHub 存放庫的 README.md
檔案。
注意
.NET 參考來源的文件連結通常會載入存放庫的預設分支,這表示下一版 .NET 的目前開發。 若要選取特定版本的標籤,請使用 [切換分支或標籤] 下拉式清單。 如需詳細資訊,請參閱如何選取 ASP.NET Core 原始程式碼 (dotnet/AspNetCore.Docs #26205) 的版本標籤。
在 TypeScript 中撰寫 JS Interop 腳本的其他資源:
與 DOM 的互動
只有在物件未與 Blazor 互動時,才會使用 JavaScript 來變動 DOM。 JS Blazor 會維護 DOM 的表示法,並與 DOM 物件直接互動。 如果使用 JS 直接或透過 JS Interop 在外部修改 Blazor 所轉譯的專案,DOM 可能不再符合 Blazor 的內部表示法,這可能會導致未定義的行為。 未定義的行為可能僅僅會干擾元素或其函式的轉譯,但也可能會對應用程式或伺服器造成安全性風險。
本指南不僅適用於您自己的 JS Interop 程式碼,也適用於應用程式所使用的任何 JS 程式庫,包括協力廠商架構所提供的任何項目,例如 Bootstrap JS 和 jQuery。
在一些文件範例中,JS Interop 是單純就示範目的用來將元素進行變動,做為範例的一部分。 在這些案例中,會在文字中出現警告。
如需詳細資訊,請參閱從 ASP.NET Core Blazor 中的 .NET 方法呼叫 JavaScript 函式。
非同步 JavaScript 呼叫
不論所呼叫的程式碼是同步還是非同步,JS Interop 呼叫預設都是非同步。 呼叫預設為非同步,以確保元件在伺服器端和用戶端轉譯模型之間相容。 採用伺服器端轉譯時, JS Interop 呼叫必須是非同步,因為它們是透過網路連線傳送的。 對於專門採用用戶端轉譯的應用程式,支援同步 JS Interop 呼叫。
如需詳細資訊,請參閱下列文章:
物件序列化
Blazor 會使用 System.Text.Json 進行下列需求和預設行為的序列化:
- 類型必須具有預設建構函式、
get
/set
存取子必須是公用的,而且永遠不會將欄位序列化。 - 無法自訂全域預設序列化,以避免中斷現有的元件庫、對效能和安全性產生影響,以及降低可靠性。
- 序列化 .NET 成員名稱會產生小寫 JSON 機碼名稱。
- JSON 會還原序列化為 JsonElement C# 執行個體,以允許混合大小寫。 儘管在 JSON 索引鍵名稱和 C# 屬性名稱之間有任何大小寫差異,指派給 C# 模型屬性的內部轉換會如預期般運作。
JsonConverter API 可用於自訂序列化。 您可以使用 [JsonConverter]
屬性來標註屬性,以覆寫現有資料類型的預設序列化。
如需詳細資訊,請參閱 .NET 文件中的下列資源:
- 在 .NET 中進行 JSON 序列化和還原序列化 (封送及解除封送)
- 如何使用
System.Text.Json
自訂屬性名稱與值 - 如何在 .NET 中撰寫 JSON 序列化 (封送) 的自訂轉換器
Blazor 支援最佳化的位元組陣列 JS Interop,可避免將位元組陣列編碼/解碼為 Base64。 應用程式可以套用自訂序列化,並傳遞所產生的位元組。 如需詳細資訊,請參閱從 ASP.NET Core Blazor 中的 .NET 方法呼叫 JavaScript 函式。
當大量 .NET 物件快速序列化,或當大型 .NET 物件或許多 .NET 物件必須進行序列化時,Blazor 支援解除封送的 JS Interop。 如需詳細資訊,請參閱從 ASP.NET Core Blazor 中的 .NET 方法呼叫 JavaScript 函式。
元件處置期間的 DOM 清除工作
請勿在元件處置期間執行 JS DOM 清除工作的 Interop 程式碼。 MutationObserver
請改用用戶端上的 JavaScript ( JS ) 模式,原因如下:
- 當清除程式碼在 中
Dispose{Async}
執行時,元件可能已經從 DOM 中移除。 - 在伺服器端轉譯期間,當清除程式碼在 中
Dispose{Async}
執行時, Blazor 架構可能會處置轉譯器。
模式 MutationObserver
可讓您在從 DOM 移除專案時執行函式。
在下列範例中 DOMCleanup
,元件:
- 包含
<div>
的id
cleanupDiv
。<div>
從 DOM 移除元件時,會從 DOM 移除元素,以及元件的其餘 DOM 標記。 DOMCleanup
JS 從DOMCleanup.razor.js
檔案載入 類別,並呼叫其createObserver
函式來設定回MutationObserver
呼。 這些工作是在生命週期方法 中OnAfterRenderAsync
完成的。
DOMCleanup.razor
:
@page "/dom-cleanup"
@attribute [RenderModeServer]
@implements IAsyncDisposable
@inject IJSRuntime JS
<h1>DOM Cleanup Example</h1>
<div id="cleanupDiv"></div>
@code {
private IJSObjectReference? jsModule;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
jsModule = await JS.InvokeAsync<IJSObjectReference>(
"import", "./Components/Pages/DOMCleanup.razor.js");
await jsModule.InvokeVoidAsync("DOMCleanup.createObserver");
}
}
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (jsModule is not null)
{
await jsModule.DisposeAsync();
}
}
}
@page "/dom-cleanup"
@implements IAsyncDisposable
@inject IJSRuntime JS
<h1>DOM Cleanup Example</h1>
<div id="cleanupDiv"></div>
@code {
private IJSObjectReference? jsModule;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
jsModule = await JS.InvokeAsync<IJSObjectReference>(
"import", "./Pages/DOMCleanup.razor.js");
await jsModule.InvokeVoidAsync("DOMCleanup.createObserver");
}
}
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (jsModule is not null)
{
await jsModule.DisposeAsync();
}
}
}
在下列範例中,每次發生 DOM 變更時, MutationObserver
都會執行回呼。 當 語句確認已移除 if (targetRemoved) { ... }
目標專案 ( cleanupDiv
) 時 if
,執行清除程式碼。 請務必中斷連線並刪除 MutationObserver
,以避免清除程式碼執行之後記憶體流失。
DOMCleanup.razor.js
與上述 DOMCleanup
元件並排放置:
export class DOMCleanup {
static observer;
static createObserver() {
const target = document.querySelector('#cleanupDiv');
this.observer = new MutationObserver(function (mutations) {
const targetRemoved = mutations.some(function (mutation) {
const nodes = Array.from(mutation.removedNodes);
return nodes.indexOf(target) !== -1;
});
if (targetRemoved) {
// Cleanup resources here
// ...
// Disconnect and delete MutationObserver
this.observer && this.observer.disconnect();
delete this.observer;
}
});
this.observer.observe(target.parentNode, { childList: true });
}
}
window.DOMCleanup = DOMCleanup;
沒有線路的 JavaScript Interop 呼叫
本節僅適用于伺服器端應用程式。
線上路中斷連線之後 SignalR ,無法發出 JavaScript ( JS ) Interop 呼叫。 在沒有元件處置期間或線路不存在的任何其他時間,下列方法呼叫會失敗,並記錄線路中斷連線的訊息: JSDisconnectedException
- JS Interop 方法呼叫
Dispose
/DisposeAsync
在任何 IJSObjectReference 上呼叫 。
為了避免記錄 JSDisconnectedException 或記錄自訂資訊,請在 語句中攔截例外狀況 try-catch
。
針對下列元件處置範例:
- 元件會實作 IAsyncDisposable。
objInstance
是 IJSObjectReference。- JSDisconnectedException 已攔截且未記錄。
- 您可以選擇性地在 語句中
catch
記錄自訂資訊,不論您偏好的記錄層級為何。 下列範例不會記錄自訂資訊,因為它假設開發人員不在乎元件處置期間線路中斷連線的時間或位置。
async ValueTask IAsyncDisposable.DisposeAsync()
{
try
{
if (objInstance is not null)
{
await objInstance.DisposeAsync();
}
}
catch (JSDisconnectedException)
{
}
}
如果您必須在遺失線路之後清除自己的 JS 物件,或在用戶端上執行其他 JS 程式碼,請在 MutationObserver
用戶端上使用 模式 JS 。 模式 MutationObserver
可讓您在從 DOM 移除專案時執行函式。
如需詳細資訊,請參閱下列文章:
- 處理 ASP.NET Core Blazor 應用程式中的錯誤: JavaScript Interop 區段討論 Interop 案例中的 JS 錯誤處理。
- ASP.NET 核心 Razor 元件生命週期 : 使用
IDisposable
和IAsyncDisposable
的元件處置一節說明如何在元件中 Razor 實作處置模式。
JavaScript 位置
使用下列任何方法載入 JavaScript (JS) 程式碼:
- 在
<head>
標記 中載入指令碼 (通常不建議使用) - 在
<body>
標記中載入指令碼 - 從外部 JavaScript 檔案載入指令碼 (
.js
) - 在 Blazor 啟動後插入指令碼
警告
請勿將 <script>
標籤 Razor 放在元件檔案 (.razor
) 中,因為 Blazor 無法動態更新 <script>
標籤。
注意
文件範例通常會將指令碼放在 <script>
標籤中,或從外部檔案載入全域指令碼。 這些方法會利用全域函式來使用戶端產生問題。 針對生產應用程式,建議您將 JavaScript 放在個別的 JavaScript 模組中,以視需要匯入。 如需詳細資訊,請參閱 JavaScript 模組中的 JavaScript 隔離一節。
注意
文件範例會將指令碼放在 <script>
標籤內,或從外部檔案載入全域指令碼。 這些方法會利用全域函式來使用戶端產生問題。 在 ASP.NET Core 5.0 以前的 Blazor 中不支援將 JavaScript 放在個別的 JavaScript 模組 (可視需要匯入) 中。 如果應用程式需要使用 JS 模組進行 JS 隔離,建議您使用 ASP.NET Core 5.0 或更新版本來建置應用程式。 如需詳細資訊,請使用 [版本] 下拉式清單選取本文的 5.0 或更新版本,並參閱 JavaScript 模組中的 JavaScript 隔離一節。
在 <head>
標記中載入指令碼
通常不建議使用本節中的方法。
將 JavaScript () 標記 ( JS ) 放在元素標記 中 <head>
: <script>...</script>
<head>
...
<script>
window.jsMethod = (methodParameter) => {
...
};
</script>
</head>
從 <head>
載入 JS 並不是最佳方法,原因如下:
- 如果指令碼相依於 Blazor,則 JS Interop 可能會失敗。 建議您使用其中一種其他方法來載入指令碼,而不是透過
<head>
標記載入指令碼。 - 頁面可能會因為剖析指令碼中的 JS 所需的時間而互動變慢。
在 <body>
標記中載入指令碼
將 JavaScript () 標記 ( JS<script>...</script>
) 放在腳本參考之後 Blazor 的 結尾 </body>
元素 內:
<body>
...
<script src="{BLAZOR SCRIPT}"></script>
<script>
window.jsMethod = (methodParameter) => {
...
};
</script>
</body>
在上述範例中 {BLAZOR SCRIPT}
,預留位置是 Blazor 腳本路徑和檔案名。
從外部 JavaScript 檔案載入指令碼 (.js
) 與元件共置
將 JavaScript (JS) 檔案組合在一起,以用於頁面、檢視和 Razor 元件,是組織應用程式中指令碼的便利方式。
使用下列副檔名慣例共置 JS 檔案:
- MVC 應用程式的 Razor Pages 應用程式和檢視的頁面:
.cshtml.js
。 例子:- Razor Pages 應用程式
Index
頁面的Pages/Index.cshtml.js
位於Pages/Index.cshtml
。 - MVC 應用程式
Views/Home/Index.cshtml.js
檢視的Index
位於Views/Home/Index.cshtml
。
- Razor Pages 應用程式
- Blazor 應用程式的 Razor 元件:
.razor.js
。 範例:Index.razor.js
用於Index
元件。
共置的 JS 檔案可使用專案中檔案的路徑公開定址:
應用程式中共置指令檔的頁面、檢視和元件:
{PATH}/{PAGE, VIEW, OR COMPONENT}.{EXTENSION}.js
{PATH}
預留位置是頁面、檢視或元件的路徑。{PAGE, VIEW, OR COMPONENT}
預留位置是頁面、檢視或元件。{EXTENSION}
預留位置符合頁面、檢視或元件的延伸模組,可以是razor
或cshtml
。
Razor 頁面範例:
Index
頁面的 JS 檔案會放在Index
頁面 (Pages/Index.cshtml
) 旁的Pages
資料夾 (Pages/Index.cshtml.js
) 中。 在Index
頁面中,會在Pages
資料夾的路徑上參考指令碼:@section Scripts { <script src="~/Pages/Index.cshtml.js"></script> }
發佈應用程式時,架構會自動將指令碼移至 Web 根目錄。 在上述範例中,指令碼會移至
bin\Release\{TARGET FRAMEWORK MONIKER}\publish\wwwroot\Pages\Index.cshtml.js
,其中{TARGET FRAMEWORK MONIKER}
預留位置是目標 Framework Moniker (TFM)。 在Index
頁面中不需要變更指令碼的相對 URL。Blazor 範例:
JS元件的檔案
Index
會放在元件旁邊Index
(Index.razor
)。 在元件中Index
,腳本會在其路徑參考。Index.razor.js
:export function showPrompt(message) { return prompt(message, 'Type anything here'); }
在
Index
元件(Index.razor
) 的OnAfterRenderAsync
方法中:module = await JS.InvokeAsync<IJSObjectReference>( "import", "./Components/Pages/Index.razor.js");
發佈應用程式時,架構會自動將指令碼移至 Web 根目錄。 在上述範例中,指令碼會移至
bin\Release\{TARGET FRAMEWORK MONIKER}\publish\wwwroot\Components\Pages\Index.razor.js
,其中{TARGET FRAMEWORK MONIKER}
預留位置是目標 Framework Moniker (TFM)。 在Index
元件中不需要變更指令碼的相對 URL。如需 Razor 類別庫 (RCL) 所提供的指令碼:
_content/{PACKAGE ID}/{PATH}/{PAGE, VIEW, OR COMPONENT}.{EXTENSION}.js
{PACKAGE ID}
預留位置是 RCL 的封裝識別碼 (或應用程式所參考類別庫的程式庫名稱)。{PATH}
預留位置是頁面、檢視或元件的路徑。 如果 Razor 元件位於 RCL 的根目錄,則不包含路徑區段。{PAGE, VIEW, OR COMPONENT}
預留位置是頁面、檢視或元件。{EXTENSION}
預留位置符合頁面、檢視或元件的延伸模組,可以是razor
或cshtml
。
在以下 Blazor 應用程式範例中:
- RCL 的封裝識別碼為
AppJS
。 - 模組的指令碼會針對
Index
元件載入 (Index.razor
)。 - 元件
Index
位於Pages
RCL 資料夾的 資料夾中Components
。
var module = await JS.InvokeAsync<IJSObjectReference>("import", "./_content/AppJS/Components/Pages/Index.razor.js");
如需關於 RCL 的詳細資訊,請參閱從 Razor 類別庫 (RCL) 取用 ASP.NET Core Razor。
從外部 JavaScript 檔案 (.js
) 載入指令碼
將 JavaScript () 標記 ( JS<script>...</script>
) 與腳本來源 ( src
) 路徑放在腳本參考之後 Blazor 的 結尾 </body>
元素 內:
<body>
...
<script src="{BLAZOR SCRIPT}"></script>
<script src="{SCRIPT PATH AND FILE NAME (.js)}"></script>
</body>
在上述範例中 {BLAZOR SCRIPT}
,預留位置是 Blazor 腳本路徑和檔案名。 {SCRIPT PATH AND FILE NAME (.js)}
預留位置是 wwwroot
下的路徑和指令檔名稱。
在上述 <script>
標籤的下列範例中,scripts.js
檔案位於應用程式的 wwwroot/js
資料夾中:
<script src="js/scripts.js"></script>
如果您不想將所有腳本保留在下方的個別資料夾中 wwwroot
,也可以直接從 wwwroot
資料夾提供腳本:
<script src="scripts.js"></script>
當 Razor 類別庫提供外部 JS 檔案時,請使用其穩定的靜態 Web 資產路徑來指定 JS 檔案:./_content/{PACKAGE ID}/{SCRIPT PATH AND FILE NAME (.js)}
:
- 需要目前目錄 (
./
) 的路徑區段,才能建立 JS 檔案的正確靜態資產路徑。 {PACKAGE ID}
預留位置是程式庫的封裝識別碼。 如果未在專案檔中指定<PackageId>
,則封裝識別碼預設為專案的組件名稱。{SCRIPT PATH AND FILE NAME (.js)}
預留位置是wwwroot
下的路徑和檔案名稱。
<body>
...
<script src="{BLAZOR SCRIPT}"></script>
<script src="./_content/{PACKAGE ID}/{SCRIPT PATH AND FILE NAME (.js)}"></script>
</body>
在上述 <script>
標籤的下列範例中:
- Razor 類別庫的組件名稱為
ComponentLibrary
,而且未在程式庫的專案檔中指定<PackageId>
。 scripts.js
檔案位於類別庫的wwwroot
資料夾中。
<script src="./_content/ComponentLibrary/scripts.js"></script>
如需詳細資訊,請參閱從 Razor 類別庫 (RCL) 取用 ASP.NET Core Razor。
在啟動前後 Blazor 插入腳本
若要確保腳本在啟動前後 Blazor 載入,請使用 JavaScript 初始化運算式。 如需詳細資訊和範例,請參閱 ASP.NET Core Blazor 啟動 。
在 Blazor 啟動後插入指令碼
若要在啟動之後 Blazor 插入腳本,請將 鏈結至 Promise
,其結果會從 手動開始 Blazor 。 如需詳細資訊和範例,請參閱 ASP.NET Core Blazor 啟動 。
JavaScript 模組中的 JavaScript 隔離
Blazor 會啟用標準 JavaScript 模組 (ECMAScript 規格) 中的 JavaScript (JS) 隔離。
JS 隔離提供下列優點:
- 已匯入的 JS 不再會產生全域命名空間問題。
- 不需要程式庫和元件的取用者即可匯入相關 JS。
如需詳細資訊,請參閱從 ASP.NET Core Blazor 中的 .NET 方法呼叫 JavaScript 函式。
ASP.NET Core 和 Blazor 支援使用 import()
運算子 的動態匯入:
if ({CONDITION}) import("/additionalModule.js");
在上述範例中 {CONDITION}
,預留位置代表條件式檢查,以判斷是否應該載入模組。
如需瀏覽器相容性,請參閱 我可以使用:JavaScript 模組:動態匯入 。
快取的 JavaScript 檔案
在 Development
環境中開發期間,通常不會在用戶端上快取 JavaScript (JS) 檔案和其他靜態資產。 在開發期間,靜態資產要求包含Cache-Control
標頭,其值為 no-cache
或 max-age
,並包含零的值 (0
)。
在 Production
環境中的生產期間,用戶端通常會快取 JS 檔案。
為了在瀏覽器中停用用戶端快取,開發人員通常會採用下列其中一種方法:
- 在瀏覽器的開發人員工具主控台開啟時停用快取。 您可以在每個瀏覽器維護者的開發人員工具檔中找到指引:
- 對 Blazor 應用程式的任何網頁執行手動瀏覽器重新整理,以從伺服器重新載入 JS 檔案。 ASP.NET Core 的 HTTP 快取中介軟體一律會接受用戶端所傳送的有效無快取
Cache-Control
標頭。
如需詳細資訊,請參閱
JavaScript Interop 呼叫的大小限制
本節僅適用于伺服器端應用程式中的互動式元件。 針對用戶端元件,架構不會限制 JavaScript ( JS ) Interop 輸入和輸出的大小。
針對伺服器端應用程式中的互動式元件, JS 從用戶端傳遞資料到伺服器的 Interop 呼叫會受限於中樞方法允許的傳入 SignalR 訊息大小上限,而中樞方法會強制執行 HubOptions.MaximumReceiveMessageSize 此大小上限(預設為:32 KB)。 JS大於擲 MaximumReceiveMessageSize 回錯誤的 .NET SignalR 訊息。 架構不會限制從中樞到用戶端的 SignalR 訊息大小。 如需有關處理訊息大小限制的大小限制、錯誤訊息和指引的詳細資訊,請參閱 ASP.NET 核心 BlazorSignalR 指引 。