BlazorにおけるJavaScript相互運用機能をちょっとだけコードリーディング

この投稿では、BlazorのJavaSciript相互運用機能のコードを読んでいきます。


* [Blazor JavaScript interop](https://docs.microsoft.com/en-us/aspnet/core/blazor/javascript-interop?view=aspnetcore-3.0)
* [Blazor dependency injection](https://docs.microsoft.com/en-us/aspnet/core/blazor/dependency-injection?view=aspnetcore-3.0)

を読んでいることを前提とします。

vesrion : 3.0.100-preview5-011568

* [aspnet/AspNetCore](https://github.com/aspnet/AspNetCore)


# 前提の確認

* [Blazor JavaScript interop](https://docs.microsoft.com/en-us/aspnet/core/blazor/javascript-interop?view=aspnetcore-3.0)
* [Blazor dependency injection](https://docs.microsoft.com/en-us/aspnet/core/blazor/dependency-injection?view=aspnetcore-3.0)

の確認を行います。

JavaScript相互運用機能の使い方はこのような感じです。

```csharp
@page "/hello-jsinterop"
@inject IJSRuntime jsRuntime;

<button onclick="@Log">Console.Log with JavaScript</button>

@functions {

    void Log()
    {
        jsRuntime.InvokeAsync<string>("console.log", "hello world JavaScript Interop");
    }
}
```

`@inject`はサービスのインジェクトを行います。Componentのrazorファイル内で、`IJSRuntime`をインジェクトし、そのインスタンスを使うことで、JavaScriptを呼び出すことができます。

Blazorでは、

* HttpClient
* IJSRuntime
* IUriHelper

はデフォルトで作成されていて、`@inject`をするだけでサービスをインジェクトすることができます。自分で作ったサービスは、デフォルトで作成されないので設定をする必要があります。

# コードリーディング

それではコードを見ていきます。リポジトリは、「[aspnet/AspNetCore](https://github.com/aspnet/AspNetCore)」です。Blazorのコードは「[AspNetCore/src/Components/Blazor/](https://github.com/aspnet/AspNetCore/tree/master/src/Components/Blazor)」にあります。


`IJSRuntime`はデフォルトで作成されています。ですので、まず作成している場所を探しましょう。

`dotnet new blazor -o`コマンドでBlazorプロジェクトを作成した時の`Program.cs`は以下の通りです。

```csharp
using Microsoft.AspNetCore.Blazor.Hosting;

namespace YourNameSpace
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IWebAssemblyHostBuilder CreateHostBuilder(string[] args) =>
            BlazorWebAssemblyHost.CreateDefaultBuilder()
                .UseBlazorStartup<Startup>();
    }
}
```

[BlazorWebAssemblyHost](https://github.com/aspnet/AspNetCore/blob/v3.0.0-preview5-19227-01/src/Components/Blazor/Blazor/src/Hosting/BlazorWebAssemblyHost.cs)はこのようになっています。`CreateDefaultBuilder`メソッドは、`WebAssemblyHostBuilder`のインスタンスを生成しています。

```csharp
public static class BlazorWebAssemblyHost
{
    /// <summary>
    /// Creates a an instance of <see cref="IWebAssemblyHostBuilder"/>.
    /// </summary>
    /// <returns>The <see cref="IWebAssemblyHostBuilder"/>.</returns>
    public static IWebAssemblyHostBuilder CreateDefaultBuilder()
    {
        return new WebAssemblyHostBuilder();
    }
}
```

次に、[WebAssemblyHostBuilder](https://github.com/aspnet/AspNetCore/blob/v3.0.0-preview5-19227-01/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHostBuilder.cs)をみてみます。

`WebAssemblyHostBuilder`の[CreateServiceProvider](https://github.com/aspnet/AspNetCore/blob/v3.0.0-preview5-19227-01/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHostBuilder.cs#L85-L109)メソッド内に注目します。

```csharp
private void CreateServiceProvider()
{
    var services = new ServiceCollection();
    services.AddSingleton(_BrowserHostBuilderContext);
    services.AddSingleton<IWebAssemblyHost, WebAssemblyHost>();
    services.AddSingleton<IJSRuntime>(WebAssemblyJSRuntime.Instance);
    services.AddSingleton<IComponentContext, WebAssemblyComponentContext>();
    services.AddSingleton<IUriHelper>(WebAssemblyUriHelper.Instance);
    services.AddSingleton<HttpClient>(s =>
    {
        // Creating the URI helper needs to wait until the JS Runtime is initialized, so defer it.
        var uriHelper = s.GetRequiredService<IUriHelper>();
        return new HttpClient
        {
            BaseAddress = new Uri(WebAssemblyUriHelper.Instance.GetBaseUri())
        };
    });

    foreach (var configureServicesAction in _configureServicesActions)
    {
        configureServicesAction(_BrowserHostBuilderContext, services);
    }

    var builder = _serviceProviderFactory.CreateBuilder(services);
    _appServices = _serviceProviderFactory.CreateServiceProvider(builder);
}
```

* HttpClient
* IJSRuntime
* IUriHelper

はデフォルトで作成されていますが、ここで作成していることがわかりました。`Instance`にだけ注目すると[ここ](https://github.com/aspnet/AspNetCore/blob/v3.0.0-preview5-19227-01/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHostBuilder.cs#L90)ですね。

```csharp
    services.AddSingleton<IJSRuntime>(WebAssemblyJSRuntime.Instance);
```


次は、[WebAssemblyJSRuntime.Instance](https://github.com/aspnet/AspNetCore/blob/v3.0.0-preview5-19227-01/src/Components/Blazor/Blazor/src/Services/WebAssemblyJSRuntime.cs)をみていきましょう。

```csharp
internal static class WebAssemblyJSRuntime
{
    public static readonly MonoWebAssemblyJSRuntime Instance = new MonoWebAssemblyJSRuntime();
}
```

`WebAssemblyJSRuntime.Instance`は、`MonoWebAssemblyJSRuntime`型のstaticフィールドです。


`MonoWebAssemblyJSRuntime`は今までみたきたものと別のアセンブリで、「[Mono.WebAssembly.Interop](https://www.nuget.org/packages/Mono.WebAssembly.Interop/3.0.0-preview5.19227.9)」にあります。コードは[こちら](https://github.com/aspnet/Extensions/tree/v3.0.0-preview5.19227.9/src/JSInterop)。


* [MonoWebAssemblyJSRuntime](https://github.com/aspnet/Extensions/blob/v3.0.0-preview5.19227.9/src/JSInterop/Mono.WebAssembly.Interop/src/MonoWebAssemblyJSRuntime.cs)
* [InternalCalls](https://github.com/aspnet/Extensions/blob/v3.0.0-preview5.19227.9/src/JSInterop/Mono.WebAssembly.Interop/src/InternalCalls.cs)

`MonoWebAssemblyJSRuntime`の各メソッドは内部で`InternalCalls`を使っています。

`InternalCalls`はexternとなっています。コメントによるとMono配布のdriver.cの中に実装があるのではないでしょうか。

```csharp
internal class InternalCalls
{
    // The exact namespace, type, and method names must match the corresponding entries
    // in driver.c in the Mono distribution

    // We're passing asyncHandle by ref not because we want it to be writable, but so it gets
    // passed as a pointer (4 bytes). We can pass 4-byte values, but not 8-byte ones.
    [MethodImpl(MethodImplOptions.InternalCall)]
    public static extern string InvokeJSMarshalled(out string exception, ref long asyncHandle, string functionIdentifier, string argsJson);

    [MethodImpl(MethodImplOptions.InternalCall)]
    public static extern TRes InvokeJSUnmarshalled<T0, T1, T2, TRes>(out string exception, string functionIdentifier, T0 arg0, T1 arg1, T2 arg2);
}
```

# まとめ

簡単にですが、JavaScript相互運用機能・`IJSRuntime`のコードを追ってみました。

主要な実装はexternでC言語で書かれているようです。

また機会があれば追ってみます。