From 690ea8af9b6e8c20de10d557e7ce371c2bed9b09 Mon Sep 17 00:00:00 2001 From: Cody Merritt Anhorn Date: Sat, 16 Dec 2023 17:09:08 -0600 Subject: [PATCH] feat(upgrade): Upgraded to .NET8 (#39) * feat(upgrade): Upgraded to .NET8 Migrated to new Interop patterns. Expanded on Performance Validations. * feat(build): Updated build files and framework versions. * feat(build): Added missing .NET Workload. * feat(version): Bumped new minor Version. +semver: minor --- .github/workflows/dotnet-build-publish.yml | 19 +- .github/workflows/dotnet-package.yml | 16 +- .github/workflows/main-tag-bump.yml | 8 +- .../EventHorizon.Blazor.Interop.Sample.csproj | 13 +- ...teropFuncArrayCallbackValidationTest.razor | 15 +- .../InteropFuncCallbackValidationTest.razor | 5 +- ...ropFuncLiteralCallbackValidationTest.razor | 5 +- ...nteropNewArrayCallbackValidationTest.razor | 5 +- .../InteropNewCallbackValidationTest.razor | 5 +- ...eropNewLiteralCallbackValidationTest.razor | 5 +- .../InteropSetDecimalTest.razor | 36 + .../GetPerformance/InteropSetFloatTest.razor | 35 + .../GetPerformance/InteropSetIntTest.razor | 35 + .../InteropSetPerformancePage.razor | 10 + .../GetPerformance/InteropSetTest.razor | 35 + .../InteropTesting/InteropGetTest.razor | 1 + ...ropResultClassCallbackValidationTest.razor | 22 +- .../Validation/InteropValidationsPage.razor | 2 + ...nteropDecimalNumberSetValidationTest.razor | 51 + .../JsonTesting/ClientJsonParseRawTest.razor | 11 +- EventHorizon.Blazor.Interop.Sample/Program.cs | 44 +- .../Shared/MainLayout.razor | 1 + .../wwwroot/Bridge.js | 12 +- .../EventHorizon.Blazor.Interop.csproj | 8 +- .../EventHorizonBlazorInterop.cs | 1261 ++++++++--------- .../InternalInteropImport.cs | 15 + .../wwwroot/interop-bridge.js | 186 ++- entry.ps1 | 51 + 28 files changed, 1056 insertions(+), 856 deletions(-) create mode 100644 EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/GetPerformance/InteropSetDecimalTest.razor create mode 100644 EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/GetPerformance/InteropSetFloatTest.razor create mode 100644 EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/GetPerformance/InteropSetIntTest.razor create mode 100644 EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/GetPerformance/InteropSetPerformancePage.razor create mode 100644 EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/GetPerformance/InteropSetTest.razor create mode 100644 EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/Validation/Set/InteropDecimalNumberSetValidationTest.razor create mode 100644 EventHorizon.Blazor.Interop/InternalInteropImport.cs create mode 100644 entry.ps1 diff --git a/.github/workflows/dotnet-build-publish.yml b/.github/workflows/dotnet-build-publish.yml index cedb1a3..f62d4ec 100644 --- a/.github/workflows/dotnet-build-publish.yml +++ b/.github/workflows/dotnet-build-publish.yml @@ -13,20 +13,28 @@ jobs: runs-on: ubuntu-latest name: Build/Deploy .NET Core steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 + - name: Setup .NET Core - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v4 with: - dotnet-version: '6.0.x' + dotnet-version: '8.0.x' + + - name: Install .NET Workloads + run: dotnet workload install wasm-tools + - name: Install dependencies run: dotnet restore + - name: Build run: dotnet build --configuration Release --no-restore + - name: .NET Publish run: dotnet publish --configuration Release -o published ./EventHorizon.Blazor.Interop.Sample + - name: Publish Static Site id: builddeploy - uses: Azure/static-web-apps-deploy@v0.0.1-preview + uses: Azure/static-web-apps-deploy@v1 with: azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_POLITE_PLANT_0F8750A10 }} repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments) @@ -44,7 +52,8 @@ jobs: steps: - name: Close Pull Request id: closepullrequest - uses: Azure/static-web-apps-deploy@v0.0.1-preview + uses: Azure/static-web-apps-deploy@v1 with: azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_POLITE_PLANT_0F8750A10 }} action: 'close' + app_location: "published/wwwroot" # App source code path diff --git a/.github/workflows/dotnet-package.yml b/.github/workflows/dotnet-package.yml index f8fa155..516e6a0 100644 --- a/.github/workflows/dotnet-package.yml +++ b/.github/workflows/dotnet-package.yml @@ -11,27 +11,37 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: '0' + - name: Install GitVersion uses: gittools/actions/gitversion/setup@v0.9.6 with: versionSpec: '5.x' + - name: Use GitVersion id: gitversion # step id used as reference for output values uses: gittools/actions/gitversion/execute@v0.9.6 with: additionalArguments: '/updateAssemblyInfo' + - run: | echo "NuGetVersionV2: ${{ steps.gitversion.outputs.NuGetVersionV2 }}" + - name: Setup .NET Core - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v4 with: - dotnet-version: '6.0.x' + dotnet-version: '8.0.x' + + - name: Install .NET Workloads + run: dotnet workload install wasm-tools + - name: Build with dotnet run: dotnet build --configuration Release ./EventHorizon.Blazor.Interop/EventHorizon.Blazor.Interop.csproj + - name: Pack with dotnet run: dotnet pack EventHorizon.Blazor.Interop/EventHorizon.Blazor.Interop.csproj --output nuget-packages --configuration Release -p:PackageVersion=${{ steps.gitversion.outputs.NuGetVersionV2 }} + - name: Push with dotnet run: dotnet nuget push nuget-packages/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json diff --git a/.github/workflows/main-tag-bump.yml b/.github/workflows/main-tag-bump.yml index cbe36f0..b42cfaf 100644 --- a/.github/workflows/main-tag-bump.yml +++ b/.github/workflows/main-tag-bump.yml @@ -9,20 +9,24 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: '0' + - name: Install GitVersion uses: gittools/actions/gitversion/setup@v0.9.6 with: versionSpec: '5.x' + - name: Use GitVersion id: gitversion # step id used as reference for output values uses: gittools/actions/gitversion/execute@v0.9.6 + - run: | echo "NuGetVersionV2: ${{ steps.gitversion.outputs.NuGetVersionV2 }}" + - name: Bump version and push tag - uses: anothrNick/github-tag-action@1.17.2 + uses: anothrNick/github-tag-action@1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # We want to push to nuget the same version we tag diff --git a/EventHorizon.Blazor.Interop.Sample/EventHorizon.Blazor.Interop.Sample.csproj b/EventHorizon.Blazor.Interop.Sample/EventHorizon.Blazor.Interop.Sample.csproj index 99cebe1..96379d2 100644 --- a/EventHorizon.Blazor.Interop.Sample/EventHorizon.Blazor.Interop.Sample.csproj +++ b/EventHorizon.Blazor.Interop.Sample/EventHorizon.Blazor.Interop.Sample.csproj @@ -1,18 +1,19 @@ - net6.0 + net8.0 false false + + true - - - - - + + + + diff --git a/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/Callbacks/Validation/InteropFuncArrayCallbackValidationTest.razor b/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/Callbacks/Validation/InteropFuncArrayCallbackValidationTest.razor index f29874a..bd4d8da 100644 --- a/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/Callbacks/Validation/InteropFuncArrayCallbackValidationTest.razor +++ b/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/Callbacks/Validation/InteropFuncArrayCallbackValidationTest.razor @@ -58,24 +58,25 @@ actualClass1 = arg1[0]; actualClass2 = arg1[1]; actualClass3 = arg1[2]; + + ValidateTest(); return Task.CompletedTask; }); EventHorizonBlazorInterop.Func( - new string[] { testId, "register" }, - actionHandler + new string[] { testId, "register" }, + actionHandler ); initialized = true; } RunTest(); - ValidateTest(); } public void RunTest() { EventHorizonBlazorInterop.Func( - new object[] { new string[] { testId, "trigger" } } + new object[] { new string[] { testId, "trigger" } } ); } @@ -83,8 +84,8 @@ { var value = actualClass1.Id; if (actualClass1.Id == expectedClass1 - && actualClass2.Id == expectedClass2 - && actualClass3.Id == expectedClass3 + && actualClass2.Id == expectedClass2 + && actualClass3.Id == expectedClass3 ) { TestStatus = "Passed"; @@ -93,5 +94,7 @@ { TestStatus = "Failed"; } + + StateHasChanged(); } } diff --git a/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/Callbacks/Validation/InteropFuncCallbackValidationTest.razor b/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/Callbacks/Validation/InteropFuncCallbackValidationTest.razor index f2ca3bf..f9898f3 100644 --- a/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/Callbacks/Validation/InteropFuncCallbackValidationTest.razor +++ b/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/Callbacks/Validation/InteropFuncCallbackValidationTest.razor @@ -64,6 +64,8 @@ actualClass2 = arg3; actualString2 = arg4; actualClass3 = arg5; + + ValidateTest(); return Task.CompletedTask; }); @@ -75,7 +77,6 @@ } RunTest(); - ValidateTest(); } public void RunTest() @@ -101,5 +102,7 @@ { TestStatus = "Failed"; } + + StateHasChanged(); } } diff --git a/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/Callbacks/Validation/InteropFuncLiteralCallbackValidationTest.razor b/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/Callbacks/Validation/InteropFuncLiteralCallbackValidationTest.razor index d028f63..42dfa53 100644 --- a/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/Callbacks/Validation/InteropFuncLiteralCallbackValidationTest.razor +++ b/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/Callbacks/Validation/InteropFuncLiteralCallbackValidationTest.razor @@ -64,6 +64,8 @@ actualClass2 = arg3; actualString2 = arg4; actualClass3 = arg5; + + ValidateTest(); return Task.CompletedTask; }); @@ -78,7 +80,6 @@ } RunTest(); - ValidateTest(); } public void RunTest() @@ -104,5 +105,7 @@ { TestStatus = "Failed"; } + + StateHasChanged(); } } diff --git a/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/Callbacks/Validation/InteropNewArrayCallbackValidationTest.razor b/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/Callbacks/Validation/InteropNewArrayCallbackValidationTest.razor index 2d62540..6e42549 100644 --- a/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/Callbacks/Validation/InteropNewArrayCallbackValidationTest.razor +++ b/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/Callbacks/Validation/InteropNewArrayCallbackValidationTest.razor @@ -61,6 +61,8 @@ actualClass1 = arg1[0]; actualClass2 = arg1[1]; actualClass3 = arg1[2]; + + ValidateTest(); return Task.CompletedTask; }); @@ -72,7 +74,6 @@ } RunTest(); - ValidateTest(); } public void RunTest() @@ -97,5 +98,7 @@ { TestStatus = "Failed"; } + + StateHasChanged(); } } diff --git a/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/Callbacks/Validation/InteropNewCallbackValidationTest.razor b/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/Callbacks/Validation/InteropNewCallbackValidationTest.razor index 751d100..03ae39e 100644 --- a/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/Callbacks/Validation/InteropNewCallbackValidationTest.razor +++ b/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/Callbacks/Validation/InteropNewCallbackValidationTest.razor @@ -66,6 +66,8 @@ actualClass2 = arg3; actualString2 = arg4; actualClass3 = arg5; + + ValidateTest(); return Task.CompletedTask; }); @@ -77,7 +79,6 @@ } RunTest(); - ValidateTest(); } public void RunTest() @@ -104,5 +105,7 @@ { TestStatus = "Failed"; } + + StateHasChanged(); } } diff --git a/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/Callbacks/Validation/InteropNewLiteralCallbackValidationTest.razor b/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/Callbacks/Validation/InteropNewLiteralCallbackValidationTest.razor index 267d4a5..1e5b94c 100644 --- a/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/Callbacks/Validation/InteropNewLiteralCallbackValidationTest.razor +++ b/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/Callbacks/Validation/InteropNewLiteralCallbackValidationTest.razor @@ -66,6 +66,8 @@ actualClass2 = arg3; actualString2 = arg4; actualClass3 = arg5; + + ValidateTest(); return Task.CompletedTask; }); @@ -80,7 +82,6 @@ } RunTest(); - ValidateTest(); } public void RunTest() @@ -107,5 +108,7 @@ { TestStatus = "Failed"; } + + StateHasChanged(); } } diff --git a/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/GetPerformance/InteropSetDecimalTest.razor b/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/GetPerformance/InteropSetDecimalTest.razor new file mode 100644 index 0000000..be6dffe --- /dev/null +++ b/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/GetPerformance/InteropSetDecimalTest.razor @@ -0,0 +1,36 @@ +@using System.Diagnostics +@using EventHorizon.Blazor.Interop; + +
+

Interop Set Decimal Test

+
Interop Set
+ + +
+ +@code { + public TimeSpan TimeTaken { get; set; } + + const int _max = 1_000; + private void HandleRunTest() + { + var s1 = Stopwatch.StartNew(); + for (int i = 0; i < _max; i++) + { + + RunTest(); + } + s1.Stop(); + TimeTaken = s1.Elapsed; + Console.WriteLine(((double)(s1.ElapsedMilliseconds * 1000000) / _max).ToString("0.00 ns")); + } + + public void RunTest() + { + EventHorizonBlazorInterop.Set( + "setPrimitive", + "setNumber", + 999.99m + ); + } +} diff --git a/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/GetPerformance/InteropSetFloatTest.razor b/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/GetPerformance/InteropSetFloatTest.razor new file mode 100644 index 0000000..8f6d804 --- /dev/null +++ b/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/GetPerformance/InteropSetFloatTest.razor @@ -0,0 +1,35 @@ +@using System.Diagnostics +@using EventHorizon.Blazor.Interop; + +
+

Interop Set Float Test

+
Interop Set
+ + +
+ +@code { + public TimeSpan TimeTaken { get; set; } + + const int _max = 1_000; + private void HandleRunTest() + { + var s1 = Stopwatch.StartNew(); + for (int i = 0; i < _max; i++) + { + RunTest(); + } + s1.Stop(); + TimeTaken = s1.Elapsed; + Console.WriteLine(((double)(s1.ElapsedMilliseconds * 1000000) / _max).ToString("0.00 ns")); + } + + public void RunTest() + { + EventHorizonBlazorInterop.Set( + "setPrimitive", + "setNumber", + 99.99f + ); + } +} diff --git a/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/GetPerformance/InteropSetIntTest.razor b/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/GetPerformance/InteropSetIntTest.razor new file mode 100644 index 0000000..8788fb5 --- /dev/null +++ b/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/GetPerformance/InteropSetIntTest.razor @@ -0,0 +1,35 @@ +@using System.Diagnostics +@using EventHorizon.Blazor.Interop; + +
+

Interop Set Int Test

+
Interop Set
+ + +
+ +@code { + public TimeSpan TimeTaken { get; set; } + + const int _max = 1_000; + private void HandleRunTest() + { + var s1 = Stopwatch.StartNew(); + for (int i = 0; i < _max; i++) + { + RunTest(); + } + s1.Stop(); + TimeTaken = s1.Elapsed; + Console.WriteLine(((double)(s1.ElapsedMilliseconds * 1000000) / _max).ToString("0.00 ns")); + } + + public void RunTest() + { + EventHorizonBlazorInterop.Set( + "setPrimitive", + "setInt", + 9999 + ); + } +} diff --git a/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/GetPerformance/InteropSetPerformancePage.razor b/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/GetPerformance/InteropSetPerformancePage.razor new file mode 100644 index 0000000..0749f3b --- /dev/null +++ b/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/GetPerformance/InteropSetPerformancePage.razor @@ -0,0 +1,10 @@ +@page "/interop/performance/set" + +

Interop Set Performance

+ +
+ + + + +
\ No newline at end of file diff --git a/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/GetPerformance/InteropSetTest.razor b/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/GetPerformance/InteropSetTest.razor new file mode 100644 index 0000000..ad2e94a --- /dev/null +++ b/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/GetPerformance/InteropSetTest.razor @@ -0,0 +1,35 @@ +@using System.Diagnostics +@using EventHorizon.Blazor.Interop; + +
+

Interop Set Test

+
Interop Set
+ + +
+ +@code { + public TimeSpan TimeTaken { get; set; } + + const int _max = 1_000; + private void HandleRunTest() + { + var s1 = Stopwatch.StartNew(); + for (int i = 0; i < _max; i++) + { + RunTest(); + } + s1.Stop(); + TimeTaken = s1.Elapsed; + Console.WriteLine(((double)(s1.ElapsedMilliseconds * 1000000) / _max).ToString("0.00 ns")); + } + + public void RunTest() + { + EventHorizonBlazorInterop.Set( + "setPrimitive", + "setString", + "location" + ); + } +} diff --git a/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/InteropGetTest.razor b/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/InteropGetTest.razor index 1fd3e53..30b738a 100644 --- a/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/InteropGetTest.razor +++ b/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/InteropGetTest.razor @@ -3,6 +3,7 @@

Interop Get Test

+
Interop Get
diff --git a/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/ResultCallback/Validation/InteropResultClassCallbackValidationTest.razor b/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/ResultCallback/Validation/InteropResultClassCallbackValidationTest.razor index d50e817..2708b7b 100644 --- a/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/ResultCallback/Validation/InteropResultClassCallbackValidationTest.razor +++ b/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/ResultCallback/Validation/InteropResultClassCallbackValidationTest.razor @@ -45,8 +45,8 @@ if (arg1 == expectedArg) { return new TestClass { Found = true, Arg1 = arg1 }; - } - + } + return new TestClass { Found = false }; }); @@ -58,13 +58,13 @@ actionHandler ); initialized = true; - TestStatus = "Failed"; - - if (result.Found - && result.Arg1 == expectedArg) - { - TestStatus = "Passed"; - } - } - } + TestStatus = "Failed"; + + if (result.Found + && result.Arg1 == expectedArg) + { + TestStatus = "Passed"; + } + } + } } diff --git a/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/Validation/InteropValidationsPage.razor b/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/Validation/InteropValidationsPage.razor index 0387fcf..8598f61 100644 --- a/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/Validation/InteropValidationsPage.razor +++ b/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/Validation/InteropValidationsPage.razor @@ -1,4 +1,5 @@ @page "/interop/validations" +@using EventHorizon.Blazor.Interop.Sample.Pages.Testing.InteropTesting.Validation.Set

