【Blazor】JavascriptからC#(.Net)のstaticもしくはインスタンスメソッドを呼び出す。

# はじめに
こんにちは。
Blazor上で、JavascriptからC#のコードを呼び出す方法について調べたので備忘録として残しておきます。
staticメソッドかインスタンスメソッドかによって
呼び出し方法が異なるのでそれぞれ記載します。

なお、呼び出し方法はバリエーションがあるようなので、一部のやり方のみを記載しています。
(また追記します)

実行環境は以下の通りです。
- C# 10.0
- .Net 6.0
- Maui Blazor(通常のBlazorでも動作すると思います)

## staticメソッドの呼び出し
実行したい処理がstaticの場合は以下のように実現できます。

1. 使用したいC#のメソッドに`JSInvokable`属性を付与する。
1. JS側で`DotNet.invokeMethodAsync()`を実行し、C#のメソッドを呼び出す。
```Test.razor.cs
[JSInvokable]
public static void StaticMethod(string arg)
{
    //実行したい処理
}
```
```Test.razor.js
//DotNet.invokeMethodAsync(アセンブリ名, 関数名, 引数);
DotNet.invokeMethodAsync('BlazorTest', 'StaticMethod', 'arg' {, 'arg2', 'arg3'});
```
アセンブリ名はいわゆるアプリケーション名になります(csprojファイルに記載があります)。

引数が不要な時は指定不要で、複数必要な時はカンマ区切りで設定ができます。
ただしJsonシリアライズ可能なものに限られます。

#### 戻り値がある場合
上記はvoidですが、戻り値がある場合は普通にreturnしてあげることでJS側で渡すことが可能です。
```Test.razor.cs
[JSInvokable]
public static string StaticMethod(string arg)
{
    //実行したい処理
    return "返したい文字";
}
```
また、複数の値を返したい時はカスタムクラスを作ってreturnすれば大丈夫です。
ここでのクラスはシリアライズ可能である必要があります(→get可能なプロパティがあること)。
```Test.razor.cs
[JSInvokable]
public static ReturnValues StaticMethod(string arg)
{
    //実行したい処理
    return new ReturnValues("テキスト", 100);
}

public class ReturnValues{
    public string Text {get;}
    public int Number {get;}
    public ReturnValues(string text, int number){
        Text = test;
        Number =number;
    }
}
```
JS側での呼び出しは基本的に`invokeMethodAsync`が推奨されているようで、
これはJSPromiseを返してくれます。
そのため、`.then()`等の記載が続けられます。


## インスタンスメソッドの呼び出し
staticメソッドの場合は上記のやり方で実行できますが、
staticでない変数を操作したい時は別の呼び出し方法にする必要があります。
(親コンポーネントから受け取ったパラメータを操作したいなど)

### staticなFuncもしくはAction経由で呼び出す
事前準備以外はstaticメソッドの呼び出しと同じです。

1. staticなFuncもしくはActionを定義しておく。
1. OnInitialized()などのタイミングで、実際に実行したい処理をActionへ設定する。
1. JS側から呼び出せるよう、`JSInvokable`属性をつけたstaticメソッドを作成する。
1. JS側で`DotNet.invokeMethodAsync()`を実行し、C#のメソッドを呼び出す。

```Test.razor.cs
private static Action<string> InstanceAction;

protected override void OnInitialized()
{
    InstanceAction = LocalInstanceAction;
}

protected void LocalInstanceAction(string args)
{
    //実行したい処理
}

[JSInvokable]
public static void InstanceMethod(string arg)
{
    InstanceAction(arg);
}
```
```Test.razor.js
DotNet.invokeMethodAsync('BlazorTest', 'InstanceMethod', 'arg');
```

コンポーネント呼び出し時に実行する処理を設定することで、staticな処理も実行することができます。
ただしコンポーネントが呼び出されていない時も、staticで宣言されているためJSからメソッドの呼び出しは可能です。
この場合は参照先が無く、例外が発生するので注意が必要です。

### JSへ.Netのオブジェクト参照を渡して呼び出す
上記のやり方でも呼び出しはできていますが、
複数の処理を実行したい場合は記載が長くなってしまいます。

複数の処理を実行する際は.Netの参照をJSへ渡すことで記載を減らすことができます。
1. razorページ自体の参照(.Netのオブジェクトへの参照)を生成する。
1. OnAfterRenderAsync()などのタイミングで、JSへ参照を渡す。
1. JS側では、参照の受け取りとメソッドの実行を行うクラスを作成する。
1. クラス経由でC#のメソッドを呼び出す。

```Test.razor.cs
@inject IJSRuntime JS;
//このページ自体の参照を作成
private DotNetObjectReference<Test>? dotNetObjRef;

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender) 
    {
        dotNetObjRef = DotNetObjectReference.Create(this);
        //JSのクラスのメソッドを呼び出し、引数には.Netの参照を指定する。
        await JS.InvokeVoidAsync("DotNetHelper.setDotNetObjRef", dotNetObjRef);
    }
}

[JSInvokable]
public void InstanceMethod1(string arg)
{
    //実行したい処理
}

[JSInvokable]
public void InstanceMethod2(string arg)
{
    //実行したい処理
}

async ValueTask IAsyncDisposable.DisposeAsync()
{
    //参照を破棄
    dotNetObjRef?.Dispose();
}

```

```Test.razor.js
class DotNetHelper {
    static dotNetObjRef;

    //.Netの参照を受け取る。
    static setDotNetObjRef(value) {
        DotNetHelper.dotNetObjRef = value;
    }

    static instanceMethod1(arg) {
        this.dotNetObjRef.invokeMethodAsync('InstanceMethod1', arg)
    }

    static instanceMethod2(arg) {
        this.dotNetObjRef.invokeMethodAsync('InstanceMethod2', arg)
    }
}

//グローバルスコープに設定する。
window.DotNetHelper = DotNetHelper;
---
DotNetHelper.instanceMethod1('test');
```

この呼び出し方法なら複数の処理でもすっきり記載できます。
ただし以下の点に注意が必要です。
1. メモリリークが発生するので必ず.Net参照を破棄すること。
1. グローバルスコープにクラスを設定しないと、JS側での.Net参照の受け取りができないこと。

#### 戻り値がある場合
staticメソッドの時と同じやり方で対応可能です。

## 参考
https://learn.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/call-dotnet-from-javascript?view=aspnetcore-6.0
https://stackoverflow.com/questions/66385844/call-a-c-sharp-non-static-method-from-a-static-method-in-blazor-invoked-by-javas

### 余談
C#側の戻り値のシリアライズは`System.Text.Json`の動きとはちょっと違うみたいです。
(というかそのまま返してくれるようです)
HTMLタグに含まれる記号や、全角の記号？なんかは
コードに変換されてしまって対応が大変だと思うんですが、
特に何の指定もせずに、以下のようにシリアライズされています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953759/0d0b653c-66ee-dbda-84d2-b80fead097c1.png)

(あと、相互にJSを呼ばないと実装できないような機能がある場合は、
Blazorを使うのに向いていないアプリケーションかも、と調べていて思いました)

不足している点や間違いがありましたらコメントお願いいたします。