この投稿では、BlazorのJavaSciript相互運用機能のコードを読んでいきます。
を読んでいることを前提とします。
vesrion : 3.0.100-preview5-011568
前提の確認
の確認を行います。
JavaScript相互運用機能の使い方はこのような感じです。
@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」です。Blazorのコードは「AspNetCore/src/Components/Blazor/」にあります。
IJSRuntime
はデフォルトで作成されています。ですので、まず作成している場所を探しましょう。
dotnet new blazor -o
コマンドでBlazorプロジェクトを作成した時のProgram.cs
は以下の通りです。
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はこのようになっています。CreateDefaultBuilder
メソッドは、WebAssemblyHostBuilder
のインスタンスを生成しています。
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をみてみます。
WebAssemblyHostBuilder
のCreateServiceProviderメソッド内に注目します。
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
にだけ注目するとここですね。
services.AddSingleton<IJSRuntime>(WebAssemblyJSRuntime.Instance);
次は、WebAssemblyJSRuntime.Instanceをみていきましょう。
internal static class WebAssemblyJSRuntime
{
public static readonly MonoWebAssemblyJSRuntime Instance = new MonoWebAssemblyJSRuntime();
}
WebAssemblyJSRuntime.Instance
は、MonoWebAssemblyJSRuntime
型のstaticフィールドです。
MonoWebAssemblyJSRuntime
は今までみたきたものと別のアセンブリで、「Mono.WebAssembly.Interop」にあります。コードはこちら。
MonoWebAssemblyJSRuntime
の各メソッドは内部でInternalCalls
を使っています。
InternalCalls
はexternとなっています。コメントによるとMono配布のdriver.cの中に実装があるのではないでしょうか。
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言語で書かれているようです。
また機会があれば追ってみます。