Interop Validations

@@ -9,4 +10,5 @@ + \ No newline at end of file diff --git a/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/Validation/Set/InteropDecimalNumberSetValidationTest.razor b/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/Validation/Set/InteropDecimalNumberSetValidationTest.razor new file mode 100644 index 0000000..6f3d0dd --- /dev/null +++ b/EventHorizon.Blazor.Interop.Sample/Pages/Testing/InteropTesting/Validation/Set/InteropDecimalNumberSetValidationTest.razor @@ -0,0 +1,51 @@ +
+

Interop Decimal Number Set Validation

+
Interop Set
+
+ Status: + @if (TestStatus == "Passed") + { + @TestStatus + } + else if (TestStatus == "Failed") + { + @TestStatus + } + else + { + @TestStatus + } +
+ +
+ + + +@code { + public string TestStatus = "Pending"; + + private string testId = "InteropDecimalNumberSetValidationTest"; + private decimal value = 0.0000000000000001224646852585m; + + private void HandleRunTest() + { + TestStatus = "Pending"; + RunTest(); + TestStatus = "Passed"; + } + + public void RunTest() + { + EventHorizonBlazorInterop.Set( + testId, + "value", + value + ); + } +} diff --git a/EventHorizon.Blazor.Interop.Sample/Pages/Testing/JsonTesting/ClientJsonParseRawTest.razor b/EventHorizon.Blazor.Interop.Sample/Pages/Testing/JsonTesting/ClientJsonParseRawTest.razor index 0a71b6e..c60a9bb 100644 --- a/EventHorizon.Blazor.Interop.Sample/Pages/Testing/JsonTesting/ClientJsonParseRawTest.razor +++ b/EventHorizon.Blazor.Interop.Sample/Pages/Testing/JsonTesting/ClientJsonParseRawTest.razor @@ -17,20 +17,21 @@ { var s1 = Stopwatch.StartNew(); // Version 1: describe version 1 here. + var runtime = ((Microsoft.JSInterop.IJSInProcessRuntime)JSRuntime); for (int i = 0; i < _max; i++) { - StandardTest(); + StandardTest(runtime); } s1.Stop(); TimeTaken = s1.Elapsed; Console.WriteLine(((double)(s1.ElapsedMilliseconds * 1000000) / _max).ToString("0.00 ns")); } - public void StandardTest() + public void StandardTest(IJSInProcessRuntime runtime) { - ((IJSUnmarshalledRuntime)JSRuntime).InvokeUnmarshalled, string>( - "bridge.jsonParseRaw", - ValueTuple.Create(@"{ ""Type"": ""event_to_run"" }") + runtime.Invoke( + "bridge.jsonParseRaw", + @"{ ""Type"": ""event_to_run"" }" ); } diff --git a/EventHorizon.Blazor.Interop.Sample/Program.cs b/EventHorizon.Blazor.Interop.Sample/Program.cs index 8c417a8..526e1ee 100644 --- a/EventHorizon.Blazor.Interop.Sample/Program.cs +++ b/EventHorizon.Blazor.Interop.Sample/Program.cs @@ -1,39 +1,29 @@ +namespace EventHorizon.Blazor.Interop.Sample; + using System; using System.Net.Http; -using System.Collections.Generic; using System.Threading.Tasks; -using System.Text; +using EventHorizon.Blazor.Interop.Sample.Pages.DITesting.Model; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using MediatR; -using EventHorizon.Blazor.Interop.Sample.Pages.DITesting.Model; -namespace EventHorizon.Blazor.Interop.Sample +public class Program { - public class Program + public static async Task Main(string[] args) { - public static async Task Main(string[] args) - { - var builder = WebAssemblyHostBuilder.CreateDefault(args); - builder.RootComponents.Add("app"); + var builder = WebAssemblyHostBuilder.CreateDefault(args); + builder.RootComponents.Add("app"); - builder.Services - .AddTransient( - sp => new HttpClient - { - BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) - } - ); - builder.Services - .AddSingleton(); - builder.Services - .AddMediatR( - typeof(Program).Assembly - ); + builder + .Services + .AddTransient( + sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) } + ); + builder.Services.AddSingleton(); + builder + .Services + .AddMediatR(opts => opts.RegisterServicesFromAssemblies(typeof(Program).Assembly)); - await builder.Build().RunAsync(); - } + await builder.Build().RunAsync(); } } diff --git a/EventHorizon.Blazor.Interop.Sample/Shared/MainLayout.razor b/EventHorizon.Blazor.Interop.Sample/Shared/MainLayout.razor index f896e46..0d0d0e0 100644 --- a/EventHorizon.Blazor.Interop.Sample/Shared/MainLayout.razor +++ b/EventHorizon.Blazor.Interop.Sample/Shared/MainLayout.razor @@ -9,6 +9,7 @@ Run All Interop Scenario Testing + Interop Set Performance Interop Validation Callback Testing Callback Validation diff --git a/EventHorizon.Blazor.Interop.Sample/wwwroot/Bridge.js b/EventHorizon.Blazor.Interop.Sample/wwwroot/Bridge.js index ed1f049..b52a2ce 100644 --- a/EventHorizon.Blazor.Interop.Sample/wwwroot/Bridge.js +++ b/EventHorizon.Blazor.Interop.Sample/wwwroot/Bridge.js @@ -4,12 +4,12 @@ }, jsonParse: (json) => { const obj = JSON.parse(json); - console.log({ obj }) + console.log({ obj }); }, jsonParseRaw: (args) => { - var json = Blazor.platform.readStringField(args); + var json = args; const obj = JSON.parse(json); - console.log({ obj }) + console.log({ obj }); } }; @@ -63,6 +63,12 @@ window["getPrimitive"] = { result: 999, numberResult: 999.99, }; +window["setPrimitive"] = { + setInt: 999, + setString: "string", + setBool: true, + setNumber: 999.99, +}; window["getArray"] = { result: ["string1", "string2"], diff --git a/EventHorizon.Blazor.Interop/EventHorizon.Blazor.Interop.csproj b/EventHorizon.Blazor.Interop/EventHorizon.Blazor.Interop.csproj index ec17c7f..4d35b42 100644 --- a/EventHorizon.Blazor.Interop/EventHorizon.Blazor.Interop.csproj +++ b/EventHorizon.Blazor.Interop/EventHorizon.Blazor.Interop.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 true EventHorizon Blazor Interop @@ -14,11 +14,13 @@ https://github.com/canhorn/EventHorizon.Blazor.Interop false + + true - - + + diff --git a/EventHorizon.Blazor.Interop/EventHorizonBlazorInterop.cs b/EventHorizon.Blazor.Interop/EventHorizonBlazorInterop.cs index 640f315..0b75cba 100644 --- a/EventHorizon.Blazor.Interop/EventHorizonBlazorInterop.cs +++ b/EventHorizon.Blazor.Interop/EventHorizonBlazorInterop.cs @@ -1,724 +1,627 @@ -namespace EventHorizon.Blazor.Interop +namespace EventHorizon.Blazor.Interop; + +using System; +using System.Globalization; +using System.Runtime.Versioning; +using System.Threading.Tasks; +using Microsoft.JSInterop; + +/// +/// Contains generic abstraction around JavaScript making corresponding calls in JavaScript when used. +///
+///
+/// Make sure to append the attribute [JsonConverter(typeof(CachedEntityConverter))] to custom created. +///
+[SupportedOSPlatform("browser")] +public static class EventHorizonBlazorInterop { - using System; - using System.Globalization; - using System.Threading.Tasks; - using Microsoft.JSInterop; - using Microsoft.JSInterop.WebAssembly; + /// + /// This is the WebAssemblyJSRuntime cast from JSRuntime. + /// This provides very low level, not supported API that allow for high performances calls to the WASM layer. + /// + public static IJSInProcessRuntime RUNTIME => JSRuntime as IJSInProcessRuntime; + + /// + /// This is used to access the Client from .NET. + /// + public static IJSRuntime JSRuntime { get; set; } /// - /// Contains generic abstraction around JavaScript making corresponding calls in JavaScript when used. + /// Call a specific function on ICachedEntity implementation. + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static void Call(params object[] args) + { + RUNTIME.InvokeVoid("blazorInterop.call", args); + } + + /// + /// This will call a function on a passed in identifier, root starts at window. + ///
///
+ /// Identifier can be ///
- /// Make sure to append the attribute [JsonConverter(typeof(CachedEntityConverter))] to custom created. + /// Support: Primitive + /// + /// + /// + /// ( + /// "document.createElement", + /// [n...arguments] + /// ); + /// ]]> + /// + /// + /// ///
- public static class EventHorizonBlazorInterop + /// Primitive type should be returned. + /// The identifier and any arguments to passed in function all. + /// The result of the function call. + public static T Func(params object[] args) { - /// - /// This is the WebAssemblyJSRuntime cast from JSRuntime. - /// This provides very low level, not supported API that allow for high performances calls to the WASM layer. - /// - public static WebAssemblyJSRuntime RUNTIME => JSRuntime as WebAssemblyJSRuntime; - /// - /// This is used to access the Client from .NET. - /// - public static IJSRuntime JSRuntime { get; set; } + return RUNTIME.Invoke("blazorInterop.func", args); + } - /// - /// Call a specific function on ICachedEntity implementation. - /// - /// - /// - /// - /// - /// - /// - /// - /// - public static void Call( - params object[] args - ) - { - RUNTIME.InvokeVoid( - "blazorInterop.call", - args - ); - } + /// + /// This will call a function on a passed in identifier, root starts at window. + ///
+ ///
+ /// Identifier can be + ///
+ /// Support: Class + /// + /// + /// + /// ( + /// entity => new Vector3(entity), + /// "document.createElement", + /// [n...arguments] + /// ); + /// ]]> + /// + /// + /// + ///
+ /// The type of class to response with. + /// The builder used to create a class from, passes in CachedEntity from client. + /// The identifier and any arguments to passed in function all. + /// The result from classBuilder. + public static T FuncClass(Func classBuilder, params object[] args) + { + var cacheKey = RUNTIME.Invoke("blazorInterop.funcClass", args); + return classBuilder(new CachedEntity { ___guid = cacheKey }); + } - /// - /// This will call a function on a passed in identifier, root starts at window. - ///
- ///
- /// Identifier can be - ///
- /// Support: Primitive - /// - /// - /// - /// ( - /// "document.createElement", - /// [n...arguments] - /// ); - /// ]]> - /// - /// - /// - ///
- /// Primitive type should be returned. - /// The identifier and any arguments to passed in function all. - /// The result of the function call. - public static T Func( - params object[] args - ) - { - return RUNTIME.Invoke( - "blazorInterop.func", - args - ); - } + /// + /// This will call a function on a passed in identifier, root starts at window. + ///
+ ///
+ /// Identifier can be + ///
+ /// Support: Array of Primitives + /// + /// + /// + /// ( + /// "document.createElement", + /// [n...arguments] + /// ); + /// ]]> + /// + /// + /// + ///
+ /// Primitive type the array should be returned as. + /// The identifier and any arguments to passed in function all. + /// The primitive array result of the function call. + public static T[] FuncArray(params object[] args) + { + return RUNTIME.Invoke("blazorInterop.funcArray", args); + } - /// - /// This will call a function on a passed in identifier, root starts at window. - ///
- ///
- /// Identifier can be - ///
- /// Support: Class - /// - /// - /// - /// ( - /// entity => new Vector3(entity), - /// "document.createElement", - /// [n...arguments] - /// ); - /// ]]> - /// - /// - /// - ///
- /// The type of class to response with. - /// The builder used to create a class from, passes in CachedEntity from client. - /// The identifier and any arguments to passed in function all. - /// The result from classBuilder. - public static T FuncClass( - Func classBuilder, - params object[] args - ) + /// + /// This will call a function on a passed in identifier, root starts at window. + ///
+ ///
+ /// Identifier can be + ///
+ /// Support: Array of Classes + /// + /// + /// + /// ( + /// entity => new Vector3(entity), + /// "document.createElement", + /// [n...arguments] + /// ); + /// ]]> + /// + /// + /// + ///
+ /// The type of the Class the array should return. + /// Used to build the Classes in the Array, passes in CachedEntity from client. + /// The identifier and any arguments to passed in function all. + /// The Class array result of the function call. + public static T[] FuncArrayClass(Func classBuilder, params object[] args) + { + var results = RUNTIME.Invoke("blazorInterop.funcArrayClass", args); + var array = new T[results.Length]; + var index = 0; + foreach (var result in results) { - var cacheKey = RUNTIME.Invoke( - "blazorInterop.funcClass", - args - ); - return classBuilder(new CachedEntity { ___guid = cacheKey }); + array[index] = classBuilder(new CachedEntity { ___guid = result }); + index++; } - /// - /// This will call a function on a passed in identifier, root starts at window. - ///
- ///
- /// Identifier can be - ///
- /// Support: Array of Primitives - /// - /// - /// - /// ( - /// "document.createElement", - /// [n...arguments] - /// ); - /// ]]> - /// - /// - /// - ///
- /// Primitive type the array should be returned as. - /// The identifier and any arguments to passed in function all. - /// The primitive array result of the function call. - public static T[] FuncArray( - params object[] args - ) - { - return RUNTIME.Invoke( - "blazorInterop.funcArray", - args - ); - } + return array; + } - /// - /// This will call a function on a passed in identifier, root starts at window. - ///
- ///
- /// Identifier can be - ///
- /// Support: Array of Classes - /// - /// - /// - /// ( - /// entity => new Vector3(entity), - /// "document.createElement", - /// [n...arguments] - /// ); - /// ]]> - /// - /// - /// - ///
- /// The type of the Class the array should return. - /// Used to build the Classes in the Array, passes in CachedEntity from client. - /// The identifier and any arguments to passed in function all. - /// The Class array result of the function call. - public static T[] FuncArrayClass( - Func classBuilder, - params object[] args - ) + /// + /// This will return a primitive property value, root starts at window. + ///
+ ///
+ /// Identifier can be + ///
+ /// Support: Primitive + /// + /// + /// + /// ( + /// "document", + /// "nodeType" + /// ); + /// ]]> + /// + /// + /// + ///
+ /// Primitive type the result should be. + /// Property name from window or ICachedEntity.___guid. + /// Property to retrieve from root. + /// The primitive typed property from root. + public static T Get(string root, string prop) + { + var result = InternalInteropImport.Get(root, prop); + if (result == null) { - var results = RUNTIME.Invoke( - "blazorInterop.funcArrayClass", - args - ); - var array = new T[results.Length]; - var index = 0; - foreach (var result in results) - { - array[index] = classBuilder(new CachedEntity { ___guid = result }); - index++; - } - - return array; + return default; } - - /// - /// This will return a primitive property value, root starts at window. - ///
- ///
- /// Identifier can be - ///
- /// Support: Primitive - /// - /// - /// - /// ( - /// "document", - /// "nodeType" - /// ); - /// ]]> - /// - /// - /// - ///
- /// Primitive type the result should be. - /// Property name from window or ICachedEntity.___guid. - /// Property to retrieve from root. - /// The primitive typed property from root. - public static T Get( - string root, - string prop - ) - { - var result = ((IJSUnmarshalledRuntime)RUNTIME).InvokeUnmarshalled, string>( - "blazorInterop.get", - ValueTuple.Create( - root, - prop - ) - ); - if (result == null) - { - return default; - } - return (T)Convert.ChangeType( + return (T) + Convert.ChangeType( result, Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T), CultureInfo.InvariantCulture ); - } - - /// - /// This will return a Class property value, root starts at window. - ///
- /// On classBuilder call it will pass in the cached entity from the response. - ///
- ///
- /// Identifier can be - ///
- /// Support: Class - /// - /// - /// - /// ( - /// playerCachedEntity.___guid, - /// "position" - /// entity => new Vector3(entity), - /// ); - /// ]]> - /// - /// - /// - ///
- /// Class type the result should be. - /// Property name from window or ICachedEntity.___guid. - /// Property to retrieve from root. - /// Used to build the Class, passes in CachedEntity from client. - /// The class typed property from root. - public static T GetClass( - string root, - string prop, - Func classBuilder - ) - { - var result = ((IJSUnmarshalledRuntime)RUNTIME).InvokeUnmarshalled, string>( - "blazorInterop.getClass", - ValueTuple.Create( - root, - prop - ) - ); - - return classBuilder(new CachedEntity { ___guid = result }); - } - - /// - /// This will return a class array property value, root starts at window. - ///
- ///
- /// Identifier can be - ///
- /// Support: Array of Primitives - /// - /// - /// - /// ( - /// npcCachedEntity.___guid, - /// "pathToPlayer" - /// entity => new Vector3(entity), - /// ); - /// ]]> - /// - /// - /// - ///
- /// Class type the array result should be. - /// Property name from window or ICachedEntity.___guid. - /// Property to retrieve from root. - /// Used to build the Class, passes in CachedEntity from client. - /// The Class typed array property from root. - public static T[] GetArrayClass( - string root, - string prop, - Func classBuilder - ) - { - var results = RUNTIME.Invoke( - "blazorInterop.getArrayClassSlow", - root, - prop - ); - var array = new T[results.Length]; - var index = 0; - foreach (var result in results) - { - array[index] = classBuilder(new CachedEntity { ___guid = result }); - index++; - } + } - return array; - } + /// + /// This will return a Class property value, root starts at window. + ///
+ /// On classBuilder call it will pass in the cached entity from the response. + ///
+ ///
+ /// Identifier can be + ///
+ /// Support: Class + /// + /// + /// + /// ( + /// playerCachedEntity.___guid, + /// "position" + /// entity => new Vector3(entity), + /// ); + /// ]]> + /// + /// + /// + ///
+ /// Class type the result should be. + /// Property name from window or ICachedEntity.___guid. + /// Property to retrieve from root. + /// Used to build the Class, passes in CachedEntity from client. + /// The class typed property from root. + public static T GetClass(string root, string prop, Func classBuilder) + { + return classBuilder( + new CachedEntity { ___guid = InternalInteropImport.GetClass(root, prop) } + ); + } - /// - /// This will return a primitive array property value, root starts at window. - ///
- ///
- /// Identifier can be - ///
- /// Support: Array of Primitives - /// - /// - /// - /// ( - /// playerCachedEntity.___guid, - /// "tags" - /// ); - /// ]]> - /// - /// - /// - ///
- /// Primitive type the array result should be. - /// Property name from window or ICachedEntity.___guid. - /// Property to retrieve from root. - /// The primitive typed array property from root. - public static T[] GetArray( - string root, - string prop - ) + /// + /// This will return a class array property value, root starts at window. + ///
+ ///
+ /// Identifier can be + ///
+ /// Support: Array of Primitives + /// + /// + /// + /// ( + /// npcCachedEntity.___guid, + /// "pathToPlayer" + /// entity => new Vector3(entity), + /// ); + /// ]]> + /// + /// + /// + ///
+ /// Class type the array result should be. + /// Property name from window or ICachedEntity.___guid. + /// Property to retrieve from root. + /// Used to build the Class, passes in CachedEntity from client. + /// The Class typed array property from root. + public static T[] GetArrayClass( + string root, + string prop, + Func classBuilder + ) + { + var results = RUNTIME.Invoke("blazorInterop.getArrayClass", root, prop); + var array = new T[results.Length]; + var index = 0; + foreach (var result in results) { - return RUNTIME.Invoke( - "blazorInterop.getArraySlow", - root, - prop - ); + array[index] = classBuilder(new CachedEntity { ___guid = result }); + index++; } - /// - /// This will call 'new' on the class/function identifier passed in, root starts at window. - ///
- ///
- /// Identifier: A '.' separated string of identifiers . - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - ///
- /// The identifier and any arguments to pass into constructor. - /// Client side cached entity created on client. - public static ICachedEntity New( - params object[] args - ) - { - var cacheRef = RUNTIME.Invoke( - "blazorInterop.new", - args - ); - return new CachedEntity { ___guid = cacheRef }; - } + return array; + } - /// - /// Create a JavaScript "Function" on the client based on the script, cached based on methodName. - ///
- ///
- /// Scripts are cached client-side, so they are not built every time it is called. - ///
- /// Arguments can be passed to script, they can be accessed from the script body using '$args'. - /// - ///
- /// The name of the method, used for caching. - /// The valid JavaScript that should be used to create the script. - /// The JSON object to be passed into the script when called. - /// Allows for Async running - public static ValueTask RunScript( - string methodName, - string script, - object args - ) - { - return JSRuntime.InvokeVoidAsync( - "blazorInterop.runScript", - new JavaScriptMethodRunner - { - MethodName = methodName, - Script = script, - Args = args - } - ); - } + /// + /// This will return a primitive array property value, root starts at window. + ///
+ ///
+ /// Identifier can be + ///
+ /// Support: Array of Primitives + /// + /// + /// + /// ( + /// playerCachedEntity.___guid, + /// "tags" + /// ); + /// ]]> + /// + /// + /// + ///
+ /// Primitive type the array result should be. + /// Property name from window or ICachedEntity.___guid. + /// Property to retrieve from root. + /// The primitive typed array property from root. + public static T[] GetArray(string root, string prop) + { + return RUNTIME.Invoke("blazorInterop.getArraySlow", root, prop); + } - /// - /// This will call the passed in property funcCallbackName on the entity with a callback function. - ///
- /// On callback calls it will cache the objects and pass them through the CachedEntityConverter to marshal the arguments. - /// - ///
- /// The type of class creating the callback. - /// The entity this callback should be attached to. - /// The name of the function that the callback will be passed to. - /// The method that should be called on the invokableReference during the callback trigger. - /// The reference object that the referenceMethod should call on. - /// This method is async. - public static ValueTask FuncCallback( - ICachedEntity entity, - string funcCallbackName, - string referenceMethod, - DotNetObjectReference invokableReference - ) where T : class - { - return JSRuntime.InvokeVoidAsync( - "blazorInterop.funcCallback", - entity.___guid, - funcCallbackName, - referenceMethod, - invokableReference - ); - } + /// + /// This will call 'new' on the class/function identifier passed in, root starts at window. + ///
+ ///
+ /// Identifier: A '.' separated string of identifiers . + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + ///
+ /// The identifier and any arguments to pass into constructor. + /// Client side cached entity created on client. + public static ICachedEntity New(params object[] args) + { + var cacheRef = RUNTIME.Invoke("blazorInterop.new", args); + return new CachedEntity { ___guid = cacheRef }; + } + /// + /// Create a JavaScript "Function" on the client based on the script, cached based on methodName. + ///
+ ///
+ /// Scripts are cached client-side, so they are not built every time it is called. + ///
+ /// Arguments can be passed to script, they can be accessed from the script body using '$args'. + /// + ///
+ /// The name of the method, used for caching. + /// The valid JavaScript that should be used to create the script. + /// The JSON object to be passed into the script when called. + /// Allows for Async running + public static ValueTask RunScript(string methodName, string script, object args) + { + return JSRuntime.InvokeVoidAsync( + "blazorInterop.runScript", + new JavaScriptMethodRunner + { + MethodName = methodName, + Script = script, + Args = args + } + ); + } - /// - /// This will call the identifier, from window, and attached a callback function to it. - ///
- ///
- /// Does NOT take into account the identifier could be an CachedEntity. - ///
- ///
- /// Identifier: A '.' separated string of identifiers. - /// - ///
- /// The location on window that should be called. - /// This should be the dll assembly name that the referenceCallback is located. - /// The method that should be called when the identifier callback is triggered. - /// This method is async. - public static ValueTask AssemblyFuncCallback( - string identifier, - string assemblyName, - string referenceCallback - ) - { - return JSRuntime.InvokeVoidAsync( - "blazorInterop.assemblyFuncCallback", - identifier, - assemblyName, - referenceCallback - ); - } + /// + /// This will call the passed in property funcCallbackName on the entity with a callback function. + ///
+ /// On callback calls it will cache the objects and pass them through the CachedEntityConverter to marshal the arguments. + /// + ///
+ /// The type of class creating the callback. + /// The entity this callback should be attached to. + /// The name of the function that the callback will be passed to. + /// The method that should be called on the invokableReference during the callback trigger. + /// The reference object that the referenceMethod should call on. + /// This method is async. + public static ValueTask FuncCallback( + ICachedEntity entity, + string funcCallbackName, + string referenceMethod, + DotNetObjectReference invokableReference + ) + where T : class + { + return JSRuntime.InvokeVoidAsync( + "blazorInterop.funcCallback", + entity.___guid, + funcCallbackName, + referenceMethod, + invokableReference + ); + } - /// - /// This will take the value and set it on the root property. - /// - /// - /// Property name from window or . - /// The property from root that the value should be set on. - /// The value that should be set on the root identifier property. - public static void Set( - string root, - string identifier, - object value - ) - { - JSRuntime.InvokeVoidAsync( - "blazorInterop.set", - root, - identifier, - value - ); - } + /// + /// This will call the identifier, from window, and attached a callback function to it. + ///
+ ///
+ /// Does NOT take into account the identifier could be an CachedEntity. + ///
+ ///
+ /// Identifier: A '.' separated string of identifiers. + /// + ///
+ /// The location on window that should be called. + /// This should be the dll assembly name that the referenceCallback is located. + /// The method that should be called when the identifier callback is triggered. + /// This method is async. + public static ValueTask AssemblyFuncCallback( + string identifier, + string assemblyName, + string referenceCallback + ) + { + return JSRuntime.InvokeVoidAsync( + "blazorInterop.assemblyFuncCallback", + identifier, + assemblyName, + referenceCallback + ); + } - /// - /// This takes in a and property adding the returned value into the cache on the client. - /// - /// - /// The on the client. - /// The property on the root the value should be from. - /// The cache identifier of the prop value. - public static ICachedEntity CacheEntity( - string identifier, - string prop - ) - { - var cacheRef = RUNTIME.Invoke( - "blazorInterop.cacheEntity", - identifier, - prop - ); + /// + /// This will take the value and set it on the root property. + /// + /// + /// Property name from window or . + /// The property from root that the value should be set on. + /// The value that should be set on the root identifier property. + public static void Set(string root, string identifier, object value) + { + RUNTIME.InvokeVoid("blazorInterop.set", root, identifier, value); + } - return new CachedEntity { ___guid = cacheRef }; - } + /// + /// This takes in a and property adding the returned value into the cache on the client. + /// + /// + /// The on the client. + /// The property on the root the value should be from. + /// The cache identifier of the prop value. + public static ICachedEntity CacheEntity(string identifier, string prop) + { + var cacheRef = RUNTIME.Invoke("blazorInterop.cacheEntity", identifier, prop); + return new CachedEntity { ___guid = cacheRef }; + } - /// - /// This will call a promise based on the identifier, returning a primitive. - ///
- /// Root starts at 'window' - ///
- ///
- /// Identifier can be - ///
- /// Support: Primitive - /// - /// - /// - /// ( - /// "document.createElement", - /// [n...arguments] - /// ); - /// ]]> - /// - /// - /// - ///
- /// Primitive Type of Result. - /// The identifier and any arguments to passed into Promise function. - /// The result of the Promise call. - public static ValueTask Task( - params object[] args - ) - { - return RUNTIME.InvokeAsync( - "blazorInterop.task", - args - ); - } + /// + /// This will call a promise based on the identifier, returning a primitive. + ///
+ /// Root starts at 'window' + ///
+ ///
+ /// Identifier can be + ///
+ /// Support: Primitive + /// + /// + /// + /// ( + /// "document.createElement", + /// [n...arguments] + /// ); + /// ]]> + /// + /// + /// + ///
+ /// Primitive Type of Result. + /// The identifier and any arguments to passed into Promise function. + /// The result of the Promise call. + public static ValueTask Task(params object[] args) + { + return RUNTIME.InvokeAsync("blazorInterop.task", args); + } - /// - /// This will call a promise based on the identifier, returning a Class. - ///
- /// Root starts at 'window' - ///
- ///
- /// Identifier can be - ///
- /// Support: Class - /// - /// - /// - /// ( - /// entity => new Vector3(entity), - /// "document.createElement", - /// [n...arguments] - /// ); - /// ]]> - /// - /// - /// - ///
- /// Class Type of Result. - /// The builder used to create a class from, passes in CachedEntity from client. - /// The identifier and any arguments to passed in Promise function call. - /// The result of the Promise call. - public static async ValueTask TaskClass( - Func classBuilder, - params object[] args - ) - { - var cacheKey = await RUNTIME.InvokeAsync( - "blazorInterop.taskClass", - args - ); - return classBuilder(new CachedEntity { ___guid = cacheKey }); - } + /// + /// This will call a promise based on the identifier, returning a Class. + ///
+ /// Root starts at 'window' + ///
+ ///
+ /// Identifier can be + ///
+ /// Support: Class + /// + /// + /// + /// ( + /// entity => new Vector3(entity), + /// "document.createElement", + /// [n...arguments] + /// ); + /// ]]> + /// + /// + /// + ///
+ /// Class Type of Result. + /// The builder used to create a class from, passes in CachedEntity from client. + /// The identifier and any arguments to passed in Promise function call. + /// The result of the Promise call. + public static async ValueTask TaskClass( + Func classBuilder, + params object[] args + ) + { + var cacheKey = await RUNTIME.InvokeAsync("blazorInterop.taskClass", args); + return classBuilder(new CachedEntity { ___guid = cacheKey }); + } - /// - /// This will call a promise based on the identifier, returning an Array of Primitives. - ///
- /// Root starts at 'window' - ///
- ///
- /// Identifier can be - ///
- /// Support: Array of Primitives - /// - /// - /// - /// ( - /// "document.createElement", - /// [n...arguments] - /// ); - /// ]]> - /// - /// - /// - ///
- /// Primitive Type of Result. - /// The identifier and any arguments to passed into the Promise function call. - /// The Array result of the Promise call. - public static ValueTask TaskArray( - params object[] args - ) - { - return RUNTIME.InvokeAsync( - "blazorInterop.taskArray", - args - ); - } + /// + /// This will call a promise based on the identifier, returning an Array of Primitives. + ///
+ /// Root starts at 'window' + ///
+ ///
+ /// Identifier can be + ///
+ /// Support: Array of Primitives + /// + /// + /// + /// ( + /// "document.createElement", + /// [n...arguments] + /// ); + /// ]]> + /// + /// + /// + ///
+ /// Primitive Type of Result. + /// The identifier and any arguments to passed into the Promise function call. + /// The Array result of the Promise call. + public static ValueTask TaskArray(params object[] args) + { + return RUNTIME.InvokeAsync("blazorInterop.taskArray", args); + } - /// - /// This will call a promise based on the identifier, returning an Array of Classes. - ///
- /// Root starts at 'window' - ///
- ///
- /// Identifier can be - ///
- /// Support: Array of Classes - /// - /// - /// - /// ( - /// entity => new Vector3(entity), - /// "document.createElement", - /// [n...arguments] - /// ); - /// ]]> - /// - /// - /// - ///
- /// Class Type of Result. - /// Used to build the Classes in the Array, passes in CachedEntity from client. - /// The identifier and any arguments to passed into the Promise function call. - /// The Class Array result of the Promise call. - public static async ValueTask TaskArrayClass( - Func classBuilder, - params object[] args - ) + /// + /// This will call a promise based on the identifier, returning an Array of Classes. + ///
+ /// Root starts at 'window' + ///
+ ///
+ /// Identifier can be + ///
+ /// Support: Array of Classes + /// + /// + /// + /// ( + /// entity => new Vector3(entity), + /// "document.createElement", + /// [n...arguments] + /// ); + /// ]]> + /// + /// + /// + ///
+ /// Class Type of Result. + /// Used to build the Classes in the Array, passes in CachedEntity from client. + /// The identifier and any arguments to passed into the Promise function call. + /// The Class Array result of the Promise call. + public static async ValueTask TaskArrayClass( + Func classBuilder, + params object[] args + ) + { + var results = await RUNTIME.InvokeAsync("blazorInterop.taskArrayClass", args); + var array = new T[results.Length]; + var index = 0; + foreach (var result in results) { - var results = await RUNTIME.InvokeAsync( - "blazorInterop.taskArrayClass", - args - ); - var array = new T[results.Length]; - var index = 0; - foreach (var result in results) - { - array[index] = classBuilder(new CachedEntity { ___guid = result }); - index++; - } - - return array; + array[index] = classBuilder(new CachedEntity { ___guid = result }); + index++; } - /// - /// Removes an entity from the cache, allowing the JS runtime to garbage collect it. - /// This method is called automatically by any objects derived from - /// upon finalization. - /// - /// Identifier is a - public static void RemoveEntity(string identifier) - { - RUNTIME.InvokeVoid("blazorInterop.removeEntity", identifier); - } + return array; } - internal struct JavaScriptMethodRunner + /// + /// Removes an entity from the cache, allowing the JS runtime to garbage collect it. + /// This method is called automatically by any objects derived from + /// upon finalization. + /// + /// Identifier is a + public static void RemoveEntity(string identifier) { - public string MethodName { get; set; } - public string Script { get; set; } - public object Args { get; set; } + RUNTIME.InvokeVoid("blazorInterop.removeEntity", identifier); } } + +internal struct JavaScriptMethodRunner +{ + public string MethodName { get; set; } + public string Script { get; set; } + public object Args { get; set; } +} diff --git a/EventHorizon.Blazor.Interop/InternalInteropImport.cs b/EventHorizon.Blazor.Interop/InternalInteropImport.cs new file mode 100644 index 0000000..8a7e446 --- /dev/null +++ b/EventHorizon.Blazor.Interop/InternalInteropImport.cs @@ -0,0 +1,15 @@ +namespace EventHorizon.Blazor.Interop; + +using System; +using System.Runtime.InteropServices.JavaScript; +using System.Runtime.Versioning; + +[SupportedOSPlatform("browser")] +internal partial class InternalInteropImport +{ + [JSImport("globalThis.blazorInterop.get")] + internal static partial string Get(string root, string prop); + + [JSImport("globalThis.blazorInterop.getClass")] + internal static partial string GetClass(string root, string prop); +} diff --git a/EventHorizon.Blazor.Interop/wwwroot/interop-bridge.js b/EventHorizon.Blazor.Interop/wwwroot/interop-bridge.js index 34403e5..252d4ff 100644 --- a/EventHorizon.Blazor.Interop/wwwroot/interop-bridge.js +++ b/EventHorizon.Blazor.Interop/wwwroot/interop-bridge.js @@ -8,7 +8,10 @@ argumentCache, methodCache, }; - const CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".split(""); + const CHARS = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".split( + "" + ); const guid = () => { var chars = CHARS, uuid = new Array(36), @@ -20,7 +23,9 @@ } else if (i === 14) { uuid[i] = "4"; } else { - if (rnd <= 0x02) { rnd = (0x2000000 + Math.random() * 0x1000000) | 0; } + if (rnd <= 0x02) { + rnd = (0x2000000 + Math.random() * 0x1000000) | 0; + } r = rnd & 0xf; rnd = rnd >> 4; uuid[i] = chars[i === 19 ? (r & 0x3) | 0x8 : r]; @@ -49,7 +54,7 @@ /** * Check argument for existing in argumentCache and if actionResultCallbackType and if actionCallbackType. * Returns argValue if not part argumentCache or actionCallbackType. - * + * * @param {any} argValue */ const convertArg = (argValue) => { @@ -58,24 +63,36 @@ } if (argValue[cacheKey] && argumentCache.has(argValue[cacheKey])) { return argumentCache.get(argValue[cacheKey]); - } else if (argValue[typeKey] && argValue[typeKey] === actionResultCallbackType) { + } else if ( + argValue[typeKey] && + argValue[typeKey] === actionResultCallbackType + ) { const invokableReference = argValue["invokableReference"]; const method = argValue["method"]; return function () { - return invokableReference.invokeMethod(method, ...convertCallbackArguments(arguments)); + return invokableReference.invokeMethod( + method, + ...convertCallbackArguments(arguments) + ); }; - } else if (argValue[typeKey] && argValue[typeKey] === actionCallbackType) { + } else if ( + argValue[typeKey] && + argValue[typeKey] === actionCallbackType + ) { const invokableReference = argValue["invokableReference"]; const method = argValue["method"]; return async function () { - return await invokableReference.invokeMethodAsync(method, ...convertCallbackArguments(arguments)); + return await invokableReference.invokeMethodAsync( + method, + ...convertCallbackArguments(arguments) + ); }; } return argValue; }; /** * Loop through all the argumentArray items and convert the args to usable references. - * + * * @param {any} argumentArray */ const convertArgs = (argumentArray) => { @@ -89,12 +106,17 @@ /** * Convert argument to cached entity, introspects and converts children as well. - * + * * @param {any} args * @param {any} arg */ const constructArgument = (args, arg) => { - if (arg && typeof (arg) === "object" && !arg[cacheKey] && !Array.isArray(arg)) { + if ( + arg && + typeof arg === "object" && + !arg[cacheKey] && + !Array.isArray(arg) + ) { // Object literal: { prop: "hi", prop2: { ___type: "action_callback" } } const newArg = {}; for (const key in arg) { @@ -110,7 +132,7 @@ } } args.push(newArg); - } else if (arg && typeof (arg) === "object" && Array.isArray(arg)) { + } else if (arg && typeof arg === "object" && Array.isArray(arg)) { // Array: [ { ___type: "action_callback" } ] const newArg = []; for (const item of arg) { @@ -130,9 +152,7 @@ const convertCallbackArguments = (callbackArguments) => { const args = []; for (var arg of callbackArguments) { - if (typeof (arg) === "object" - && !Array.isArray(arg) - ) { + if (typeof arg === "object" && !Array.isArray(arg)) { args.push(cacheEntity(arg)); } else if (Array.isArray(arg)) { args.push(arg.map(cacheEntity)); @@ -150,21 +170,21 @@ let numStr = String(num); if (Math.abs(num) < 1.0) { - let e = parseInt(num.toString().split('e-')[1]); + let e = parseInt(num.toString().split("e-")[1]); if (e) { let negative = num < 0; - if (negative) num *= -1 + if (negative) num *= -1; num *= Math.pow(10, e - 1); - numStr = '0.' + (new Array(e)).join('0') + num.toString().substring(2); + numStr = + "0." + new Array(e).join("0") + num.toString().substring(2); if (negative) numStr = "-" + numStr; } - } - else { - let e = parseInt(num.toString().split('+')[1]); + } else { + let e = parseInt(num.toString().split("+")[1]); if (e > 20) { e -= 20; num /= Math.pow(10, e); - numStr = num.toString() + (new Array(e + 1)).join('0'); + numStr = num.toString() + new Array(e + 1).join("0"); } } @@ -178,14 +198,11 @@ window["blazorInterop"] = { /** * This will call a function on a cached object. - * args = Tuple - * Tuble[0] = Root - Property Name from window or Cached Entity GUID - * Tuble[1] = Identitifer - Property to get from Root + * @param root - Property Name from window or Cached Entity GUID + * @param identifier - Property to get from Root **/ - get: function (args) { + get: function (root, identifier) { try { - var root = Blazor.platform.readStringField(args); - var identifier = Blazor.platform.readStringField(args, 4); identifier = identifier.split("."); let value = window[root]; if (argumentCache.has(root)) { @@ -198,24 +215,25 @@ value = value[identifier[i]]; } - if (typeof (value) === "number") { - value = numberToString(value); + if (typeof value === "number") { + return numberToString(value); + } + else if (!value) { + return null; } - return BINDING.js_to_mono_obj(value); + + return value.toString(); } catch (ex) { - console.log("error", { ex, args }); + console.log("error", { ex, root, identifier }); } }, /** * This will call a function on a cached object. - * args = Tuple - * Tuble[0] = Root - Property Name from window or Cached Entity GUID - * Tuble[1] = Identitifer - Property to get from Root + * @param root - Property Name from window or Cached Entity GUID + * @param identifier - Property to get from Root **/ - getClass: function (args) { + getClass: function (root, identifier) { try { - var root = Blazor.platform.readStringField(args); - var identifier = Blazor.platform.readStringField(args, 4); identifier = identifier.split("."); let value = window[root]; if (argumentCache.has(root)) { @@ -234,53 +252,17 @@ argumentCache.set(newCacheKey, value); } - return BINDING.js_to_mono_obj(value[cacheKey]); - } catch (ex) { - console.log("error", { ex, args }); - } - }, - /** - * This will call a function on a cached object. - * args = Tuple - * Tuble[0] = Root - Property Name from window or Cached Entity GUID - * Tuble[1] = Identitifer - Property to get from Root - **/ - getArrayClass: function (args) { - try { - var root = Blazor.platform.readStringField(args); - var identifier = Blazor.platform.readStringField(args, 4); - identifier = identifier.split("."); - let values = window[root]; - if (argumentCache.has(root)) { - values = argumentCache.get(root); - } - for (var i = 0; i < identifier.length; i++) { - values = values[identifier[i]]; - } - const result = []; - for (var value of values) { - if (!argumentCache.has(value[cacheKey])) { - // Add to cache - const newCacheKey = guid(); - value[cacheKey] = newCacheKey; - argumentCache.set(newCacheKey, value); - } - result.push(value[cacheKey]); - } - - return BINDING.js_to_mono_obj(result); + return value[cacheKey]; } catch (ex) { console.log("error", { ex, args }); - return BINDING.js_to_mono_obj([]); } }, /** * This will call a function on a cached object. - * args = Tuple - * Tuble[0] = Root - Property Name from window or Cached Entity GUID - * Tuble[1] = Identitifer - Property to get from Root + * @param root - Property Name from window or Cached Entity GUID + * @param identifier - Property to get from Root **/ - getArrayClassSlow: function (root, identifier) { + getArrayClass: function (root, identifier) { try { identifier = identifier.split("."); let values = window[root]; @@ -328,7 +310,7 @@ }, /** * This will set a the passed in value on the identifier starting at the root. - * + * * @param root Property Name from window or Cached Entity GUID * @param identifier Property to get from Root * @param value The value to set at the root.identifier @@ -366,7 +348,11 @@ var args = []; for (var i = 2; i < arguments.length; i++) { var arg = arguments[i]; - if (arg && arg[cacheKey] && argumentCache.has(arg[cacheKey])) { + if ( + arg && + arg[cacheKey] && + argumentCache.has(arg[cacheKey]) + ) { args.push(argumentCache.get(arg[cacheKey])); } else { args.push(arg); @@ -449,8 +435,9 @@ createNew = createNew[identifier[i]]; } var newObject = createNew.call(context, ...args); - if (typeof (newObject) === "object" - && !Array.isArray(newObject) + if ( + typeof newObject === "object" && + !Array.isArray(newObject) ) { if (!argumentCache.has(newObject[cacheKey])) { // Add to cache @@ -585,8 +572,9 @@ obj = obj[identifier[i]]; } var newObject = await obj.call(context, ...args); - if (typeof (newObject) === "object" - && !Array.isArray(newObject) + if ( + typeof newObject === "object" && + !Array.isArray(newObject) ) { if (!argumentCache.has(newObject[cacheKey])) { // Add to cache @@ -686,14 +674,14 @@ "$args", methodRunner.script ); - methodCache.set( - methodRunner.methodName, - script - ); + methodCache.set(methodRunner.methodName, script); } - script({ - argumentCache, - }, methodRunner.args); + script( + { + argumentCache, + }, + methodRunner.args + ); }, /** * This will create a callback function and trigger the args @@ -708,17 +696,16 @@ const cachedEntity = argumentCache.get(entity); cachedEntity[funcCallbackName](function () { - invokableReference.invokeMethodAsync(referenceMethod, ...convertCallbackArguments(arguments)); + invokableReference.invokeMethodAsync( + referenceMethod, + ...convertCallbackArguments(arguments) + ); }); }, /** * This will create a callback function and trigger the assembly callback **/ - assemblyFuncCallback: ( - identifier, - assemblyName, - referenceCallback - ) => { + assemblyFuncCallback: (identifier, assemblyName, referenceCallback) => { var identifier = identifier.split("."); var func = window[identifier[0]]; for (var i = 1; i < identifier.length; i++) { @@ -726,16 +713,13 @@ } func(function (/* TODO: Support passing back props */) { - DotNet.invokeMethodAsync(assemblyName, referenceCallback) + DotNet.invokeMethodAsync(assemblyName, referenceCallback); }); }, /** * This will create a cachedEntity from the prop on the passed in entity. **/ - cacheEntity: ( - identifier, - prop - ) => { + cacheEntity: (identifier, prop) => { const cachedEntity = argumentCache.get(identifier); var newObject = cachedEntity[prop]; newObject[cacheKey] = guid(); diff --git a/entry.ps1 b/entry.ps1 new file mode 100644 index 0000000..c20a88e --- /dev/null +++ b/entry.ps1 @@ -0,0 +1,51 @@ +param( + [string] + [ValidateSet( + "clean", + "restore", + "build", + "run", + "format", + "test", + "publish", + "serve:publish", + "pre" + )] + $Command, + [string] $Configuration = "Release", + [string] $Filter = "" +) + +$sampleProject = "./EventHorizon.Blazor.Interop.Sample/EventHorizon.Blazor.Interop.Sample.csproj" + +switch ($Command) { + "clean" { + dotnet clean + } + "restore" { + dotnet restore --no-cache + } + "build" { + dotnet build --no-incremental --no-cache + } + "run" { + dotnet run --project $sampleProject + } + "format" { + dotnet csharpier . + } + "test" { + echo "No Tests to Run" + } + "publish" { + dotnet publish -c $Configuration -o ./published $sampleProject + } + "serve:publish" { + ./entry.ps1 publish + + dotnet serve -d="./published/wwwroot" + } + Default { + Write-Output "Invalid Command" + } +}