lostbeard / spawndev.blazorjs Goto Github PK
View Code? Open in Web Editor NEWFull Blazor WebAssembly and Javascript Interop with multithreading via WebWorkers
Home Page: https://blazorjs.spawndev.com
License: MIT License
Full Blazor WebAssembly and Javascript Interop with multithreading via WebWorkers
Home Page: https://blazorjs.spawndev.com
License: MIT License
Hello,
First of all, thank you for making such an awesome library. I was disappointed by the lack of .NET 7 support in BlazorWorker, so I'm glad to have stumbled upon yours.
I am trying to utilize web workers to execute some long running tasks (running dynamically compiled code / unit tests), and some of them may run infinitely long. Which is why I am searching for some way to terminate tasks after a certain period of time.
Using BlazorJS.WebWorkers I was able to offload the compilation and execution of the tests to a WebWorker, so the browser UI no longer hangs. And by chaining WaitAsync
to the Task that is returned by the WebWorker and awaiting that, it will throw a TimeOutException when the Task exceeds the timeout that has been set. All good so far!
However, the WebWorker continues executing the Task until completion, and I'd like for a way to stop it from doing that..
I do not control all the inner workings of my test runner, so I am unable to e.g. send over a CancellationToken and check that continuously. Do you have any idea how to achieve this?
Here's a code snippet for reference:
public async Task CompileAndRun()
{
var code = await Editor.GetValue();
_webWorker ??= await WebWorkerService.GetWebWorker();
var runner = _webWorker.GetService<ITestWorker>();
try
{
var result = await runner
.RunTests(code)
.WaitAsync(TimeSpan.FromSeconds(5));
if (result.TestCount == 0)
{
await Alert("An unexpected error occurred");
return;
}
if (result.FailedCount > 0)
{
await Alert("One or more tests failed");
}
else
{
await Alert("All tests passed, nice!");
}
}
catch (TimeoutException e)
{
await Alert("Test suite timed out");
}
finally
{
_webWorker?.Dispose();
_webWorker = null;
// In case of timeout, WebWorker is still doing work at this point..
}
}
Describe the bug
When trying to run a method in a service from a worker, the following debug messages and error comes up, the method does not run.
Got WebWorker instance Id 840b320e-8d30-4bb1-8998-3288312843a7
blazor.webassembly.js:1 Got ISyncService from _webWorkerService.TaskPool.GetService<ISyncService>() instance Id 840b320e-8d30-4bb1-8998-3288312843a7
blazor.webassembly.js:1 [ERROR] FATAL UNHANDLED EXCEPTION: System.StackOverflowException: The requested operation caused a stack overflow.
Dt @ blazor.webassembly.js:1
blazor.webassembly.js:1 at uint.TryConvertFromTruncating<uint16> (uint16,uint&) [0x0016b] in <7938b2e668744d1ab43cf38633aa3fac>:0
blazor.webassembly.js:1 at uint.CreateTruncating<uint16> (uint16) [0x0002c] in <7938b2e668744d1ab43cf38633aa3fac>:0
blazor.webassembly.js:1 at System.Text.Ascii.ChangeCase<uint16, uint16, System.Text.Ascii/ToLowerConversion> (uint16*,uint16*,uintptr) [0x00318] in <7938b2e668744d1ab43cf38633aa3fac>:0
blazor.webassembly.js:1 at System.Text.Ascii.ChangeCase<uint16, uint16, System.Text.Ascii/ToLowerConversion> (System.ReadOnlySpan`1<uint16>,System.Span`1<uint16>,int&) [0x00062] in <7938b2e668744d1ab43cf38633aa3fac>:0
blazor.webassembly.js:1 at System.Text.Ascii.ToLower (System.ReadOnlySpan`1<char>,System.Span`1<char>,int&) [0x0000d] in <7938b2e668744d1ab43cf38633aa3fac>:0
blazor.webassembly.js:1 at System.Globalization.TextInfo.ChangeCaseCommon<System.Globalization.TextInfo/ToLowerConversion> (System.ReadOnlySpan`1<char>,System.Span`1<char>) [0x00035] in <7938b2e668744d1ab43cf38633aa3fac>:0
blazor.webassembly.js:1 at System.Globalization.TextInfo.ChangeCaseCommon<System.Globalization.TextInfo/ToLowerConversion> (string) [0x00119] in <7938b2e668744d1ab43cf38633aa3fac>:0
blazor.webassembly.js:1 at System.Globalization.TextInfo.ToLower (string) [0x0001b] in <7938b2e668744d1ab43cf38633aa3fac>:0
blazor.webassembly.js:1 at string.ToLowerInvariant () [0x00006] in <7938b2e668744d1ab43cf38633aa3fac>:0
blazor.webassembly.js:1 at SpawnDev.BlazorJS.WebWorkers.TypeConversionInfo.GetPropertyJSName (System.Reflection.PropertyInfo) [0x00007] in D:\users\tj\Projects\SpawnDev.BlazorJS\SpawnDev.BlazorJS\SpawnDev.BlazorJS.WebWorkers\TypeConversionInfo.cs:54
Dt @ blazor.webassembly.js:1
blazor.webassembly.js:1 at SpawnDev.BlazorJS.WebWorkers.TypeConversionInfo..ctor (System.Type) [0x00338] in D:\users\tj\Projects\SpawnDev.BlazorJS\SpawnDev.BlazorJS\SpawnDev.BlazorJS.WebWorkers\TypeConversionInfo.cs:177
blazor.webassembly.js:1 at SpawnDev.BlazorJS.WebWorkers.TypeConversionInfo.GetTypeConversionInfo (System.Type) [0x00012] in D:\users\tj\Projects\SpawnDev.BlazorJS\SpawnDev.BlazorJS\SpawnDev.BlazorJS.WebWorkers\TypeConversionInfo.cs:453
blazor.webassembly.js:1 at SpawnDev.BlazorJS.WebWorkers.TypeConversionInfo..ctor (System.Type) [0x00350] in D:\users\tj\Projects\SpawnDev.BlazorJS\SpawnDev.BlazorJS\SpawnDev.BlazorJS.WebWorkers\TypeConversionInfo.cs:179
blazor.webassembly.js:1 at SpawnDev.BlazorJS.WebWorkers.TypeConversionInfo.GetTypeConversionInfo (System.Type) [0x00012] in D:\users\tj\Projects\SpawnDev.BlazorJS\SpawnDev.BlazorJS\SpawnDev.BlazorJS.WebWorkers\TypeConversionInfo.cs:453
The last few lines are repeated many times, maybe a few 100's (see screenshot), then:
blazor.webassembly.js:1
(null)
blazor.webassembly.js:1
(null)
blazor.webassembly.js:1
program exited (with status: 1), but keepRuntimeAlive() is set (counter=1) due to an async operation, so halting execution but not exiting the runtime or preventing further async execution (you can use emscripten_force_exit, if you want to force a true shutdown)
SpawnDev.BlazorJS.lib.module.js:305
Callback invokeMethod error:
(2) ['Invoke', {…}]
null
ExitStatus {name: 'ExitStatus', message: 'Program terminated with exit(1)', status: 1}
blazor.webassembly.js:1
(null)
blazor.webassembly.js:1
Unhandled Exception:
blazor.webassembly.js:1
StackOverflowException
blazor.webassembly.js:1
[ERROR] FATAL UNHANDLED EXCEPTION: System.StackOverflowException: The requested operation caused a stack overflow.
blazor.webassembly.js:1
at Microsoft.AspNetCore.Components.Reflection.PropertySetter.CallPropertySetter<MudBlazor.MudAppBar, int> (System.Action`2<MudBlazor.MudAppBar, int>,object,object) [0x0002b] in <b6ecada0543848beb43c97c645b55df3>:0
blazor.webassembly.js:1
at Microsoft.AspNetCore.Components.Reflection.PropertySetter.SetValue (object,object) [0x00008] in <b6ecada0543848beb43c97c645b55df3>:0
The error happens in this method (note the debug messages in the error):
public async Task<List<object>> GetToListAsync(Type entityType, CancellationToken cancellationToken = default, bool doSync = true)
{
if (doSync)
{
Console.WriteLine("Getting ISyncService from _webWorkerService.TaskPool.GetService<ISyncService>()");
var webWorker = await _webWorkerService.GetWebWorker();
Console.WriteLine($"Got WebWorker instance Id {webWorker?.LocalInfo.InstanceId}");
var service = webWorker.GetService<ISyncService>();
Console.WriteLine($"Got ISyncService from _webWorkerService.TaskPool.GetService<ISyncService>() instance Id {_webWorkerService.InstanceId}");
await service.SyncRemoteWithLocal(entityType, cancellationToken);
Console.WriteLine("Finished SyncRemoteWithLocal using _webWorkerService.TaskPool.GetService<ISyncService>()");
}
return await _unitOfWork.GetListAsync(entityType,cancellationToken);
}
The first "Console.WriteLine" is never show in the browser's console:
public async Task SyncRemoteWithLocal(Type entityType, CancellationToken cancellationToken = default, int pageNumber = 0)
{
Console.WriteLine($"SyncService.SyncRemoteWithLocal({entityType.Name}) started");
_listenerService.TriggerNotifySyncStarted(entityType);
var localEntitiesSet = _unitOfWork.Get(entityType);
var localEntities = await localEntitiesSet.Cast<object>().ToListAsync(cancellationToken);
var remoteCount = await _httpClient.GetCount(entityType, cancellationToken);
_listenerService.TriggerSyncProgress(entityType, 0, remoteCount / pageSize);
IEnumerable<object> remoteEntities;
if (pageNumber == 0)
{
//do a sliding window sync
for (int i = 1; i <= (remoteCount / pageSize)+1; i++)
{
_listenerService.TriggerSyncProgress(entityType, i, remoteCount / pageSize);
remoteEntities = await _httpClient.GetPage(entityType, i, pageSize, cancellationToken);
await MergeLocalWithRemote(entityType, remoteEntities, localEntities, cancellationToken);
if (cancellationToken.IsCancellationRequested)
{
break;
}
_listenerService.TriggerSyncComplete(entityType);
//if (i == 1)
//{
// _listenerService.TriggerSyncComplete(entityType); //show first page of sync as complete
//}
}
}
else if (pageNumber > 0)
{
remoteEntities = await _httpClient.GetPage(entityType, pageNumber, pageSize, cancellationToken);
await MergeLocalWithRemote(entityType, remoteEntities, localEntities, cancellationToken);
}
_listenerService.TriggerNotifySyncCompleted(entityType);
_listenerService.TriggerSyncComplete(entityType);
}
To Reproduce
Expected behavior
The method to run.
Desktop (please complete the following information):
Additional context
Add any other context about the problem here.
Is System.Net.Http.HttpClient supported in this webworker?
I'm using .net 6.0 and am getting this exception when calling SendAsync from HttpClient.
SyncDataException: Data Sync Error
VM8:3 ---> System.Net.Http.HttpRequestException: TypeError: Failed to execute 'fetch' on 'WorkerGlobalScope': Illegal invocation
VM8:3 ---> System.Runtime.InteropServices.JavaScript.JSException: TypeError: Failed to execute 'fetch' on 'WorkerGlobalScope': Illegal invocation
VM8:3 at System.Net.Http.BrowserHttpHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
VM8:3 --- End of inner exception stack trace ---
VM8:3 at System.Net.Http.BrowserHttpHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
VM8:3 at System.Net.Http.HttpClient.g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
Is that intended / necessary?
There does not seem to be a way to access the indexes in IndexedDB. In JavaScript, you would do something like this:
const myIndex = objectStore.index("myIndexedField"); // index name
const getRequest = myIndex.get(2); // index value to match
Would it be possible to add this interface? Probably something more like objectStore.GetIndex(indexName)
https://developer.mozilla.org/en-US/docs/Web/API/IDBIndex
Also IDBKeyRange and IDBCursor would be awesome and would solve my use cases.
I'm trying to implement the webworkers in .NET 8 but I'm getting the following error in spawndev.blazorjs.webworkers.js:119:47
TypeError: /src="(.+?)"/.exec(...) is null
The specific line where this error occurs is let scriptSrc = /src="(.+?)"/.exec(scriptTagBody)[1];
The browser I'm testing this is in Firefox on Windows 11. The error occurs when running await workerService.GetWebWorker()
in Blazor WASM
I believe the wrong event is subscribed to on this line of code:
Should be:
public JSEventCallback OnAppInstalled { get => new JSEventCallback(o => AddEventListener("appinstalled", o), o => RemoveEventListener("appinstalled", o)); set { /** set MUST BE HERE TO ENABLE += -= operands **/ } }
First of all, thank you for all the effort you have put into this library :)
If the project uses Microsoft.Authentication.WebAssembly.Msal
nuget package, an exception will be thrown at this line:
var webWorker = await workerService.GetWebWorker();
The root cause was MSAL library was trying to read appsettings.json
which is not available at the location specified.
Create appsettings.json
file at wwwroot/_content/SpawnDev.BlazorJS.WebWorkers/appsettings.json
with empty JSON object as follows:
with workaround, the library functions normally.
Could you update the documentation for blazor worker to show that each function names must be unique with unique signatures.
Even though the compiler allows same function names, with different signatures, your plugin does not due to (presumably) limitations set by the way things are serialized.
An example that I ran into that appears to fail is...
//FAIL
public interface IContractService
{
public Task<byte[]> DoThis(string value);
public Task<byte[]> DoThis(double value);
}
//SUCCESS
public interface IContractService
{
public Task<byte[]> DoThis(string value);
public Task<byte[]> DoThat(double value);
}
console log of error:
ERROR: MessageEvent {isTrusted: true, data: {…}, origin: '', lastEventId: '', source: null, …}
VM8:3 ERROR: Exception has been thrown by the target of an invocation.
VM8:3 ERROR stacktrace: at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
VM8:3 at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
VM8:3 at SpawnDev.BlazorJS.IJSInProcessRuntimeExtensions.Invoke(IJSInProcessRuntime _js, Type returnType, String identifier, Object[] args)
VM8:3 at SpawnDev.BlazorJS.JSInterop.Get(Type returnType, IJSInProcessObjectReference targetObject, Int32 identifier)
VM8:3 at SpawnDev.BlazorJS.IJSInProcessObjectReferenceExtensions.Get(IJSInProcessObjectReference targetObject, Type returnType, Int32 identifier)
VM8:3 at SpawnDev.BlazorJS.WebWorkers.ServiceCallDispatcher.PostDeserializeArgs(String requestId, MethodInfo methodInfo, Int32 argsLength, Func`3 getArg)
VM8:3 at SpawnDev.BlazorJS.WebWorkers.ServiceCallDispatcher._worker_OnMessage(MessageEvent e)
I get the following warning after build/publish:
##[warning]/home/vsts/.nuget/packages/spawndev.blazorjs.webworkers/2.2.56/build/SpawnDev.BlazorJS.WebWorkers.targets(40,3): Warning : ********************************** ImportShimBlazorWASM.Execute **********************************
Do I have something misconfigured? Or should it be ignored, and if so, is there a way to suppress this? I don't want it to litter my logs if it's not an actual problem.
Thanks!
Describe the bug
Blazor WASM resources appear as if loaded multiple times, as shown in the browser console, it previously only appears once:
dotnet Loaded 17.94 MB resourcesThis application was built with linking (tree shaking) disabled. Published applications will be significantly smaller if you install wasm-tools workload. See also https://aka.ms/dotnet-wasm-features
Also, the current error appears when the page is first loaded, also twice:
blazor.webassembly.js:1 Error: One or more errors occurred. (Could not find 'synchronizeDbWithCache' ('synchronizeDbWithCache' was undefined).
Error: Could not find 'synchronizeDbWithCache' ('synchronizeDbWithCache' was undefined).
at http://localhost/_framework/blazor.webassembly.js:1:368
at Array.forEach (<anonymous>)
at l.findFunction (http://localhost/_framework/blazor.webassembly.js:1:336)
at w (http://localhost/_framework/blazor.webassembly.js:1:5079)
at http://localhost/_framework/blazor.webassembly.js:1:2872
at new Promise (<anonymous>)
at b.beginInvokeJSFromDotNet (http://localhost/_framework/blazor.webassembly.js:1:2835)
at Object.vn [as invokeJSJson] (http://localhost/_framework/blazor.webassembly.js:1:58849)
at http://localhost/_framework/dotnet.runtime.8.0.5.gongq8hbow.js:3:178364
at Tl (http://localhost/_framework/dotnet.runtime.8.0.5.gongq8hbow.js:3:179198))
at Jn (http://localhost/_framework/dotnet.runtime.8.0.5.gongq8hbow.js:3:31614)
at Tl (http://localhost/_framework/dotnet.runtime.8.0.5.gongq8hbow.js:3:182415)
at do_icall (wasm://wasm/03f7fb96:wasm-function[2658]:0xd6e5a)
at do_icall_wrapper (wasm://wasm/03f7fb96:wasm-function[2543]:0xd1dce)
at mono_interp_exec_method (wasm://wasm/03f7fb96:wasm-function[2536]:0xc3cfc)
at interp_entry (wasm://wasm/03f7fb96:wasm-function[2612]:0xd51f2)
at interp_entry_static_0 (wasm://wasm/03f7fb96:wasm-function[2634]:0xd5cb6)
at wasm_native_to_interp_System_Private_CoreLib_ThreadPool_BackgroundJobHandler (wasm://wasm/03f7fb96:wasm-function[16013]:0x35ed45)
at mono_background_exec (wasm://wasm/03f7fb96:wasm-function[3547]:0x1074b9)
at e.<computed> (http://localhost/_framework/dotnet.runtime.8.0.5.gongq8hbow.js:3:215551)
Other than those messages, the page actually loads and functions as normal.
To Reproduce
Expected behavior
Desktop (please complete the following information):
My Blazor WASM app is running on net7.0
, and using this package
<PackageVersion Include="SpawnDev.BlazorJS.WebWorkers" Version="2.2.0" />
I'm getting the below exception when calling the following async method from within my ViewModel
public async Task DownloadFile(Product product)
{
using var _ = Busy();
using var webWorker = await _webWorkerService.GetWebWorker() ?? throw new InvalidOperationException("Unable to find web worker");
var exportService = webWorker.GetService<IProductExportService>();
var result = await exportService.Export(product).ConfigureAwait(false);
await _jsRuntime.InvokeVoidAsync("BlazorDownloadFile", result.Name, Convert.ToBase64String(result.Data)).ConfigureAwait(true);
}
I think I have my Program.cs setup correctly, so I'm at a loss as to what to do.
If you include the trailing /index.html
in the browser address (I have to do this in certain situations), the _framework and _content paths are no longer correct (they include the /index.html
).
I guess this would need to be checked in spawndev.blazorjs.webworkers.js near line 60.
I'm doing this and need to cancel it sometimes:
await WebWorkerService.TaskPool.Run(() => AssembleSync(request));
AssembleSync is:
public byte[]? AssembleSync(CorePDFAssembleRequest request)
{
// ...
}
Can that be cancelled when using the oh-so-convenient TaskPool-style interface?
Alternately I tried
Func<bool> checkCancelled = () => doTheCheck();
await WebWorkerService.TaskPool.Run(() => AssembleSync1(request, checkCancelled));
AssembleSync1 is:
public byte[]? AssembleSync1(CorePDFAssembleRequest request, Func<bool> checkCancelled)
{
// ...
}
but unfortunately I get a runtime exception
blazor.webassembly.js:1 Uncaught (in promise) Error: System.Exception: Object of type 'System.Action1[System.Boolean]' cannot be converted to type 'System.Func1[System.Boolean]'.`
Another alternative would be to send the WebWorker a cancel message. Is that possible usingTaskPool-style?
Any wisdom is appreciated.
I'm trying to follow best practice to create a custom PWA install experience: https://web.dev/customize-install/
In short, this involves hooking the BeforeInstallPrompt event, caching the BeforeInstallPromptEvent, and then calling it later after presenting a custom UI.
I do something like this:
protected override async Task OnInitializedAsync()
{
using var window = JS.Get<Window>("window");
window.OnBeforeInstallPrompt += Window_OnBeforeInstallPrompt;
}
private BeforeInstallPromptEvent? DeferredPrompt;
void Window_OnBeforeInstallPrompt(BeforeInstallPromptEvent e)
{
e.PreventDefault();
DeferredPrompt = e;
InvokeAsync(() => StateHasChanged()); // This enables the custom install button in the UI
}
// This gets called with the result of the custom dialog we present to the user
async Task HandleInstallDialog(DialogResult dialogResult)
{
try
{
if (dialogResult.Cancelled)
{
Logger.LogInformation("User cancelled install app promo dialog");
}
else if (DeferredPrompt != null)
{
var installResult = await DeferredPrompt.Prompt; // <-- crashes here
Logger.LogInformation($"User responded to app installation with outcome: {installResult.Outcome}");
}
}
catch (Exception ex)
{
Logger.LogError("Error installing app", ex);
}
}
Exception is a JSException with Message: 'undefined\nundefined'
...btw, I think Prompt should really be a function Prompt()
It's kind of a pain to test because Chrome disables the beforeInstallPrompt event after the crash, so if you need to force it, go to Dev Tools and enter this into the Console: window.dispatchEvent(new Event('beforeinstallprompt'))
I am looking for blazer to run JavaScript. This project is great, I need to use it in projects in auto mode,but the web app auto mode is not yet supported. Will it be supported in the future?
I have references I need to use that rely on IJSRuntime in the scope. Code that relies on IJSRuntime seems to fail and/or work in a different sand box than the rest of the website. Any insight into this or workarounds wasm apps?
Example would be using Blazored.LocalStorage within a SpawnDev Web Worker. When I write to storage it will be wiped with the service, after the service is disposed.
Describe the bug
Hello!
In Blazor WebAssembly
I create a webWorker to processAudioData:
var webWorker = await _workerService.GetWebWorker();
_proccessingService = webWorker.GetService<IProcessingService>();
then i call it:
var result = await _proccessingService.ProcessData(samples);
For test - i hardcoded result but i always get the result of the parameterless constructor of the object response type.
And console stops write any debug messages.
Here is Program.cs file:
builder.Services.AddBlazorJSRuntime();
builder.Services.AddWebWorkerService();
builder.Services.AddSingleton<IProcessingService, AudioProcessingService>();
await builder.Build().BlazorJSRunAsync();
**Desktop **
I would like to be able to attach event handlers to window.keydown and window.keyup events and receive a typed KeyboardEvent
My simple F# demo project is working, but I get stuck if I use F# types that are not supported out of the box by System.Text.Json. I would need to add a custom F# converter such as Fsharp.SystemTextJson.
Would you consider adding a way to do that?
E.g. just making the getter of RuntimeJsonSerializerOptions
public; or add a service AddJsonOptions
like the MVC controllers do (see here and here).
I encounter this error on .NET 8 RC-1
Error: Could not find 'blazorCulture.get' ('blazorCulture' was undefined).
Program.cs
services.AddBlazorJSRuntime();
services.AddWebWorkerService();
services.AddSingleton<INotificationWorker, NotificationWorker>();
On Razor
protected override async Task OnInitializedAsync()
{
var webWorker = await worker.GetWebWorker();
webWorker!.OnMessage += (sender, msg) =>
{
Log("Hello world", LogType.Error);
};
var service = webWorker!.GetService<INotificationWorker>();
await service!.DoAsync();
}
The interface
public interface INotificationWorker
{
Task DoAsync();
}
public class NotificationWorker : INotificationWorker
{
public async Task DoAsync()
{
await Task.CompletedTask;
}
}
On the project
<PackageReference Include="SpawnDev.BlazorJS.WebWorkers" Version="2.2.10" />
Please add a Prepend
method to the Element
class corresponding with Element.prepend()
Describe the bug
Getting the following error when calling a service method using a web worker.
Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
Unhandled exception rendering component: Cannot resolve scoped service 'RihalDotnetTemplateWasm.Client.Data.UnitOfWork' from root provider.
System.Exception: Cannot resolve scoped service 'RihalDotnetTemplateWasm.Client.Data.UnitOfWork' from root provider.
at SpawnDev.BlazorJS.WebWorkers.ServiceCallDispatcher.Call(MethodInfo methodInfo, Object[] args) in D:\users\tj\Projects\SpawnDev.BlazorJS\SpawnDev.BlazorJS\SpawnDev.BlazorJS.WebWorkers\ServiceCallDispatcher.cs:line 372
at SpawnDev.BlazorJS.Reflection.InterfaceCallDispatcher`1.<InvokeTaskVoid>d__10[[RihalDotnetTemplateWasm.Client.Services.ISyncService, RihalDotnetTemplateWasm.Client, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].MoveNext() in D:\users\tj\Projects\SpawnDev.BlazorJS\SpawnDev.BlazorJS\SpawnDev.BlazorJS\Reflection\InterfaceCallDispatcher.cs:line 42
at RihalDotnetTemplateWasm.Client.Data.DynamicRepository.GetToListAsync(Type entityType, CancellationToken cancellationToken, Boolean doSync) in C:\Users\alhar\source\repos\rihal-dotnet-template-wasm\RihalDotnetTemplateWasm\Client\Data\DynamicRepository.cs:line 36
at RihalDotnetTemplateWasm.Client.Features.Models.ModelsTable.OnParametersSetAsync() in C:\Users\alhar\source\repos\rihal-dotnet-template-wasm\RihalDotnetTemplateWasm\Client\Features\Models\ModelsTable.razor:line 72
at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()
at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)
To Reproduce
Steps to reproduce the behaviour:
Expected behavior
The method to run
Screenshots
N/A
Desktop (please complete the following information):
Additional context
DynamicRepository.cs
public async Task<List<object>> GetToListAsync(Type entityType, CancellationToken cancellationToken = default, bool doSync = true)
{
if (doSync)
{
//_listenerService.TriggerSyncStart(entityType);
//await _syncService.SyncRemoteWithLocal(entityType, cancellationToken);
Console.WriteLine("Getting ISyncService from _webWorkerService.TaskPool.GetService<ISyncService>()");
var webWorker = await _webWorkerService.GetWebWorker();
Console.WriteLine($"Got WebWorker instance Id {webWorker?.LocalInfo.InstanceId}");
var service = webWorker.GetService<ISyncService>();
Console.WriteLine($"Got ISyncService from _webWorkerService.TaskPool.GetService<ISyncService>() instance Id {_webWorkerService.InstanceId}");
await service.SyncRemoteWithLocal(entityType, cancellationToken);
Console.WriteLine("Finished SyncRemoteWithLocal using _webWorkerService.TaskPool.GetService<ISyncService>()");
//Console.WriteLine("_webWorkerService.WindowTask.Run(() => _syncService.SyncRemoteWithLocal(entityType, cancellationToken, 0));");
//await _webWorkerService.WindowTask.Run(() => _syncService.SyncRemoteWithLocal(entityType, cancellationToken, 0));
//Console.WriteLine("Finished SyncRemoteWithLocal using _webWorkerService.WindowTask.Run(() => _syncService.SyncRemoteWithLocal(entityType, cancellationToken, 0));");
}
return await _unitOfWork.GetListAsync(entityType,cancellationToken);
}
SyncService.cs
public class SyncService : ISyncService
{
private readonly GenericHttpClient _httpClient;
private readonly ISyncListenerService _listenerService;
private readonly UnitOfWork _unitOfWork;
private const int pageSize = 30;
public SyncService(GenericHttpClient httpClient, UnitOfWork unitOfWork, ISyncListenerService listenerService)
{
_httpClient = httpClient;
_unitOfWork = unitOfWork;
_listenerService = listenerService;
_listenerService.RegisterListener(this);
_listenerService.SyncStart += async (sender, entityType) =>
{
await SyncRemoteWithLocal(entityType);
};
_listenerService.SyncItemById += async (sender, args) =>
{
await SyncRemoteWithLocal(args.entityType, args.id);
};
}
public async Task SyncRemoteWithLocal(Type entityType, CancellationToken cancellationToken = default, int pageNumber = 0)
{
Console.WriteLine($"SyncService.SyncRemoteWithLocal({entityType.Name}) started");
_listenerService.TriggerNotifySyncStarted(entityType);
var localEntitiesSet = _unitOfWork.Get(entityType);
var localEntities = await localEntitiesSet.Cast<object>().ToListAsync(cancellationToken);
var remoteCount = await _httpClient.GetCount(entityType, cancellationToken);
_listenerService.TriggerSyncProgress(entityType, 0, remoteCount / pageSize);
IEnumerable<object> remoteEntities;
if (pageNumber == 0)
{
//do a sliding window sync
for (int i = 1; i <= (remoteCount / pageSize)+1; i++)
{
_listenerService.TriggerSyncProgress(entityType, i, remoteCount / pageSize);
remoteEntities = await _httpClient.GetPage(entityType, i, pageSize, cancellationToken);
await MergeLocalWithRemote(entityType, remoteEntities, localEntities, cancellationToken);
if (cancellationToken.IsCancellationRequested)
{
break;
}
_listenerService.TriggerSyncComplete(entityType);
//if (i == 1)
//{
// _listenerService.TriggerSyncComplete(entityType); //show first page of sync as complete
//}
}
}
else if (pageNumber > 0)
{
remoteEntities = await _httpClient.GetPage(entityType, pageNumber, pageSize, cancellationToken);
await MergeLocalWithRemote(entityType, remoteEntities, localEntities, cancellationToken);
}
_listenerService.TriggerNotifySyncCompleted(entityType);
_listenerService.TriggerSyncComplete(entityType);
}
}
UnitOfWork.cs
public class UnitOfWork : IDisposable
{
private readonly IDbContextFactory<AppDbContext> _dbContextFactory;
private readonly IServiceProvider _serviceProvider;
private AppDbContext _dbContext;
public UnitOfWork(IDbContextFactory<AppDbContext> dbContextFactory, IServiceProvider serviceProvider)
{
_dbContextFactory = dbContextFactory;
_serviceProvider = serviceProvider;
}
//More methods below
}
Program.cs
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.EntityFrameworkCore;
using MudBlazor;
using MudBlazor.Services;
using RihalDotnetTemplateWasm.Client;
using RihalDotnetTemplateWasm.Client.Data;
using RihalDotnetTemplateWasm.Client.Services;
using System.Text.Json;
using System.Text.Json.Serialization;
using SpawnDev.BlazorJS;
using SpawnDev.BlazorJS.WebWorkers;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
// Configure EF Core logging
builder.Logging.SetMinimumLevel(LogLevel.Warning); // Set default minimum log level to Warning
builder.Logging.AddFilter("Microsoft.EntityFrameworkCore.Database.Command", LogLevel.Warning); // Filter out EF Core command logs
builder.Logging.AddFilter("Microsoft.EntityFrameworkCore.Infrastructure", LogLevel.Warning); // Filter out EF Core infrastructure logs
builder.Services.AddBlazorJSRuntime();
builder.Services.AddWebWorkerService(webWorkerService =>
{
// Optionally configure the WebWorkerService service before it is used
// Default WebWorkerService.TaskPool settings: PoolSize = 0, MaxPoolSize = 1, AutoGrow = true
// Below sets TaskPool max size to 2. By default the TaskPool size will grow as needed up to the max pool size.
// Setting max pool size to -1 will set it to the value of navigator.hardwareConcurrency
webWorkerService.TaskPool.MaxPoolSize = 2;
// Below is telling the WebWorkerService TaskPool to set the initial size to 2 if running in a Window scope and 0 otherwise
// This starts up 2 WebWorkers to handle TaskPool tasks as needed
// Setting this to -1 will set the initial pool size to max pool size
//webWorkerService.TaskPool.PoolSize = webWorkerService.GlobalScope == GlobalScope.Window ? 2 : 0;
webWorkerService.TaskPool.PoolSize = webWorkerService.GlobalScope == GlobalScope.Window ? 2 : 0;
});
//builder.Services.RegisterServiceWorker<SyncServiceWorker>();
builder.Services.AddBesqlDbContextFactory<AppDbContext>(options =>
{
options.UseSqlite("Data Source=app.db");
options.UseSnakeCaseNamingConvention();
options.EnableDetailedErrors();
options.EnableSensitiveDataLogging(); // This enables logging parameter values
options.LogTo(Console.WriteLine, LogLevel.Error); // Add logging to console
});
builder.Services.AddScoped<UnitOfWork>();
builder.Services.AddScoped<DynamicRepository>();
// Configure JSON serialization options
builder.Services.AddOptions<JsonSerializerOptions>().Configure(options =>
{
options.ReferenceHandler = ReferenceHandler.Preserve; // Use ReferenceHandler.Preserve to handle object cycles
});
builder.Services.AddSingleton(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddSingleton<GenericHttpClient>();
//MudBlazor
builder.Services.AddMudServices(config =>
{
config.SnackbarConfiguration.PositionClass = Defaults.Classes.Position.BottomRight;
});
builder.Services.AddSingleton<MudThemeService>();
//Listeners to trigger UI updates from background services
builder.Services.AddSingleton<ISyncListenerService, SyncListenerService>();
builder.Services.AddScoped<ISyncService, SyncService>();
var app = builder.Build();
await using (var scope = app.Services.CreateAsyncScope())
{
// Create db context
await using var dbContext = await scope.ServiceProvider
.GetRequiredService<IDbContextFactory<AppDbContext>>()
.CreateDbContextAsync();
// migrate database
await dbContext.Database.MigrateAsync();
}
//await app.RunAsync();
await app.BlazorJSRunAsync();
First of all, thank you for creating this project. I have gotten immediate use out of it.
I am trying to match this equivalent JavaScript using your API and can't figure it out. This code watches for changes in the color scheme and then calls my instance method "SystemThemeChanged":
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', ({ matches }) => { dotNetHelper.invokeMethodAsync("SystemThemeChanged"); });
The event listener is not being added to the window, but rather to the MediaQueryList returned from matchMedia. How do I accomplish this?
The demo works for me on Chrome.
In Firefox, I get ReferenceError: blazorWASMScriptEl is not defined
.
(Firefox 111.0.1 (64-bit) on Windows 11)
When I use the webworkers package with MSAL.
Then I get this error:
"There seems to be a problem. Try is later again!
Message:
Error: could not resolve endpoints. Please check network and try again. Detail: TypeError: Cannot read properties of null (reading 'filter')
"
In Blazor WASM PWA .net 8, my client's program.cs file is the following...
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.DependencyInjection.Extensions;
using SpawnDev.BlazorJS;
using SpawnDev.BlazorJS.WebWorkers;
...
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
// Add SpawnDev.BlazorJS.BlazorJSRuntime
builder.Services.AddBlazorJSRuntime();
// Add SpawnDev.BlazorJS.WebWorkers.WebWorkerService
builder.Services.AddWebWorkerService();
//PROBLEM EXISTS BECAUSE OF THIS LINE
builder.Services.AddHttpClient("ServerAPI", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)).AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("ServerAPI"));
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
});
await builder.Build().BlazorJSRunAsync();
Expected behavior
Using a http message handler (BaseAddressAuthorizationMessageHandler) seems to cause this.
My authentication requires this and was wondering if there was some sort of work around or fix.
ERROR
ON STARTUP (in chrome dev console output)
Error: One or more errors occurred. (An item with the same key has already been added. Key: Microsoft.Extensions.Http.IHttpMessageHandlerBuilderFilter)
at Jn (marshal-to-js.ts:349:18)
at Ul (marshal-to-js.ts:306:28)
at 00b1e8b6:0x1faca
at 00b1e8b6:0x1bf8b
at 00b1e8b6:0xf172
at 00b1e8b6:0x1e7e4
at 00b1e8b6:0x1efda
at 00b1e8b6:0xcfec
at 00b1e8b6:0x440ad
at e. (cwraps.ts:338:24)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.