前言

Blazor 應用程式有提供從 JavaSript 呼叫 .NET Method 的方法,當然反過來也可以從 .NET Method 叫用 JavaScript Function,打通兩個語言之間的邊界,讓互動性更上一層樓!

概觀

在 Blazor 中想要撰寫JS,有五種方式可以挑選

  1. <head> 標記 中載入指令碼 (不推薦)
1
2
3
4
5
6
7
8
<head>

    <script>
      window.jsMethod = () => {
        alert("Hello World!")
      };
    </script>
</head>

不推薦寫在 head標記內的原因有兩個

  • 如果指令碼相依於 Blazor,則 JS Interop 可能會失敗
  • 頁面可能會因為解析 JS 所需的時間增加而變慢
  1. <body> 標記中載入指令碼
1
2
3
4
5
6
7
8
9
<body>
    ...
    <script src="_framework/blazor.{webassembly|server}.js"></script>
    <script>
      window.jsMethod = () => {
        alert("Hello World!")
      };
    </script>
</body>
  1. 從外部 JavaScript 檔案載入指令碼 這是最熟悉的方式,獨立的Javascript檔案從外部引入到檔案內,可以將Javascript 放置於wwwroot內
1
2
3
4
5
6
<body>
    ...

    <script src="_framework/blazor.{webassembly|server}.js"></script>
    <script src="{要引入的Javascript檔名.js}"></script>
</body>
  1. 從外部 JavaScript 檔案載入指令碼 (.js) 與元件共置 將 JavaScript 檔案組合在一起,以用於頁面(Page)、檢視(View)和 Razor 元件,是組織指令碼的便利方式,在 Blazor 中可以在元件的後方加上.js結尾,看起來像這樣.razor.js

Pages/Index.razor.js

1
2
3
export function showHello() {
  return prompt('Hello');
}

Pages/Index.razor

1
2
module = await JS.InvokeAsync<IJSObjectReference>(
    "import", "./Pages/Index.razor.js");
  1. 在 Blazor 啟動後插入指令碼
  • 在引入 Blazor 的 JS 時加入屬性 autostart="false"
  • Blazor.start().then Blazor 啟動後,在建立Sctipt區塊填入JS檔案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<body>
    ...

    <script src="_framework/blazor.{webassembly|server}.js" 
        autostart="false"></script>
    <script>
      Blazor.start().then(function () {
        var customScript = document.createElement('script');
        customScript.setAttribute('src', 'scripts.js');
        document.head.appendChild(customScript);
      });
    </script>
</body>

不要把 <script> 放在 .razor 內,因為 Blazor 無法動態更新

.NET 叫用 JavaScript 函式

想要在 .Net 內呼叫 JS 方法,首先需要 IJSRuntime 這一個介面,他的實作由 Blazor 框架註冊,這部分介紹兩個方法

  • JSRuntimeExtensions.InvokeAsync 這個方法適用於呼叫 JavaScript 函式並讀取傳回的值,用起來的效果像是這樣

JavaScriptRun.razor.js

1
2
3
export function getHello() {
    return 'Hello';
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@page "/JsRun"
@inject IJSRuntime JS

<button @onclick="getHello">Click</button>
<p>@text</p>

@code {
    private IJSObjectReference? module;
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            module = await JS.InvokeAsync<IJSObjectReference>("import", "./Pages/JavaScriptRun.razor.js");
        }
    }

    private string text{ get; set; }

    private async Task getHello()
    {
        text = await module.InvokeAsync<string>("getHello");
    }
}

這邊採用的是上面的第四種方式: 從外部 JavaScript 檔案載入指令碼 (.js) 與元件共置

  • JSRuntimeExtensions.InvokeVoidAsync 這個則是用於呼叫沒有回傳值的 JS 方法
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@page "/JsRun"
@inject IJSRuntime JS

<button @onclick="AlertHello">Click</button>

@code {
    private async Task AlertHello()
    {
        await JS.InvokeVoidAsync("alert", "Hello");
    }
}

JS Interop 呼叫預設都是非同步的,如果應用程式只有在 Blazor WebAssembly 執行,可以選擇進行同步 JS Interop 呼叫,這樣會稍微少一些額外負荷,而且可能會縮短轉譯週期

JavaScript 叫用 .NET

接下來我們說說,從 JS 呼叫 .NET Method的方法

  • 呼叫靜態方法
  1. DotNet.invokeMethodAsync 這是一個非同步的方法,推薦使用這個來做 .NET 方法的呼叫 語法看起來長這樣
1
DotNet.invokeMethodAsync('{組件名稱}', '{方法名稱}', {參數});
  1. DotNet.invokeMethod 同步方法,僅對 Blazor WebAssembly 應用程式同步

想要讓 JS可以呼叫到 .NET 有幾個先決條件

  1. 方法必須是公開的(Public)
  2. 需要加入 [JSInvokable] Attribute

JsRun.razor

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@page "/JsRun"
<button onclick="getString()">
    Click
</button>

@code {
    [JSInvokable]
    public static async Task<string> GetHello()
    {
        await Task.CompletedTask;
        return "Hello";
    }
}

仔細看在Button Click的事件,沒有加入 @ 代表這不是 Razor 語法

JsRun.js

1
2
3
4
5
6
window.getString = () => {
    DotNet.invokeMethodAsync('BlazorApp1', 'GetHello')
        .then(data => {
            console.log(data);
        });
};

填入組件名稱以及方法名稱,就可以從JS呼叫 .NET 靜態方法了

  • 執行個體方法 直接上程式碼

JsRun.razor

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@page "/JsRun"
@implements IDisposable
@inject IJSRuntime JS

<button @onclick="JsCallInstanceMethod">
    按我呼叫C# 執行個體方法
</button>

<P>@result</P>

@code {
    private string? result;
    private DotNetObjectReference<JsRun>? objRef;

    public async Task JsCallInstanceMethod()
    {
        objRef = DotNetObjectReference.Create(this);
        result = await JS.InvokeAsync<string>("sayHello", objRef);
    }

    [JSInvokable]
    public string GetHelloMessage()
    {
        return $"Hello !";
    }

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

JsRun.razor.js

1
2
3
window.sayHello = (dotNetHelper) => {
    return dotNetHelper.invokeMethodAsync('GetHelloMessage');
};

呼叫執行個體方法稍稍複雜了一些,我們一步一步看他是怎麼執行的

  1. 首先按鈕按下去後,執行JsCallInstanceMethod(),建立新的 DotNetObjectReference<JsRun> 執行個體
  2. 執行 JS 的 sayHello Function,調用 .NET GetHelloMessage 方法,完成後結果回傳給result
  3. 最後為了避免記憶體洩漏以及允許GC,DotNetObjectReference 建立的執行個體要被放在 Dispose

小結

就這樣我們一來一回的完成兩種語言的相互呼叫,在JS互通其實還有很多好玩的東西和細節,像是可以直接把stream 從 JS 傳輸到 .NET 、Blazor 在 JavaScript Module 有啟用JavaScript 隔離再也不用擔心全域汙染的問題……等等,一時半刻也說不清,在後續的章節慢慢補充說明

寫完這篇才發現忘記介紹在 Blazor 該怎麼寫 CSS 了XDD 下一篇文章回頭說說 Blazor 元件 CSS