2 |
3 |
4 | net5.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | $(DefineConstants);NETCOREAPP31
21 |
22 |
23 |
24 | $(DefineConstants);NET5
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/BlazorWorker.Demo/Server/Pages/Error.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model BlazorWorker.Demo.Server.Pages.ErrorModel
3 | @{
4 | Layout = "_Layout";
5 | ViewData["Title"] = "Error";
6 | }
7 |
8 | Error.
9 | An error occurred while processing your request.
10 |
11 | @if (Model.ShowRequestId)
12 | {
13 |
14 | Request ID: @Model.RequestId
15 |
16 | }
17 |
18 | Development Mode
19 |
20 | Swapping to the Development environment displays detailed information about the error that occurred.
21 |
22 |
23 | The Development environment shouldn't be enabled for deployed applications.
24 | It can result in displaying sensitive information from exceptions to end users.
25 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development
26 | and restarting the app.
27 |
28 |
--------------------------------------------------------------------------------
/src/BlazorWorker.Demo/Server/Pages/Error.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 | using Microsoft.AspNetCore.Mvc;
7 | using Microsoft.AspNetCore.Mvc.RazorPages;
8 | using Microsoft.Extensions.Logging;
9 |
10 | namespace BlazorWorker.Demo.Server.Pages
11 | {
12 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
13 | public class ErrorModel : PageModel
14 | {
15 | public string RequestId { get; set; }
16 |
17 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
18 |
19 | private readonly ILogger _logger;
20 |
21 | public ErrorModel(ILogger logger)
22 | {
23 | _logger = logger;
24 | }
25 |
26 | public void OnGet()
27 | {
28 | RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/BlazorWorker.Demo/Server/Pages/Shared/_Layout.cshtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | @ViewBag.Title
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | @RenderBody()
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/BlazorWorker.Demo/Server/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Hosting;
6 | using Microsoft.Extensions.Configuration;
7 | using Microsoft.Extensions.Hosting;
8 | using Microsoft.Extensions.Logging;
9 |
10 | namespace BlazorWorker.Demo.Server
11 | {
12 | public class Program
13 | {
14 | public static void Main(string[] args)
15 | {
16 | CreateHostBuilder(args).Build().Run();
17 | }
18 |
19 | public static IHostBuilder CreateHostBuilder(string[] args) =>
20 | Host.CreateDefaultBuilder(args)
21 | .ConfigureWebHostDefaults(webBuilder =>
22 | {
23 | webBuilder.UseStartup();
24 | });
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/BlazorWorker.Demo/Server/Startup.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Builder;
2 | using Microsoft.AspNetCore.Hosting;
3 | using Microsoft.Extensions.Configuration;
4 | using Microsoft.Extensions.DependencyInjection;
5 | using Microsoft.Extensions.Hosting;
6 |
7 | namespace BlazorWorker.Demo.Server
8 | {
9 | public class Startup
10 | {
11 | public Startup(IConfiguration configuration)
12 | {
13 | Configuration = configuration;
14 | }
15 |
16 | public IConfiguration Configuration { get; }
17 |
18 | // This method gets called by the runtime. Use this method to add services to the container.
19 | // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
20 | public void ConfigureServices(IServiceCollection services)
21 | {
22 |
23 | services.AddControllersWithViews();
24 | services.AddRazorPages();
25 | }
26 |
27 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
28 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
29 | {
30 | if (env.IsDevelopment())
31 | {
32 | app.UseDeveloperExceptionPage();
33 | app.UseWebAssemblyDebugging();
34 | }
35 | else
36 | {
37 | app.UseExceptionHandler("/Error");
38 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
39 | app.UseHsts();
40 | }
41 |
42 | app.UseHttpsRedirection();
43 | app.UseBlazorFrameworkFiles();
44 | app.UseStaticFiles();
45 |
46 | app.UseRouting();
47 |
48 | app.UseEndpoints(endpoints =>
49 | {
50 | endpoints.MapRazorPages();
51 | endpoints.MapControllers();
52 | endpoints.MapFallbackToFile("index.html");
53 | });
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/BlazorWorker.Demo/Server/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Information"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/BlazorWorker.Demo/Server/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Information"
7 | }
8 | },
9 | "AllowedHosts": "*"
10 | }
11 |
--------------------------------------------------------------------------------
/src/BlazorWorker.Demo/ServerWasm32/BlazorWorker.Demo.Server.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.1
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | $(DefineConstants);NETCOREAPP31
22 |
23 |
24 |
25 | $(DefineConstants);NET5
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/BlazorWorker.Demo/ServerWasm32/Pages/Error.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model BlazorWorker.Demo.Server.Pages.ErrorModel
3 | @{
4 | Layout = "_Layout";
5 | ViewData["Title"] = "Error";
6 | }
7 |
8 | Error.
9 | An error occurred while processing your request.
10 |
11 | @if (Model.ShowRequestId)
12 | {
13 |
14 | Request ID: @Model.RequestId
15 |
16 | }
17 |
18 | Development Mode
19 |
20 | Swapping to the Development environment displays detailed information about the error that occurred.
21 |
22 |
23 | The Development environment shouldn't be enabled for deployed applications.
24 | It can result in displaying sensitive information from exceptions to end users.
25 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development
26 | and restarting the app.
27 |
28 |
--------------------------------------------------------------------------------
/src/BlazorWorker.Demo/ServerWasm32/Pages/Error.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 | using Microsoft.AspNetCore.Mvc;
7 | using Microsoft.AspNetCore.Mvc.RazorPages;
8 | using Microsoft.Extensions.Logging;
9 |
10 | namespace BlazorWorker.Demo.Server.Pages
11 | {
12 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
13 | public class ErrorModel : PageModel
14 | {
15 | public string RequestId { get; set; }
16 |
17 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
18 |
19 | private readonly ILogger _logger;
20 |
21 | public ErrorModel(ILogger logger)
22 | {
23 | _logger = logger;
24 | }
25 |
26 | public void OnGet()
27 | {
28 | RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/BlazorWorker.Demo/ServerWasm32/Pages/Shared/_Layout.cshtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | @ViewBag.Title
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | @RenderBody()
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/BlazorWorker.Demo/ServerWasm32/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Hosting;
2 | using Microsoft.Extensions.Hosting;
3 |
4 | namespace BlazorWorker.Demo.Server
5 | {
6 | public class Program
7 | {
8 | public static void Main(string[] args)
9 | {
10 | CreateHostBuilder(args).Build().Run();
11 | }
12 |
13 | public static IHostBuilder CreateHostBuilder(string[] args) =>
14 | Host.CreateDefaultBuilder(args)
15 | .ConfigureWebHostDefaults(webBuilder =>
16 | {
17 | webBuilder.UseStartup();
18 | });
19 | }
20 | }
--------------------------------------------------------------------------------
/src/BlazorWorker.Demo/ServerWasm32/Startup.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Builder;
2 | using Microsoft.AspNetCore.Hosting;
3 | using Microsoft.Extensions.Configuration;
4 | using Microsoft.Extensions.DependencyInjection;
5 | using Microsoft.Extensions.Hosting;
6 |
7 | namespace BlazorWorker.Demo.Server
8 | {
9 | public class Startup
10 | {
11 | public Startup(IConfiguration configuration)
12 | {
13 | Configuration = configuration;
14 | }
15 |
16 | public IConfiguration Configuration { get; }
17 |
18 | // This method gets called by the runtime. Use this method to add services to the container.
19 | // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
20 | public void ConfigureServices(IServiceCollection services)
21 | {
22 |
23 | services.AddControllersWithViews();
24 | }
25 |
26 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
27 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
28 | {
29 | if (env.IsDevelopment())
30 | {
31 | app.UseDeveloperExceptionPage();
32 | app.UseWebAssemblyDebugging();
33 | }
34 | else
35 | {
36 | app.UseExceptionHandler("/Error");
37 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
38 | app.UseHsts();
39 | }
40 |
41 | app.UseHttpsRedirection();
42 | app.UseBlazorFrameworkFiles();
43 | app.UseStaticFiles();
44 |
45 | app.UseRouting();
46 |
47 | app.UseEndpoints(endpoints =>
48 | {
49 | endpoints.MapControllers();
50 | endpoints.MapFallbackToFile("index.html");
51 | });
52 | }
53 | }
54 | }
--------------------------------------------------------------------------------
/src/BlazorWorker.Demo/ServerWasm32/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Information"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/BlazorWorker.Demo/ServerWasm32/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Information"
7 | }
8 | },
9 | "AllowedHosts": "*"
10 | }
11 |
--------------------------------------------------------------------------------
/src/BlazorWorker.Demo/Shared/BlazorWorker.Demo.Shared.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net7.0;net8.0
5 | Debug;Release;Nuget
6 | true
7 |
8 |
9 |
10 | 1701;1702;1591;CA1416
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/BlazorWorker.Demo/Shared/CoreMathsService.cs:
--------------------------------------------------------------------------------
1 | using BlazorWorker.WorkerCore;
2 | using System.Text.RegularExpressions;
3 |
4 | namespace BlazorWorker.Demo.Shared
5 | {
6 | ///
7 | /// This service runs in the worker.
8 | /// Uses hand-written seriaization of messages.
9 | ///
10 | public class CoreMathsService
11 | {
12 | public static readonly string EventsPi = $"Events.{nameof(MathsService.Pi)}";
13 | public static readonly string ResultMessage = $"Methods.{nameof(MathsService.EstimatePI)}.Result";
14 |
15 | private readonly MathsService mathsService;
16 | private readonly IWorkerMessageService messageService;
17 |
18 | public CoreMathsService(IWorkerMessageService messageService)
19 | {
20 | this.messageService = messageService;
21 | this.messageService.IncomingMessage += OnMessage;
22 | mathsService = new MathsService();
23 | mathsService.Pi += (s, progress) => messageService.PostMessageAsync($"{EventsPi}:{progress.Progress}");
24 | }
25 |
26 | private void OnMessage(object sender, string message)
27 | {
28 | if (message.StartsWith(nameof(mathsService.EstimatePI)))
29 | {
30 | var messageParams = message.Substring(nameof(mathsService.EstimatePI).Length).Trim();
31 | var rx = new Regex(@"\((?[^\)]+)\)");
32 | var arg0 = rx.Match(messageParams).Groups["arg"].Value.Trim();
33 | var iterations = int.Parse(arg0);
34 | mathsService.EstimatePI(iterations).ContinueWith(t =>
35 | messageService.PostMessageAsync($"{ResultMessage}:{t.Result}"));
36 | return;
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/BlazorWorker.Demo/Shared/JsDirectExample.cs:
--------------------------------------------------------------------------------
1 | using BlazorWorker.BackgroundServiceFactory;
2 | using BlazorWorker.Core;
3 | using BlazorWorker.WorkerBackgroundService;
4 | using BlazorWorker.WorkerCore;
5 | using Microsoft.JSInterop;
6 | using System;
7 | using System.Threading.Tasks;
8 |
9 | namespace BlazorWorker.Demo.Shared
10 | {
11 | public partial class JsDirectExample
12 | {
13 | private readonly IWorkerFactory workerFactory;
14 | private readonly IJSRuntime jsRuntime;
15 | private IWorkerBackgroundService service;
16 | private long workerId;
17 | public event EventHandler LogHandler;
18 |
19 | public JsDirectExample(IWorkerFactory workerFactory, IJSRuntime jsRuntime)
20 | {
21 | this.workerFactory = workerFactory;
22 | this.jsRuntime = jsRuntime;
23 | }
24 |
25 | private void Log(string message)
26 | {
27 | LogHandler?.Invoke(this, message);
28 | }
29 |
30 | public async Task Execute()
31 | {
32 | if (this.service == null)
33 | {
34 | Log("Execute: Creating worker...");
35 | var worker = await this.workerFactory.CreateAsync();
36 | this.workerId = worker.Identifier;
37 | Log("Execute: Creating service...");
38 | this.service = await worker.CreateBackgroundServiceAsync();
39 |
40 | Log("Execute: Setting up main js...");
41 |
42 | // Method setupJsDirectForWorker is defined in BlazorWorker.Demo.SharedPages/wwwroot/JsDirectExample.js
43 | await this.jsRuntime.InvokeVoidAsync("setupJsDirectForWorker", this.workerId);
44 | }
45 |
46 | Log("Execute: Calling ExecuteJsDirect on worker...");
47 | await service.RunAsync(s => s.ExecuteJsDirect());
48 | Log("Execute: Done");
49 | }
50 |
51 | }
52 |
53 | public class JsDirectExampleWorkerService
54 | {
55 | private readonly IWorkerMessageService messageService;
56 | public JsDirectExampleWorkerService(IWorkerMessageService messageService)
57 | {
58 | this.messageService = messageService;
59 | }
60 | public async Task ExecuteJsDirect()
61 | {
62 | await messageService.PostMessageJsDirectAsync("Hello main js thread.");
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/BlazorWorker.Demo/Shared/MathsService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Net.Http;
5 | using System.Numerics;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace BlazorWorker.Demo.Shared
10 | {
11 | public class PiProgress
12 | {
13 | public int Progress { get; set; }
14 | }
15 |
16 |
17 | ///
18 | /// This service runs insinde the worker.
19 | ///
20 | public class MathsService
21 | {
22 | public event EventHandler Pi;
23 |
24 | private IEnumerable AlternatingSequence(int start = 0)
25 | {
26 | int i;
27 | bool flip;
28 | if (start == 0)
29 | {
30 | yield return 1;
31 | i = 1;
32 | flip = false;
33 | }
34 | else
35 | {
36 | i = (start * 2) - 1;
37 | flip = start % 2 == 0;
38 | }
39 |
40 | while (true) yield return ((flip = !flip) ? -1 : 1) * (i += 2);
41 | }
42 |
43 | public async Task EstimatePI(int sumLength)
44 | {
45 | var lastReport = 0;
46 | await Task.Delay(100);
47 | return (4 * AlternatingSequence().Take(sumLength)
48 | .Select((x, i) => {
49 | // Keep reporting events down a bit, serialization is expensive!
50 | var progressDelta = (Math.Abs(i - lastReport) / (double)sumLength) * 100;
51 | if (progressDelta > 3 || i >= sumLength - 1)
52 | {
53 | lastReport = i;
54 | Pi?.Invoke(this, new PiProgress() { Progress = i });
55 | }
56 | return x; })
57 | .Sum(x => 1.0 / x));
58 | }
59 |
60 | public double EstimatePISlice(int sumStart, int sumLength)
61 | {
62 | Console.WriteLine($"EstimatePISlice({sumStart},{sumLength})");
63 | var lastReport = 0;
64 | return AlternatingSequence(sumStart)
65 | .Take(sumLength)
66 | .Select((x, i) => {
67 |
68 | // Keep reporting events down a bit, serialization is expensive!
69 | var progressDelta = (Math.Abs(i - lastReport) / (double)sumLength) * 100;
70 | if (progressDelta > 3 || i >= sumLength - 1)
71 | {
72 | lastReport = i;
73 | Pi?.Invoke(this, new PiProgress() { Progress = i });
74 | }
75 | return x;
76 | })
77 | .Sum(x => 1.0 / x);
78 |
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/BlazorWorker.Demo/Shared/SharedDemoExtensions.cs:
--------------------------------------------------------------------------------
1 | using BlazorWorker.Core;
2 | using BlazorWorker.Demo.IoCExample;
3 | using Microsoft.Extensions.DependencyInjection;
4 |
5 | namespace BlazorWorker.Demo.Shared
6 | {
7 | public static class SharedDemoExtensions
8 | {
9 | public static IServiceCollection AddDemoDependencies(this IServiceCollection serviceCollection)
10 | {
11 | serviceCollection.AddWorkerFactory();
12 | serviceCollection.AddIndexedDbDemoPersonConfig();
13 | serviceCollection.AddTransient();
14 | serviceCollection.AddTransient();
15 | return serviceCollection;
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/BlazorWorker.Demo/Shared/WebCallerService.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Http;
2 | using System.Threading.Tasks;
3 |
4 | namespace BlazorWorker.Demo.Shared
5 | {
6 | public class WebCallerService
7 | {
8 | public HttpClient HttpClient { get; }
9 |
10 | public WebCallerService(HttpClient httpClient)
11 | {
12 | HttpClient = httpClient;
13 | }
14 |
15 | public async Task CallGitHubApi()
16 | {
17 | var github = await HttpClient.GetAsync("https://api.github.com");
18 | return await github.Content.ReadAsStringAsync();
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/BlazorWorker.Demo/SharedPages/BlazorWorker.Demo.SharedPages.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net7.0;net8.0
5 | Latest
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | true
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/BlazorWorker.Demo/SharedPages/Pages/Http.razor:
--------------------------------------------------------------------------------
1 | @inject IWorkerFactory workerFactory
2 |
3 |
4 |
5 |
Using HttpClient
6 |
7 | This demo calls the github api from a new thread.
8 |
9 |
10 |
11 |
Run test
12 |
13 |
14 |
Output:
15 |
16 |
17 | @output
18 |
19 |
20 |
21 |
22 |
23 |
24 | @code {
25 | string output;
26 | IWorker worker;
27 | IWorkerBackgroundService backgroundService;
28 | string canDisposeWorker => worker == null ? null : "disabled";
29 | string canDisposeService => backgroundService == null ? null : "disabled";
30 | string RunDisabled => Running ? "disabled" : null;
31 | bool Running = false;
32 | private string rn = Environment.NewLine;
33 |
34 | public async Task OnClick(EventArgs _)
35 | {
36 | Running = true;
37 | output = "";
38 | try
39 | {
40 |
41 | if (worker == null)
42 | {
43 | worker = await workerFactory.CreateAsync();
44 | }
45 |
46 | var sw = new System.Diagnostics.Stopwatch();
47 | if (backgroundService == null)
48 | {
49 | output = $"{rn}{LogDate()} Creating background service...";
50 | StateHasChanged();
51 |
52 | sw.Start();
53 | backgroundService = await worker.CreateBackgroundServiceAsync(
54 | /*options => options
55 | .AddConventionalAssemblyOfService()
56 | .AddHttpClient()*/
57 | );
58 |
59 | sw.Stop();
60 | output += $"{rn}{LogDate()} Background service created in {sw.ElapsedMilliseconds}ms";
61 | StateHasChanged();
62 | }
63 |
64 | output += $"{rn}{LogDate()} Calling Github WebService...";
65 | var result = await backgroundService.RunAsync(s => s.CallGitHubApi());
66 |
67 | output += $"{rn}{LogDate()} Result:{rn}{FormatJson(result)}";
68 | StateHasChanged();
69 |
70 | }
71 | catch (Exception e)
72 | {
73 | output += $"{rn}Error = {e}";
74 | }
75 | finally
76 | {
77 | Running = false;
78 | }
79 | }
80 |
81 | private string LogDate()
82 | {
83 | return DateTime.Now.ToString("HH:mm:ss:fff");
84 | }
85 |
86 | private static string FormatJson(string json)
87 | {
88 | var parsedJson = Newtonsoft.Json.JsonConvert.DeserializeObject(json);
89 | return Newtonsoft.Json.JsonConvert.SerializeObject(parsedJson, Newtonsoft.Json.Formatting.Indented);
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/BlazorWorker.Demo/SharedPages/Pages/JsDirect.razor:
--------------------------------------------------------------------------------
1 | @inject JsDirectExample jsDirectExample
2 |
3 |
4 |
5 |
6 |
JSDirect Calls
7 |
8 | Demonstrates how to receive messages from a dotnet worker on the main/ui js thread directly.
9 | Also demonstrates putting most of the logic in a separate class (JsDirectExample)
10 |
11 |
12 |
Run test
13 |
14 |
15 |
16 |
17 |
JsDirect.razor Output:
18 |
19 |
20 |
@output
21 |
22 |
23 |
Main js Output:
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | @code {
35 | string output;
36 | string RunDisabled => Running ? "disabled" : null;
37 | bool Running = false;
38 |
39 | protected override void OnInitialized()
40 | {
41 | jsDirectExample.LogHandler += (s, e) => log(e);
42 | output = "";
43 | base.OnInitialized();
44 | }
45 |
46 | public async Task OnClick(EventArgs _)
47 | {
48 | Running = true;
49 | try
50 | {
51 | await jsDirectExample.Execute();
52 | }
53 | catch (Exception e)
54 | {
55 | log($"Error = {e}");
56 | }
57 | finally
58 | {
59 | Running = false;
60 | }
61 | }
62 |
63 | void log(string logStr){
64 | output += $"{Environment.NewLine}{LogDate()} {logStr}";
65 | StateHasChanged();
66 | }
67 |
68 | private string LogDate()
69 | {
70 | return DateTime.Now.ToString("HH:mm:ss:fff");
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/BlazorWorker.Demo/SharedPages/Pages/JsInteractions.razor:
--------------------------------------------------------------------------------
1 | @inject JsInteractionsExample jsDirectExample
2 |
3 |
4 |
5 |
Interacting with Js Calls
6 |
7 | Demonstrates how to call to js from a worker using IJsRuntime,
8 | and how call back directly to a dotnet instance.
9 |
10 |
11 |
12 |
13 |
Run test
14 |
15 |
16 |
17 |
18 |
JsInteractions.razor Output:
19 |
20 |
21 |
@output
22 |
23 |
24 |
Worker dotnet Output:
25 |
26 |
@workeroutput
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | @code {
36 | string output;
37 | string workeroutput;
38 | string RunDisabled => Running ? "disabled" : null;
39 | bool Running = false;
40 |
41 | protected override void OnInitialized()
42 | {
43 | jsDirectExample.LogHandler += (s, e) => log(e);
44 | jsDirectExample.WorkerLogHandler += (s, e) => workerlog(e);
45 | output = "";
46 | workeroutput = "";
47 | base.OnInitialized();
48 | }
49 |
50 | public async Task OnClick(EventArgs _)
51 | {
52 | Running = true;
53 | try
54 | {
55 | await jsDirectExample.Execute();
56 | }
57 | catch (Exception e)
58 | {
59 | log($"Error = {e}");
60 | }
61 | finally
62 | {
63 | Running = false;
64 | }
65 | }
66 |
67 | void log(string logStr){
68 | output += $"{Environment.NewLine}{LogDate()} {logStr}";
69 | StateHasChanged();
70 | }
71 |
72 | void workerlog(string logStr)
73 | {
74 | workeroutput += $"{Environment.NewLine}{LogDate()} {logStr}";
75 | StateHasChanged();
76 | }
77 |
78 | private string LogDate()
79 | {
80 | return DateTime.Now.ToString("HH:mm:ss:fff");
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/BlazorWorker.Demo/SharedPages/Shared/CustomExpressionSerializer.cs:
--------------------------------------------------------------------------------
1 | using BlazorWorker.WorkerBackgroundService;
2 | using Serialize.Linq.Serializers;
3 | using System;
4 | using System.Linq.Expressions;
5 | using static BlazorWorker.Demo.SharedPages.Pages.ComplexSerialization;
6 |
7 | namespace BlazorWorker.Demo.SharedPages.Shared
8 | {
9 | ///
10 | /// Example 1: Simple custom expression Serializer using
11 | /// as base class, but explicitly adds complex types as known types.
12 | ///
13 | public class CustomSerializeLinqExpressionJsonSerializer : SerializeLinqExpressionJsonSerializerBase
14 | {
15 | public override Type[] GetKnownTypes() =>
16 | [typeof(ComplexServiceArg), typeof(ComplexServiceResponse), typeof(OhLookARecord)];
17 | }
18 |
19 | ///
20 | /// Fully custom Expression Serializer, which uses but you could use an alternative implementation.
21 | ///
22 | public class CustomExpressionSerializer : IExpressionSerializer
23 | {
24 | private readonly ExpressionSerializer serializer;
25 |
26 | public CustomExpressionSerializer()
27 | {
28 | var specificSerializer = new JsonSerializer();
29 | specificSerializer.AddKnownType(typeof(ComplexServiceArg));
30 | specificSerializer.AddKnownType(typeof(ComplexServiceResponse));
31 | specificSerializer.AddKnownType(typeof(OhLookARecord));
32 |
33 | this.serializer = new ExpressionSerializer(specificSerializer);
34 | }
35 |
36 | public Expression Deserialize(string expressionString)
37 | {
38 | return serializer.DeserializeText(expressionString);
39 | }
40 |
41 | public string Serialize(Expression expression)
42 | {
43 | return serializer.SerializeText(expression);
44 | }
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/BlazorWorker.Demo/SharedPages/Shared/GithubSource.razor:
--------------------------------------------------------------------------------
1 | Source for this page
2 | @RelativePath
3 |
4 |
5 | @code {
6 | private const string Tag = "v4.1.2";
7 | private static readonly string RawRoot = $"https://rawcdn.githack.com/Tewr/BlazorWorker/{Tag}/src/BlazorWorker.Demo/SharedPages/";
8 | private static readonly string Root = $"https://github.com/Tewr/BlazorWorker/blob/{Tag}/src/BlazorWorker.Demo/SharedPages/";
9 |
10 | [Parameter]
11 | public string RelativePath { get; set; }
12 |
13 | public string FullRawUrl => RawRoot + RelativePath;
14 | public string FullUrl => Root + RelativePath;
15 | }
16 |
--------------------------------------------------------------------------------
/src/BlazorWorker.Demo/SharedPages/Shared/MainLayout.razor:
--------------------------------------------------------------------------------
1 | @inherits LayoutComponentBase
2 |
3 |
6 |
7 |
12 |
--------------------------------------------------------------------------------
/src/BlazorWorker.Demo/SharedPages/Shared/NavMenu.razor:
--------------------------------------------------------------------------------
1 |
7 |
8 |
17 |
18 | @code {
19 | private bool collapseNavMenu = true;
20 |
21 | private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;
22 |
23 | private void ToggleNavMenu()
24 | {
25 | collapseNavMenu = !collapseNavMenu;
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/BlazorWorker.Demo/SharedPages/Shared/NavMenuLink.razor:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 | @if (Model.Small)
16 | {
17 | @(Model.Text)
18 | }
19 | else
20 | {
21 | @(Model.Text)
22 | }
23 |
24 | @code {
25 | [Parameter]
26 | public NavMenuLinkInfo Model { get; set; }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/BlazorWorker.Demo/SharedPages/Shared/NavMenuLinksModel.cs:
--------------------------------------------------------------------------------
1 | using BlazorWorker.Demo.SharedPages.Pages;
2 | using Microsoft.AspNetCore.Components;
3 | using Microsoft.AspNetCore.Components.Routing;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Text;
7 |
8 | namespace BlazorWorker.Demo.SharedPages.Shared
9 | {
10 | public class NavMenuLinksModel
11 | {
12 | static string BlazorWorkerVersion { get; } =
13 | $"v{typeof(BlazorWorker.BackgroundServiceFactory.WorkerBackgroundServiceExtensions).Assembly.GetName().Version}";
14 |
15 | static string BlazorCoreWorkerVersion { get; } =
16 | $"v{typeof(BlazorWorker.WorkerCore.IWorkerMessageService).Assembly.GetName().Version}";
17 |
18 | public static IEnumerable NavMenuLinks { get; } = new List()
19 | {
20 | {
21 | new() { Icon = "cog", Href="", Text = "Simple Worker", Match= NavLinkMatch.All }
22 | },
23 | {
24 | new() { Icon = "copywriting", Href="BackgroundServiceMulti", Text = "Multiple Workers" }
25 | },
26 | {
27 | new() { Icon = "command", Href="CoreExample", Text = "Core Example" }
28 | },
29 | {
30 | new() { Icon = "globe", Href="Http", Text = "HttpClient Example" }
31 | },
32 | {
33 | new() { Icon = "transfer", Href="IoCExample", Text = "IoC / DI Example" }
34 | },
35 | {
36 | new() { Icon = "document", Href="IndexedDb", Text = "IndexedDB" }
37 | },
38 | {
39 | new() { Icon = "document", Href="ComplexSerialization", Text = "ComplexSerialization" }
40 | },
41 | {
42 | new() { Icon = "document", Href="JsDirect", Text = "JsDirect" }
43 | },
44 | {
45 | new() { Icon = "document", Href="JsInteractions", Text = "JsInteractions" }
46 | },
47 | {
48 | new() { Icon = "fork", Href="https://github.com/tewr/BlazorWorker", Text = "To the source!" }
49 | },
50 | {
51 | new() { Icon = "info", Href="https://www.nuget.org/packages/Tewr.BlazorWorker.BackgroundService", Small=true, Text = $"BackgroundService {BlazorWorkerVersion}" }
52 | },
53 | {
54 | new() { Icon = "info", Href="https://www.nuget.org/packages/Tewr.BlazorWorker.Core", Small=true, Text = $"Core {BlazorCoreWorkerVersion}" }
55 | }
56 | };
57 | }
58 |
59 | public class NavMenuLinkInfo
60 | {
61 |
62 | public string Icon { get; set; }
63 |
64 |
65 | public string Href { get; set; }
66 |
67 | public bool Small { get; set; }
68 |
69 |
70 | public NavLinkMatch Match { get; set; } = NavLinkMatch.Prefix;
71 | public string Text { get; internal set; }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/BlazorWorker.Demo/SharedPages/_Imports.razor:
--------------------------------------------------------------------------------
1 | @using System.Net.Http
2 | @using Microsoft.AspNetCore.Components.Forms
3 | @using Microsoft.AspNetCore.Components.Routing
4 | @using Microsoft.AspNetCore.Components.Web
5 | @using Microsoft.JSInterop
6 | @using BlazorWorker.Demo.SharedPages
7 | @using BlazorWorker.Demo.SharedPages.Shared
8 | @using BlazorWorker.Demo.Shared
9 | @using BlazorWorker.BackgroundServiceFactory
10 | @using BlazorWorker.WorkerBackgroundService
11 | @using BlazorWorker.Core
--------------------------------------------------------------------------------
/src/BlazorWorker.Demo/SharedPages/wwwroot/JsDirectExample.js:
--------------------------------------------------------------------------------
1 | // This 'classic' script will run on the main thread and cirectly receieve message from the worker
2 | function setupJsDirectForWorker(myWorkerId) {
3 | const currtime = () => [new Date()].map(d => `${d.toTimeString().split(" ")[0]}:${d.getMilliseconds()}`)[0];
4 | const output = document.getElementById('jsDirectOutputElement');
5 | output.innerText += `\n${currtime()} Setting up event listener.`;
6 | window.addEventListener('blazorworker:jsdirect', function (e) {
7 | if (e.detail.workerId === myWorkerId) {
8 | output.innerText += `\n${currtime()} blazorworker:jsdirect listener. workerId: ${e.detail.workerId}. data: '${e.detail.data}'`;
9 | }
10 | else {
11 | console.log('blazorworker:jsdirect handler for some other worker not handled by this listener', { workerId: e.detail.workerId, data: e.detail.data });
12 | }
13 | });
14 | }
--------------------------------------------------------------------------------
/src/BlazorWorker.Demo/SharedPages/wwwroot/JsInteractionsExample.js:
--------------------------------------------------------------------------------
1 | // This script will run on the worker.
2 | self.jsInteractionsExample = async (JsInteractionsExampleWorkerService) => {
3 | console.log(`Interacting with worker Js on worker thread.`);
4 | await JsInteractionsExampleWorkerService.invokeMethodAsync("CallbackFromJavascript", 'Callback to dotnet instance method');
5 |
6 | // Calling static methods can be done like this
7 | var demoExports = await self.BlazorWorker.getAssemblyExports("BlazorWorker.Demo.Shared")
8 | var methodResult = demoExports.BlazorWorker.Demo.Shared.JsInteractionsExampleWorkerService.StaticCallbackFromJs("Static methods are cheaper to call.");
9 |
10 | await JsInteractionsExampleWorkerService.invokeMethodAsync("CallbackFromJavascript", 'StaticCallbackFromJs returned: '+ methodResult);
11 | }
--------------------------------------------------------------------------------
/src/BlazorWorker.Extensions.JSRuntime/BlazorWorker.Extensions.JSRuntime.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net7.0;net8.0
5 | Tor Knutsson (Tewr)
6 | BlazorWorker
7 | MIT
8 | Extension for BlazorWorker.Core -- use IJSRuntime in dotnet Web Workers Threads in Blazor
9 | https://github.com/Tewr/BlazorWorker
10 | https://github.com/Tewr/BlazorWorker
11 | WebWorker Worker Process Threading Multithreading Blazor Isolation
12 | Tewr.BlazorWorker.Extensions.JSRuntime
13 | Debug;Release;Nuget
14 | 4.1.3
15 | 4.1.3.0
16 | BlazorWorker.Extensions.JSRuntime.xml
17 | icon.png
18 | false
19 | Fix a bug related to .net instance callback from worker thread js
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | true
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | true
46 |
47 |
48 |
49 | 1701;1702;1591;CA1416
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/src/BlazorWorker.Extensions.JSRuntime/BlazorWorkerJsRuntimeSetupExtensions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.DependencyInjection;
2 | using Microsoft.JSInterop;
3 | using System;
4 | using System.Text.Json;
5 |
6 | namespace BlazorWorker.Extensions.JSRuntime
7 | {
8 | public static class BlazorWorkerJsRuntimeSetupExtensions
9 | {
10 | public static IServiceCollection AddBlazorWorkerJsRuntime(this IServiceCollection source, Action optionsModifier = null)
11 | {
12 | source.AddSingleton(CreateBlazorWorkerJSRuntime(optionsModifier));
13 | return source;
14 | }
15 |
16 | private static Func CreateBlazorWorkerJSRuntime(Action optionsModifier) {
17 |
18 | var instance = new BlazorWorkerJSRuntime();
19 |
20 | if (optionsModifier != null)
21 | {
22 | optionsModifier(instance.SerializerOptions);
23 | }
24 |
25 | return _ => instance;
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/BlazorWorker.Extensions.JSRuntime/DefaultBlazorWorkerJSRuntimeSerializer.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.JSInterop;
2 | using System.Text.Json;
3 |
4 | namespace BlazorWorker.Extensions.JSRuntime
5 | {
6 | internal class DefaultBlazorWorkerJSRuntimeSerializer : IBlazorWorkerJSRuntimeSerializer
7 | {
8 | private readonly JsonSerializerOptions options;
9 |
10 | public DefaultBlazorWorkerJSRuntimeSerializer(JsonSerializerOptions options)
11 | {
12 | this.options = options;
13 | }
14 |
15 | public T Deserialize(string serializedObject)
16 | {
17 | return System.Text.Json.JsonSerializer.Deserialize(serializedObject, options);
18 | }
19 |
20 | public string Serialize(object obj)
21 | {
22 | return System.Text.Json.JsonSerializer.Serialize(obj, options);
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/BlazorWorker.Extensions.JSRuntime/DotNetObjectReferenceJsonConverter.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.JSInterop;
2 | using System;
3 | using System.Text.Json;
4 | using System.Text.Json.Serialization;
5 |
6 | namespace BlazorWorker.Extensions.JSRuntime
7 | {
8 | internal sealed class DotNetObjectReferenceJsonConverter : JsonConverter> where TValue : class
9 | {
10 | public DotNetObjectReferenceJsonConverter(BlazorWorkerJSRuntime jsRuntime)
11 | {
12 | CallbackJSRuntime = jsRuntime;
13 | }
14 |
15 | public BlazorWorkerJSRuntime CallbackJSRuntime { get; }
16 |
17 | public override DotNetObjectReference Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
18 | {
19 | long dotNetObjectId = 0;
20 |
21 | while (reader.Read() && reader.TokenType != JsonTokenType.EndObject)
22 | {
23 | if (reader.TokenType == JsonTokenType.PropertyName)
24 | {
25 | if (dotNetObjectId == 0 && reader.ValueTextEquals(DotNetObjectReferenceTracker.DotNetObjectRefKey.EncodedUtf8Bytes))
26 | {
27 | reader.Read();
28 | dotNetObjectId = reader.GetInt64();
29 | }
30 | else
31 | {
32 | throw new JsonException($"Unexcepted JSON property {reader.GetString()}.");
33 | }
34 | }
35 | else
36 | {
37 | throw new JsonException($"Unexcepted JSON Token {reader.TokenType}.");
38 | }
39 | }
40 |
41 | if (dotNetObjectId is 0)
42 | {
43 | throw new JsonException($"Required property {DotNetObjectReferenceTracker.DotNetObjectRefKey} not found.");
44 | }
45 |
46 | var value = DotNetObjectReferenceTracker.GetObjectReference(dotNetObjectId);
47 | return value;
48 | }
49 |
50 | public override void Write(Utf8JsonWriter writer, DotNetObjectReference value, JsonSerializerOptions options)
51 | {
52 | DotNetObjectReferenceTracker.SetCallbackJSRuntime(value, CallbackJSRuntime);
53 | var objectId = DotNetObjectReferenceTracker.TrackObjectReference(value);
54 |
55 | writer.WriteStartObject();
56 | writer.WriteNumber(DotNetObjectReferenceTracker.DotNetObjectRefKey, objectId);
57 |
58 | writer.WriteEndObject();
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/BlazorWorker.Extensions.JSRuntime/DotNetObjectReferenceJsonConverterFactory.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using Microsoft.JSInterop;
5 | using System;
6 | using System.Text.Json;
7 | using System.Text.Json.Serialization;
8 |
9 | namespace BlazorWorker.Extensions.JSRuntime
10 | {
11 | internal sealed class DotNetObjectReferenceJsonConverterFactory : JsonConverterFactory
12 | {
13 | public DotNetObjectReferenceJsonConverterFactory(IJSRuntime jsRuntime)
14 | {
15 | JSRuntime = jsRuntime;
16 | }
17 |
18 | public IJSRuntime JSRuntime { get; private set; }
19 |
20 | public override bool CanConvert(Type typeToConvert)
21 | {
22 | return typeToConvert.IsGenericType && typeToConvert.GetGenericTypeDefinition() == typeof(DotNetObjectReference<>);
23 | }
24 |
25 | public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions jsonSerializerOptions)
26 | {
27 | // System.Text.Json handles caching the converters per type on our behalf. No caching is required here.
28 | var instanceType = typeToConvert.GetGenericArguments()[0];
29 | var converterType = typeof(DotNetObjectReferenceJsonConverter<>).MakeGenericType(instanceType);
30 |
31 | return (JsonConverter)Activator.CreateInstance(converterType, JSRuntime)!;
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/BlazorWorker.Extensions.JSRuntime/GenericNonPublicDelegateCache.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 | using System.Linq;
4 | using System.Reflection;
5 |
6 | namespace BlazorWorker.Extensions.JSRuntime
7 | {
8 |
9 | public class GenericNonPublicDelegateCache : ConcurrentDictionary
10 | {
11 | public GenericNonPublicDelegateCache(Type forType)
12 | {
13 | TargetType = forType;
14 | }
15 |
16 | public Type TargetType { get; }
17 |
18 | public TDelegate GetDelegate(object definingTypeInstance, string name) where TDelegate : Delegate
19 | {
20 | var method = this.GetOrAdd(typeof(T), firstGenericTypeArg => {
21 |
22 | var firstArgGenericDef = typeof(TFirstArg).GetGenericTypeDefinition();
23 | var methodInfo = TargetType.GetRuntimeMethods().FirstOrDefault(methodInfo =>
24 | !methodInfo.IsPublic &&
25 | methodInfo.Name == name &&
26 | methodInfo.ContainsGenericParameters &&
27 | AreGenericTypeEquals(methodInfo.GetParameters().FirstOrDefault()?.ParameterType, firstArgGenericDef));
28 | if (methodInfo == null)
29 | {
30 | throw new ArgumentException($"Unable to find non-public method {definingTypeInstance}.{name}({firstArgGenericDef})");
31 | }
32 |
33 | var genericMethodInfo = methodInfo.MakeGenericMethod(typeof(T));
34 |
35 | return Delegate.CreateDelegate(typeof(TDelegate), definingTypeInstance, genericMethodInfo);
36 | });
37 |
38 | return (TDelegate)method;
39 | }
40 |
41 |
42 | private bool AreGenericTypeEquals(Type genericType1, Type genericType2)
43 | {
44 | return genericType1.Assembly == genericType2.Assembly &&
45 | genericType1.Namespace == genericType2.Namespace &&
46 | genericType1.Name == genericType2.Name;
47 | }
48 | }
49 |
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/src/BlazorWorker.Extensions.JSRuntime/IBlazorWorkerJSRuntimeSerializer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace BlazorWorker.Extensions.JSRuntime
6 | {
7 | public interface IBlazorWorkerJSRuntimeSerializer
8 | {
9 | public string Serialize(object obj);
10 |
11 | T Deserialize(string serializedObject);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/BlazorWorker.Extensions.JSRuntime/JSInvokeService.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.InteropServices.JavaScript;
2 | using System.Threading.Tasks;
3 |
4 | namespace BlazorWorker.Extensions.JSRuntime
5 | {
6 | public partial class JSInvokeService
7 | {
8 | ///
9 | /// Imports locally hosted module scripts
10 | ///
11 | ///
12 | ///
13 | public static Task ImportLocalScripts(params string[] relativeUrls)
14 | => PrivateImportLocalScripts(relativeUrls);
15 |
16 | ///
17 | /// Invokes a method defined on the worker globalThis / self object
18 | ///
19 | ///
20 | ///
21 | /// JSON serialized parameters
22 | ///
23 | public static string WorkerInvoke(string method, string args)
24 | => (string) PrivateWorkerInvoke(method, args);
25 |
26 | ///
27 | /// Asynchronically Invokes a method defined on the worker globalThis / self object
28 | ///
29 | ///
30 | ///
31 | /// JSON serialized parameters
32 | ///
33 | public static async Task WorkerInvokeAsync(string method, string args)
34 | => (await PrivateWorkerInvokeAsync(method, args));
35 |
36 | #region Generated methods
37 |
38 | ///
39 | /// Checks if the specified object path is defined using the self / globalThis object as root.
40 | ///
41 | ///
42 | ///
43 | [JSImport("IsObjectDefined", "BlazorWorker.js")]
44 | public static partial bool IsObjectDefined(string objectPath);
45 |
46 | ///
47 | /// Prepending the specified with the base path of the application, invokes the importScripts() method of the WorkerGlobalScope interface, which synchronously imports one or more scripts into the worker's scope.
48 | ///
49 | ///
50 | [JSImport("ImportLocalScripts", "BlazorWorker.js")]
51 | private static partial Task PrivateImportLocalScripts(string[] relativeUrls);
52 |
53 | [JSImport("WorkerInvokeAsync", "BlazorWorkerJSRuntime.js")]
54 | private static partial Task PrivateWorkerInvokeAsync(string method, string args);
55 |
56 | [JSImport("WorkerInvoke", "BlazorWorkerJSRuntime.js")]
57 | private static partial string PrivateWorkerInvoke(string method, string args);
58 |
59 | #endregion
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/BlazorWorker.Extensions.JSRuntime/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Tewr/BlazorWorker/d5b86005df4d1a964beaed3ae3d03936221ee41b/src/BlazorWorker.Extensions.JSRuntime/icon.png
--------------------------------------------------------------------------------
/src/BlazorWorker.Extensions.JSRuntime/wwwroot/BlazorWorkerJSRuntime.js:
--------------------------------------------------------------------------------
1 | class BlazorWorkerBaseSerializer {
2 | serialize(o) { return JSON.stringify(o); }
3 | deserialize(s) {return JSON.parse(s); }
4 | }
5 |
6 | class DotNetObjectProxy {
7 | constructor(id) {
8 | this.__dotNetObject = `${id}`;
9 | this.serializer = self.jsRuntimeSerializer || new BlazorWorkerBaseSerializer();
10 | }
11 |
12 | async invokeMethodAsync(methodName, ...methodArgs) {
13 | if (this.blazorWorkerJSRuntime === undefined) {
14 | const exports = await self.BlazorWorker.getAssemblyExports("BlazorWorker.Extensions.JSRuntime");
15 | this.blazorWorkerJSRuntime = exports.BlazorWorker.Extensions.JSRuntime.BlazorWorkerJSRuntime;
16 | }
17 |
18 | return await new Promise((resolve, reject) => {
19 | try {
20 | const argsString = this.serializer.serialize({
21 | methodName,
22 | methodArgs : methodArgs || []
23 | });
24 | var result = this.blazorWorkerJSRuntime.InvokeMethod(this.__dotNetObject, argsString);
25 | resolve(result);
26 | } catch (e) {
27 | reject(e);
28 | }
29 | });
30 | }
31 | }
32 |
33 | class BlazorWorkerJSRuntimeSerializer {
34 |
35 | constructor() {
36 | this.baseSerializer = new BlazorWorkerBaseSerializer();
37 | }
38 |
39 | serialize = (o) => this.baseSerializer.serialize(o);
40 |
41 | deserialize = (s) => {
42 | let deserializedObj = this.baseSerializer.deserialize(s);
43 | deserializedObj = BlazorWorkerJSRuntimeSerializer.recursivelyFindDotnetObjectProxy(deserializedObj);
44 | return deserializedObj;
45 | }
46 |
47 | static recursivelyFindDotnetObjectProxy(obj) {
48 |
49 | const recursion = BlazorWorkerJSRuntimeSerializer.recursivelyFindDotnetObjectProxy;
50 | const dotnetObjectKey = "__dotNetObject";
51 | const keys = Object.keys(obj);
52 | if (keys.length === 1 && keys[0] === dotnetObjectKey) {
53 | return new DotNetObjectProxy(obj[dotnetObjectKey]);
54 | }
55 |
56 | for (let i = 0; i < keys.length; i++) {
57 | const property = keys[i];
58 | let value = obj[property];
59 | if (value !== null && typeof value === "object") {
60 | obj[property] = recursion(value);
61 | }
62 | }
63 |
64 | return obj;
65 | }
66 | };
67 |
68 | const serializer = new BlazorWorkerJSRuntimeSerializer();
69 |
70 | const workerInvokeAsync = async function (method, argsString) {
71 |
72 | const methodHandle = self.BlazorWorker.getChildFromDotNotation(method);
73 |
74 | if (methodHandle === self.BlazorWorker.empty) {
75 | throw new Error(`workerInvokeAsync: Method '${method}' not defined`);
76 | }
77 |
78 | const argsArray = serializer.deserialize(argsString);
79 | const result = await methodHandle(...argsArray);
80 | return serializer.serialize(result);
81 | }
82 |
83 | const workerInvoke = function (method, argsString) {
84 |
85 | const methodHandle = self.BlazorWorker.getChildFromDotNotation(method);
86 | if (methodHandle === self.BlazorWorker.empty) {
87 | throw new Error(`workerInvoke: Method '${method}' not defined`);
88 | }
89 |
90 | const argsArray = serializer.deserialize(argsString);
91 | const result = methodHandle(...argsArray);
92 | return serializer.serialize(result);
93 | }
94 |
95 | self.BlazorWorker.setModuleImports("BlazorWorkerJSRuntime.js", {
96 | WorkerInvokeAsync: workerInvokeAsync,
97 | WorkerInvoke: workerInvoke
98 | });
--------------------------------------------------------------------------------
/src/BlazorWorker.ServiceFactory/BackgroundServiceWorkerException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text;
3 | namespace BlazorWorker.BackgroundServiceFactory
4 | {
5 |
6 | public class BackgroundServiceWorkerException: Exception
7 | {
8 | private readonly string workerExceptionString;
9 | private string _stacktrace;
10 |
11 | public long WorkerId { get; }
12 |
13 | public BackgroundServiceWorkerException(long workerId, string message, string workerExceptionString) : base(message)
14 | {
15 | this.WorkerId = workerId;
16 | this.workerExceptionString = workerExceptionString;
17 | }
18 |
19 | public override string StackTrace => (this._stacktrace ??= BuildStackTrace());
20 |
21 | private string BuildStackTrace()
22 | {
23 | var stack = new StringBuilder();
24 |
25 | stack.AppendLine(this.workerExceptionString);
26 | stack.AppendLine($" --- Worker Process Border (WorkerId: {this.WorkerId}) ---");
27 | stack.AppendLine(base.StackTrace);
28 |
29 | return stack.ToString();
30 | }
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/BlazorWorker.ServiceFactory/BlazorWorker.BackgroundServiceFactory.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net7.0;net8.0
5 | Tewr.BlazorWorker.BackgroundService
6 | MIT
7 | Use dotnet Web Workers Threads in Blazor
8 | https://github.com/Tewr/BlazorWorker
9 | https://github.com/Tewr/BlazorWorker
10 | WebWorker Worker Process Threading Multithreading Blazor Isolation
11 | Tewr
12 | BlazorWorker
13 | Debug;Release;Nuget
14 | 4.1.2
15 | 4.1.2.0
16 | icon.png
17 | false
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | all
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | all
51 |
52 |
53 |
54 |
55 |
56 | $(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage
57 |
58 | true
59 |
60 |
61 |
62 |
63 | BlazorWorker.BackgroundServiceFactory.xml
64 |
65 |
66 |
67 | 1701;1702;1591;CA1416
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/src/BlazorWorker.ServiceFactory/BlazorWorker.BackgroundServiceFactory.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | BlazorWorker.BackgroundServiceFactory
5 |
6 |
7 |
8 |
9 | Creates a background service using the specified .
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | Creates a new background service using the specified
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | Attached objects, notably parent worker proxy which may have been created without consumer being directly able to dispose
31 |
32 |
33 |
34 |
35 | Sets a custom ExpressionSerializer type. Must implement ..
36 |
37 |
38 | A type that implements
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/BlazorWorker.ServiceFactory/WorkerInitExtension.cs:
--------------------------------------------------------------------------------
1 | using BlazorWorker.Core;
2 | using BlazorWorker.WorkerBackgroundService;
3 | using System;
4 |
5 | namespace BlazorWorker.BackgroundServiceFactory
6 | {
7 | public static class WorkerInitExtension
8 | {
9 |
10 | ///
11 | /// Sets a custom ExpressionSerializer type. Must implement ..
12 | ///
13 | ///
14 | /// A type that implements
15 | ///
16 | ///
17 | public static WorkerInitOptions UseCustomExpressionSerializer(this WorkerInitOptions source, Type expressionSerializerType)
18 | {
19 | if (source == null)
20 | {
21 | throw new ArgumentNullException(nameof(source));
22 | }
23 |
24 | source.SetEnv(WebWorkerOptions.ExpressionSerializerTypeEnvKey, expressionSerializerType.AssemblyQualifiedName);
25 | return source;
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/BlazorWorker.ServiceFactory/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Tewr/BlazorWorker/d5b86005df4d1a964beaed3ae3d03936221ee41b/src/BlazorWorker.ServiceFactory/icon.png
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerBackgroundService/AssemblyInfo1.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.InteropServices;
2 |
3 | // In SDK-style projects such as this one, several assembly attributes that were historically
4 | // defined in this file are now automatically added during build and populated with
5 | // values defined in project properties. For details of which attributes are included
6 | // and how to customise this process see: https://aka.ms/assembly-info-properties
7 |
8 |
9 | // Setting ComVisible to false makes the types in this assembly not visible to COM
10 | // components. If you need to access a type in this assembly from COM, set the ComVisible
11 | // attribute to true on that type.
12 |
13 | [assembly: ComVisible(false)]
14 |
15 | // The following GUID is for the ID of the typelib if this project is exposed to COM.
16 |
17 | [assembly: Guid("738e7519-3b77-4ecc-a639-3c47fa4412fd")]
18 |
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerBackgroundService/BaseMessage.cs:
--------------------------------------------------------------------------------
1 | namespace BlazorWorker.WorkerBackgroundService
2 | {
3 | public class BaseMessage
4 | {
5 | public string MessageType { get; set; }
6 | }
7 | }
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerBackgroundService/BlazorWorker.WorkerBackgroundService.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net7.0;net8.0
5 |
6 | Debug;Release;Nuget
7 | 4.1.0
8 | 4.1.0.0
9 | true
10 |
11 |
12 |
13 | BlazorWorker.WorkerBackgroundService.xml
14 |
15 |
16 |
17 | 1701;1702;1591;CA1416
18 |
19 |
20 |
21 |
22 | $(MSBuildProjectName).xml
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerBackgroundService/DefaultMessageSerializer.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | namespace BlazorWorker.WorkerBackgroundService
4 | {
5 | public class DefaultMessageSerializer : ISerializer
6 | {
7 | public T Deserialize(string objStr)
8 | {
9 | return JsonConvert.DeserializeObject(objStr);
10 | }
11 |
12 | public string Serialize(object obj)
13 | {
14 | return JsonConvert.SerializeObject(obj);
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerBackgroundService/DisposeInstance.cs:
--------------------------------------------------------------------------------
1 | namespace BlazorWorker.WorkerBackgroundService
2 | {
3 | public class DisposeInstance : BaseMessage
4 | {
5 | public DisposeInstance()
6 | {
7 | MessageType = nameof(DisposeInstance);
8 | }
9 |
10 | public long InstanceId { get; set; }
11 |
12 | public long CallId { get; set; }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerBackgroundService/DisposeInstanceComplete.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace BlazorWorker.WorkerBackgroundService
4 | {
5 | public class DisposeInstanceComplete : BaseMessage
6 | {
7 | public DisposeInstanceComplete()
8 | {
9 | MessageType = nameof(DisposeInstanceComplete);
10 | }
11 |
12 | public long CallId { get; set; }
13 |
14 | public bool IsSuccess { get; set; }
15 |
16 | public Exception Exception { get; set; }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerBackgroundService/EventHandle.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace BlazorWorker.WorkerBackgroundService
6 | {
7 | public class EventHandle
8 | {
9 | public delegate void HandlePayloadMessage(string payLoad);
10 |
11 | private static long idSource;
12 | public EventHandle()
13 | {
14 | Id = ++idSource;
15 | }
16 | public long Id { get; }
17 |
18 | public HandlePayloadMessage EventHandler { get; set; }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerBackgroundService/EventHandlerWrapper.cs:
--------------------------------------------------------------------------------
1 | using BlazorWorker.WorkerBackgroundService;
2 | using System;
3 |
4 | namespace BlazorWorker.WorkerBackgroundService
5 | {
6 | public class EventHandlerWrapper : IEventWrapper
7 | {
8 | private readonly WorkerInstanceManager wim;
9 |
10 | public EventHandlerWrapper(
11 | WorkerInstanceManager wim,
12 | long instanceId,
13 | long eventHandleId)
14 | {
15 | this.wim = wim;
16 | InstanceId = instanceId;
17 | EventHandleId = eventHandleId;
18 | }
19 |
20 | public long InstanceId { get; }
21 | public long EventHandleId { get; }
22 |
23 | public Action Unregister { get; set; }
24 |
25 | public void OnEvent(object _, T eventArgs)
26 | {
27 | wim.PostObject(new EventRaised()
28 | {
29 | EventHandleId = EventHandleId,
30 | InstanceId = InstanceId,
31 | ResultPayload = wim.serializer.Serialize(eventArgs)
32 | });
33 | }
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerBackgroundService/EventRaised.cs:
--------------------------------------------------------------------------------
1 | namespace BlazorWorker.WorkerBackgroundService
2 | {
3 | public class EventRaised : BaseMessage
4 | {
5 | public EventRaised()
6 | {
7 | MessageType = nameof(EventRaised);
8 | }
9 |
10 | public long EventHandleId { get; set; }
11 |
12 | public long InstanceId { get; set; }
13 |
14 | public string ResultPayload { get; set; }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerBackgroundService/IEventWrapper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace BlazorWorker.WorkerBackgroundService
4 | {
5 | public interface IEventWrapper
6 | {
7 | long InstanceId { get; }
8 | long EventHandleId { get; }
9 | Action Unregister { get; set; }
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerBackgroundService/IExpressionSerializer.cs:
--------------------------------------------------------------------------------
1 | using System.Linq.Expressions;
2 |
3 | namespace BlazorWorker.WorkerBackgroundService
4 | {
5 | public interface IExpressionSerializer
6 | {
7 | string Serialize(Expression expr);
8 |
9 | Expression Deserialize(string exprString);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerBackgroundService/ISerializer.cs:
--------------------------------------------------------------------------------
1 | namespace BlazorWorker.WorkerBackgroundService
2 | {
3 | public interface ISerializer
4 | {
5 | string Serialize(object obj);
6 |
7 | T Deserialize(string objStr);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerBackgroundService/IWorkerBackgroundService.cs:
--------------------------------------------------------------------------------
1 | using BlazorWorker.WorkerCore;
2 | using System;
3 | using System.Linq.Expressions;
4 | using System.Reflection;
5 | using System.Threading.Tasks;
6 |
7 | namespace BlazorWorker.WorkerBackgroundService
8 | {
9 | public interface IWorkerBackgroundService : IAsyncDisposable where T : class
10 | {
11 | ///
12 | /// Registers an event listener to the specified event.
13 | ///
14 | ///
15 | ///
16 | ///
17 | ///
18 | Task RegisterEventListenerAsync(string eventName, EventHandler myHandler);
19 |
20 | ///
21 | /// Queues the specified work to run on the underlying worker and returns a object that represents that work.
22 | ///
23 | ///
24 | ///
25 | ///
26 | Task RunAsync(Expression> function);
27 |
28 | ///
29 | /// Queues the specified work to run on the underlying worker and returns a object that represents that work.
30 | ///
31 | ///
32 | ///
33 | Task RunAsync(Expression> action);
34 |
35 | ///
36 | /// Queues the specified awaitable work to run on the underlying worker, awaits the result, and returns a object that represents that work.
37 | ///
38 | ///
39 | ///
40 | ///
41 | Task RunAsync(Expression>> function);
42 |
43 | ///
44 | /// Queues the specified awaitable work to run on the underlying worker, awaits the result, and returns a object that represents that work.
45 | ///
46 | ///
47 | ///
48 | Task RunAsync(Expression> function);
49 |
50 | ///
51 | /// Returns the message service used by the underlying worker.
52 | ///
53 | ///
54 | IWorkerMessageService GetWorkerMessageService();
55 |
56 | ///
57 | /// Unregisters the event corresponding to the specified
58 | ///
59 | Task UnRegisterEventListenerAsync(EventHandle handle);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerBackgroundService/IWorkerBackgroundServiceFactory.cs:
--------------------------------------------------------------------------------
1 | using BlazorWorker.Core;
2 | using System.Threading.Tasks;
3 |
4 | namespace BlazorWorker.WorkerBackgroundService
5 | {
6 | public interface IWorkerBackgroundServiceFactory
7 | {
8 | Task CreateWebworkerAsync();
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerBackgroundService/InitInstance.cs:
--------------------------------------------------------------------------------
1 | using System.Net.WebSockets;
2 |
3 | namespace BlazorWorker.WorkerBackgroundService
4 | {
5 | public class InitInstance : BaseMessage
6 | {
7 | public InitInstance()
8 | {
9 | MessageType = nameof(InitInstance);
10 | }
11 |
12 | public long WorkerId { get; set; }
13 | public long InstanceId { get; set; }
14 | public string AssemblyName { get; set; }
15 | public string TypeName { get; set; }
16 |
17 | public long CallId { get; set; }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerBackgroundService/InitInstanceComplete.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace BlazorWorker.WorkerBackgroundService
4 | {
5 | public class InitInstanceComplete : BaseMessage
6 | {
7 | public InitInstanceComplete()
8 | {
9 | MessageType = nameof(InitInstanceComplete);
10 | }
11 |
12 | public long CallId { get; set; }
13 |
14 | public bool IsSuccess { get; set; }
15 |
16 | public Exception Exception { get; set; }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerBackgroundService/InitInstanceFromFactory.cs:
--------------------------------------------------------------------------------
1 | namespace BlazorWorker.WorkerBackgroundService
2 | {
3 | public class InitInstanceFromFactory : BaseMessage
4 | {
5 | public InitInstanceFromFactory()
6 | {
7 | MessageType = nameof(InitInstanceFromFactory);
8 | }
9 |
10 | public long WorkerId { get; set; }
11 | public long InstanceId { get; set; }
12 |
13 | public long FactoryInstanceId { get; set; }
14 | public string SerializedFactoryExpression { get; set; }
15 |
16 | public long CallId { get; set; }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerBackgroundService/InitInstanceFromFactoryComplete.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace BlazorWorker.WorkerBackgroundService
4 | {
5 | public class InitInstanceFromFactoryComplete : BaseMessage
6 | {
7 | public InitInstanceFromFactoryComplete()
8 | {
9 | MessageType = nameof(InitInstanceFromFactoryComplete);
10 | }
11 |
12 | public long CallId { get; set; }
13 |
14 | public bool IsSuccess { get; set; }
15 |
16 | public Exception Exception { get; set; }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerBackgroundService/InitWorkerComplete.cs:
--------------------------------------------------------------------------------
1 | namespace BlazorWorker.WorkerBackgroundService
2 | {
3 | public class InitWorkerComplete : BaseMessage
4 | {
5 | public InitWorkerComplete()
6 | {
7 | MessageType = nameof(InitWorkerComplete);
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerBackgroundService/LinkerConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerBackgroundService/MethodCallParams.cs:
--------------------------------------------------------------------------------
1 | namespace BlazorWorker.WorkerBackgroundService
2 | {
3 | public class MethodCallParams : BaseMessage
4 | {
5 | public MethodCallParams()
6 | {
7 | MessageType = nameof(MethodCallParams);
8 | }
9 |
10 | public bool AwaitResult { get; set; }
11 | public long InstanceId { get; set; }
12 | public string SerializedExpression { get; set; }
13 | public long WorkerId { get; set; }
14 | public long CallId { get; set; }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerBackgroundService/MethodCallResult.cs:
--------------------------------------------------------------------------------
1 | namespace BlazorWorker.WorkerBackgroundService
2 | {
3 | public class MethodCallResult : BaseMessage
4 | {
5 | public MethodCallResult()
6 | {
7 | MessageType = nameof(MethodCallResult);
8 | }
9 |
10 | public string ResultPayload { get; set; }
11 |
12 | public bool IsException { get; set; }
13 |
14 | public string ExceptionString { get; set; }
15 |
16 | public string ExceptionMessage { get; set; }
17 |
18 | public long CallId { get; set; }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerBackgroundService/RegisterEvent.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace BlazorWorker.WorkerBackgroundService
4 | {
5 | public class RegisterEvent: BaseMessage
6 | {
7 | public RegisterEvent()
8 | {
9 | MessageType = nameof(RegisterEvent);
10 | }
11 |
12 | public long EventHandleId { get; set; }
13 |
14 | public long InstanceId { get; set; }
15 | public string EventName { get; set; }
16 | public string EventHandlerTypeArg { get; set; }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerBackgroundService/SerializeLinqExpressionJsonSerializerBase.cs:
--------------------------------------------------------------------------------
1 | using Serialize.Linq.Serializers;
2 | using System;
3 | using System.Linq.Expressions;
4 |
5 | namespace BlazorWorker.WorkerBackgroundService
6 | {
7 | ///
8 | /// Base class for adding known types to a serializer using .
9 | ///
10 | public abstract class SerializeLinqExpressionJsonSerializerBase : IExpressionSerializer
11 | {
12 | private ExpressionSerializer serializer;
13 |
14 | private ExpressionSerializer Serializer
15 | => this.serializer ?? (this.serializer = GetSerializer());
16 |
17 | ///
18 | /// Automatically adds known types as array types. If set to true , sets to false .
19 | ///
20 | public bool? AutoAddKnownTypesAsArrayTypes { get; set; }
21 |
22 | ///
23 | /// Automatically adds known types as list types. If set to true , sets to false .
24 | ///
25 | public bool? AutoAddKnownTypesAsListTypes { get; set; }
26 |
27 | public abstract Type[] GetKnownTypes();
28 |
29 | private ExpressionSerializer GetSerializer()
30 | {
31 | var jsonSerializer = new JsonSerializer();
32 | foreach (var type in GetKnownTypes())
33 | {
34 | jsonSerializer.AddKnownType(type);
35 | }
36 | if (this.AutoAddKnownTypesAsArrayTypes.HasValue) {
37 | jsonSerializer.AutoAddKnownTypesAsArrayTypes = this.AutoAddKnownTypesAsArrayTypes.Value;
38 | }
39 | if (this.AutoAddKnownTypesAsListTypes.HasValue)
40 | {
41 | jsonSerializer.AutoAddKnownTypesAsListTypes = this.AutoAddKnownTypesAsListTypes.Value;
42 | }
43 |
44 | return new ExpressionSerializer(jsonSerializer);
45 | }
46 |
47 | public Expression Deserialize(string expressionString)
48 | {
49 | return this.Serializer.DeserializeText(expressionString);
50 | }
51 |
52 | public string Serialize(Expression expression)
53 | {
54 | return this.Serializer.SerializeText(expression);
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerBackgroundService/SerializeLinqExpressionSerializer.cs:
--------------------------------------------------------------------------------
1 | using Serialize.Linq.Serializers;
2 | using System.Linq.Expressions;
3 |
4 | namespace BlazorWorker.WorkerBackgroundService
5 | {
6 | public class SerializeLinqExpressionSerializer : IExpressionSerializer
7 | {
8 | private readonly ExpressionSerializer serializer;
9 |
10 | public SerializeLinqExpressionSerializer()
11 | {
12 | this.serializer = new ExpressionSerializer(new JsonSerializer());
13 | }
14 |
15 | public Expression Deserialize(string expressionString)
16 | {
17 | return serializer.DeserializeText(expressionString);
18 | }
19 |
20 | public string Serialize(Expression expression)
21 | {
22 | return serializer.SerializeText(expression);
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerBackgroundService/UnRegisterEvent.cs:
--------------------------------------------------------------------------------
1 | namespace BlazorWorker.WorkerBackgroundService
2 | {
3 | public class UnRegisterEvent : BaseMessage
4 | {
5 | public UnRegisterEvent()
6 | {
7 | MessageType = nameof(UnRegisterEvent);
8 | }
9 |
10 | public long EventHandleId { get; set; }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerBackgroundService/WebWorkerOptions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace BlazorWorker.WorkerBackgroundService
4 | {
5 | public class WebWorkerOptions
6 | {
7 | ///
8 | /// Name of environment variable to be used for transferring the serializer typename
9 | ///
10 | public static readonly string ExpressionSerializerTypeEnvKey = "BLAZORWORKER_EXPRESSIONSERIALIZER";
11 |
12 | private ISerializer messageSerializer;
13 | private IExpressionSerializer expressionSerializer;
14 | private Type expressionSerializerType;
15 |
16 |
17 |
18 | public ISerializer MessageSerializer {
19 | get => messageSerializer ?? (messageSerializer = new DefaultMessageSerializer());
20 | set => messageSerializer = value;
21 | }
22 |
23 | public IExpressionSerializer ExpressionSerializer {
24 | get => expressionSerializer ?? (expressionSerializer = CreateSerializerInstance());
25 | set => expressionSerializer = value;
26 | }
27 |
28 | public Type ExpressionSerializerType
29 | {
30 | get => expressionSerializerType ?? typeof(SerializeLinqExpressionSerializer);
31 | set => expressionSerializerType = ValidateExpressionSerializerType(value);
32 | }
33 |
34 | ///
35 | /// Ensures that the provided type implements
36 | ///
37 | ///
38 | ///
39 | private Type ValidateExpressionSerializerType(Type sourceType)
40 | {
41 | if (sourceType == null)
42 | {
43 | return null;
44 | }
45 |
46 | if (!sourceType.IsClass)
47 | {
48 | throw new Exception($"The {nameof(ExpressionSerializerType)} '{sourceType.AssemblyQualifiedName}' must be a class.");
49 | }
50 |
51 | if (!typeof(IExpressionSerializer).IsAssignableFrom(sourceType))
52 | {
53 | throw new Exception($"The {nameof(ExpressionSerializerType)} '{sourceType.AssemblyQualifiedName}' must be assignable to {nameof(IExpressionSerializer)}");
54 | }
55 |
56 | return sourceType;
57 | }
58 |
59 | private IExpressionSerializer CreateSerializerInstance()
60 | {
61 | var instance = Activator.CreateInstance(ExpressionSerializerType);
62 | return (IExpressionSerializer)instance;
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerCore/BlazorWorker.WorkerCore.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net7.0;net8.0
5 | Debug;Release;Nuget
6 | 4.2.0
7 | 4.2.0.0
8 | Tor Knutsson (Tewr)
9 | Tor Knutsson (Tewr)
10 | BlazorWorker
11 | true
12 |
13 |
14 |
15 | BlazorWorker.WorkerCore.xml
16 | Exe
17 |
18 |
19 |
20 | 1701;1702;1591;CA1416
21 |
22 |
23 |
24 |
25 | $(MSBuildProjectName).xml
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerCore/DOMObject.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using BlazorWorker.WorkerCore.WebAssemblyBindingsProxy;
3 |
4 |
5 | namespace BlazorWorker.WorkerCore
6 | {
7 |
8 | // Serves as a wrapper around a JSObject.
9 | class DOMObject : IDisposable
10 | {
11 | public static DOMObject Self { get; } = new DOMObject("self");
12 |
13 | public JSObject ManagedJSObject { get; private set; }
14 |
15 | public DOMObject(JSObject jsobject)
16 | {
17 | ManagedJSObject = jsobject ?? throw new ArgumentNullException(nameof(jsobject));
18 | }
19 |
20 | public DOMObject(string globalName) : this(new JSObject(Runtime.GetGlobalObject(globalName)))
21 | { }
22 |
23 | public object Invoke(string method, params object[] args)
24 | {
25 | return ManagedJSObject.Invoke(method, args);
26 | }
27 |
28 | public void Dispose()
29 | {
30 | // Dispose of unmanaged resources.
31 | Dispose(true);
32 | // Suppress finalization.
33 | GC.SuppressFinalize(this);
34 | }
35 |
36 | // Protected implementation of Dispose pattern.
37 | protected virtual void Dispose(bool disposing)
38 | {
39 |
40 | if (disposing)
41 | {
42 |
43 | // Free any other managed objects here.
44 | //
45 | }
46 |
47 | // Free any unmanaged objects here.
48 | //
49 | ManagedJSObject?.Dispose();
50 | ManagedJSObject = null;
51 | }
52 |
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerCore/FetchResponse.cs:
--------------------------------------------------------------------------------
1 | namespace BlazorWorker.Core
2 | {
3 | public class FetchResponse
4 | {
5 | public string Base64Data { get; set; }
6 |
7 | public string Url { get; set; }
8 | }
9 | }
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerCore/IWorker.cs:
--------------------------------------------------------------------------------
1 | using BlazorWorker.WorkerCore;
2 | using System;
3 | using System.Threading.Tasks;
4 |
5 | namespace BlazorWorker.Core
6 | {
7 |
8 | public interface IWorker : IWorkerMessageService, IAsyncDisposable
9 | {
10 | bool IsInitialized { get; }
11 |
12 | long Identifier { get; }
13 |
14 | Task InitAsync(WorkerInitOptions initOptions);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerCore/IWorkerMessageService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | namespace BlazorWorker.WorkerCore
5 | {
6 | public interface IWorkerMessageService
7 | {
8 | ///
9 | /// Events for incoming messages to the current context
10 | ///
11 | event EventHandler IncomingMessage;
12 |
13 | ///
14 | /// Post a message to the context this message service belongs to
15 | ///
16 | ///
17 | ///
18 | Task PostMessageAsync(string message);
19 |
20 | ///
21 | /// Post a message that can be read directly on the main js thread using the "blazorworker:jsdirect" event on the window object
22 | ///
23 | ///
24 | ///
25 | Task PostMessageJsDirectAsync(string message);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerCore/InstanceWrapper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace BlazorWorker.WorkerCore
4 | {
5 | public class InstanceWrapper : IDisposable
6 | {
7 | public object Instance { get; set; }
8 | public IDisposable Services { get; set; }
9 |
10 | public void Dispose()
11 | {
12 | if (Instance is IDisposable disposable)
13 | {
14 | disposable.Dispose();
15 | }
16 |
17 | Services?.Dispose();
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerCore/LinkerConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerCore/MessageService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | #if NET7_0_OR_GREATER
4 | using System.Runtime.InteropServices.JavaScript;
5 | #endif
6 | using System.Threading.Tasks;
7 |
8 | #if NET7_0_OR_GREATER
9 | namespace BlazorWorker.WorkerCore
10 | {
11 | ///
12 | /// Simple static message service that runs in the worker thread.
13 | ///
14 | public partial class MessageService
15 | {
16 | public static event EventHandler Message;
17 |
18 | [JSExport]
19 | public static void OnMessage(string message)
20 | {
21 | Message?.Invoke(null, message);
22 | #if DEBUG
23 | Console.WriteLine($"{nameof(MessageService)}.{nameof(OnMessage)}: {message}");
24 | #endif
25 | }
26 |
27 | [JSImport("PostMessage", "BlazorWorker.js")]
28 | public static partial void PostMessage(string message);
29 |
30 | [JSImport("PostMessageJsDirect", "BlazorWorker.js")]
31 | public static partial void PostMessageJsDirect(string message);
32 | }
33 | }
34 | #else
35 |
36 | namespace BlazorWorker.WorkerCore
37 | {
38 | ///
39 | /// Simple static message service that runs in the worker thread.
40 | ///
41 | public class MessageService
42 | {
43 | private static readonly DOMObject self = DOMObject.Self;
44 |
45 | public static event EventHandler Message;
46 |
47 | static MessageService()
48 | {
49 | }
50 |
51 | public static void OnMessage(string message)
52 | {
53 | Message?.Invoke(null, message);
54 | #if DEBUG
55 | Console.WriteLine($"{nameof(MessageService)}.{nameof(OnMessage)}: {message}");
56 | #endif
57 | }
58 | public static void PostMessage(string message)
59 | {
60 | self.Invoke("postMessage", message);
61 | }
62 |
63 | }
64 | }
65 | #endif
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerCore/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | #if NET7_0_OR_GREATER
3 |
4 | Console.WriteLine("BlazorWorker.WorkerCore:Started");
5 |
6 | #else
7 |
8 | namespace BlazorWorker.WorkerCore
9 | {
10 | public class Program
11 | {
12 | static void Main()
13 | {
14 | Console.WriteLine("Hello, Dotnet Worker in Browser!");
15 | }
16 | }
17 | }
18 | #endif
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerCore/SimpleInstanceService/CSVSerializer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace BlazorWorker.WorkerCore.SimpleInstanceService
7 | {
8 | public class CSVSerializer
9 | {
10 | public const char EscapeChar = '\\';
11 | public const char Separator = '|';
12 |
13 | public static string EscapeString(string s)
14 | {
15 | return s?.Replace(EscapeChar, EscapeChar)
16 | .Replace(Separator.ToString(), new string(new[] { EscapeChar, Separator }));
17 | }
18 |
19 | public static string Serialize(string prefix, params object[] fields)
20 | {
21 | return string.Join(Separator.ToString(), new[] { prefix }.Concat(fields));
22 | }
23 |
24 | public static void Deserialize(string prefix, string message, Queue> fieldParserQueue)
25 | {
26 | if (!message.StartsWith(prefix))
27 | {
28 | throw new FormatException($"Unexpected start of message, expected {prefix}");
29 | }
30 | var body = message.Substring(prefix.Length+1);
31 | var sb = new StringBuilder(body.Length);
32 | var lastChar = ' ';
33 | var pos = -1;
34 |
35 | void nextParser() {
36 | var fieldValue = sb.ToString();
37 | try
38 | {
39 | fieldParserQueue.Dequeue()(fieldValue);
40 | }
41 | catch (Exception e)
42 | {
43 | throw new FormatException($"Error when parsing field value '{fieldValue}' message prefixed {prefix}. body '{body}' buffer left '{body.Substring(pos)}", e);
44 | }
45 | }
46 |
47 | foreach (var chr in body)
48 | {
49 | pos++;
50 | if (lastChar == EscapeChar && chr == EscapeChar)
51 | {
52 | continue;
53 | }
54 | else if (lastChar != EscapeChar && chr == Separator)
55 | {
56 | nextParser();
57 |
58 | if (fieldParserQueue.Count == 0)
59 | {
60 | return;
61 | }
62 |
63 | sb.Clear();
64 | }
65 | else
66 | {
67 | sb.Append(chr);
68 | lastChar = chr;
69 | }
70 |
71 | }
72 |
73 | if(fieldParserQueue.Count > 1)
74 | {
75 | throw new FormatException($"Unexpected end of message prefixed {prefix}");
76 | }
77 |
78 | nextParser();
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerCore/SimpleInstanceService/DisposeInstanceRequest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace BlazorWorker.WorkerCore.SimpleInstanceService
6 | {
7 | public class DisposeInstanceRequest
8 | {
9 | public readonly static string Prefix =
10 | $"{SimpleInstanceService.MessagePrefix}{SimpleInstanceService.DiposeMessagePrefix}";
11 |
12 | public long CallId { get; set; }
13 |
14 | public long InstanceId { get; set; }
15 |
16 | public static bool CanDeserialize(string message)
17 | {
18 | return message.StartsWith(Prefix);
19 | }
20 |
21 | public static DisposeInstanceRequest Deserialize(string message)
22 | {
23 | var result = new DisposeInstanceRequest();
24 | var parsers = new Queue>(
25 | new Action[]
26 | {
27 | s => result.CallId = long.Parse(s),
28 | s => result.InstanceId = long.Parse(s)
29 | });
30 |
31 | CSVSerializer.Deserialize(Prefix, message, parsers);
32 |
33 | return result;
34 | }
35 |
36 | public string Serialize()
37 | {
38 | return CSVSerializer.Serialize(Prefix, CallId, InstanceId);
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerCore/SimpleInstanceService/DisposeResult.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace BlazorWorker.WorkerCore.SimpleInstanceService
5 | {
6 | public class DisposeResult
7 | {
8 | public static readonly string Prefix = $"{SimpleInstanceService.MessagePrefix}{SimpleInstanceService.DiposeResultMessagePrefix}";
9 |
10 | public long CallId { get; set; }
11 |
12 | public bool IsSuccess { get; set; }
13 | public long InstanceId { get; set; }
14 |
15 | public string ExceptionMessage { get; set; } = string.Empty;
16 |
17 | public string FullExceptionString { get; set; } = string.Empty;
18 |
19 | public Exception Exception { get; internal set; }
20 |
21 | internal string Serialize()
22 | {
23 | return CSVSerializer.Serialize(Prefix,
24 | this.CallId,
25 | (this.IsSuccess ? 1 : 0),
26 | CSVSerializer.EscapeString(this.ExceptionMessage),
27 | CSVSerializer.EscapeString(this.FullExceptionString));
28 | }
29 |
30 | public static bool CanDeserialize(string message)
31 | {
32 | return message.StartsWith(Prefix);
33 | }
34 |
35 | public static DisposeResult Deserialize(string message)
36 | {
37 | var result = new DisposeResult();
38 |
39 | var parsers = new Queue>(
40 | new Action[] {
41 | s => result.CallId = long.Parse(s),
42 | s => result.IsSuccess = s == "1",
43 | s => result.ExceptionMessage = s,
44 | s => result.FullExceptionString = s
45 | });
46 |
47 | CSVSerializer.Deserialize(Prefix, message, parsers);
48 | return result;
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerCore/SimpleInstanceService/ISimpleInstanceService.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 |
3 | namespace BlazorWorker.WorkerCore.SimpleInstanceService
4 | {
5 | public interface ISimpleInstanceService
6 | {
7 | Task DisposeInstance(DisposeInstanceRequest request);
8 | Task InitInstance(InitInstanceRequest initInstanceRequest);
9 | }
10 | }
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerCore/SimpleInstanceService/InitInstanceRequest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace BlazorWorker.WorkerCore.SimpleInstanceService
6 | {
7 | public class InitInstanceRequest
8 | {
9 |
10 | public static readonly string Prefix = $"{SimpleInstanceService.MessagePrefix}{SimpleInstanceService.InitInstanceMessagePrefix}";
11 | public long CallId { get; set; }
12 | public long Id { get; set; }
13 | public string TypeName { get; set; }
14 | public string AssemblyName { get; set; }
15 |
16 | internal static bool CanDeserialize(string initMessage)
17 | {
18 | return initMessage.StartsWith(Prefix);
19 | }
20 |
21 | internal static InitInstanceRequest Deserialize(string initMessage)
22 | {
23 | var result = new InitInstanceRequest();
24 |
25 | var parsers = new Queue>(
26 | new Action[] {
27 | s => result.CallId = long.Parse(s),
28 | s => result.Id = long.Parse(s),
29 | s => result.TypeName = s,
30 | s => result.AssemblyName = s
31 | });
32 |
33 | CSVSerializer.Deserialize(Prefix, initMessage, parsers);
34 | return result;
35 | }
36 |
37 | public string Serialize()
38 | {
39 | return CSVSerializer.Serialize(Prefix,
40 | CallId,
41 | Id,
42 | CSVSerializer.EscapeString(TypeName),
43 | CSVSerializer.EscapeString(AssemblyName));
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerCore/SimpleInstanceService/InitInstanceResult.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace BlazorWorker.WorkerCore.SimpleInstanceService
5 | {
6 | public class InitInstanceResult
7 | {
8 | public static readonly string Prefix = $"{SimpleInstanceService.MessagePrefix}{SimpleInstanceService.InitInstanceResultMessagePrefix}";
9 |
10 | public long CallId { get; set; }
11 | public bool IsSuccess { get; set; }
12 | public long InstanceId { get; set; }
13 |
14 | public object Instance { get; set; }
15 |
16 | public string ExceptionMessage { get; set; } = string.Empty;
17 |
18 | public string FullExceptionString { get; set; } = string.Empty;
19 |
20 | public Exception Exception { get; internal set; }
21 |
22 | internal string Serialize()
23 | {
24 | return CSVSerializer.Serialize(Prefix,
25 | this.CallId,
26 | this.IsSuccess? 1 : 0,
27 | CSVSerializer.EscapeString(this.ExceptionMessage),
28 | CSVSerializer.EscapeString(this.FullExceptionString));
29 |
30 | }
31 | public static bool CanDeserialize(string message)
32 | {
33 | return message.StartsWith(Prefix);
34 | }
35 |
36 | public static InitInstanceResult Deserialize(string message)
37 | {
38 | var result = new InitInstanceResult();
39 |
40 | var parsers = new Queue>(
41 | new Action[] {
42 | s => result.CallId = long.Parse(s),
43 | s => result.IsSuccess = s == "1",
44 | s => result.ExceptionMessage = s,
45 | s => result.FullExceptionString = s
46 | });
47 |
48 | CSVSerializer.Deserialize(Prefix, message, parsers);
49 | return result;
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerCore/SimpleInstanceService/InitServiceResult.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace BlazorWorker.WorkerCore.SimpleInstanceService
6 | {
7 | public class InitServiceResult
8 | {
9 | public static readonly string Prefix = $"{SimpleInstanceService.MessagePrefix}{SimpleInstanceService.InitServiceResultMessagePrefix}";
10 |
11 | public static bool CanDeserialize(string message)
12 | {
13 | return message.StartsWith(Prefix);
14 | }
15 |
16 | public static InitServiceResult Deserialize(string message)
17 | {
18 | return new InitServiceResult();
19 | }
20 |
21 | public string Serialize()
22 | {
23 | return Prefix;
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerCore/SimpleInstanceService/InjectableMessageService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | namespace BlazorWorker.WorkerCore.SimpleInstanceService
5 | {
6 | public delegate bool IsInfrastructureMessage(string message);
7 | public class InjectableMessageService : IWorkerMessageService, IDisposable
8 | {
9 | private readonly IsInfrastructureMessage isInfrastructureMessage;
10 |
11 | public InjectableMessageService(IsInfrastructureMessage isInfrastructureMessage)
12 | {
13 | MessageService.Message += OnIncomingMessage;
14 | this.isInfrastructureMessage = isInfrastructureMessage;
15 | }
16 |
17 | private void OnIncomingMessage(object sender, string rawMessage)
18 | {
19 | if (isInfrastructureMessage(rawMessage))
20 | {
21 | // Prevents Infrastructure messages from propagating downwards
22 | return;
23 | }
24 |
25 | IncomingMessage?.Invoke(sender, rawMessage);
26 | }
27 |
28 | public event EventHandler IncomingMessage;
29 |
30 | public void Dispose()
31 | {
32 | MessageService.Message -= OnIncomingMessage;
33 | }
34 |
35 | #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
36 | public async Task PostMessageAsync(string message)
37 | #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
38 | {
39 | #if DEBUG
40 | Console.WriteLine($"{nameof(InjectableMessageService)}.{nameof(PostMessageAsync)}('{message}')");
41 | #endif
42 | MessageService.PostMessage(message);
43 | }
44 |
45 | #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
46 | public async Task PostMessageJsDirectAsync(string message)
47 | #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
48 | {
49 | MessageService.PostMessageJsDirect(message);
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerCore/TaskRegister.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 | using System.Collections.Generic;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 |
7 | namespace BlazorWorker.WorkerCore
8 | {
9 | public class TaskRegister : ConcurrentDictionary>
10 | {
11 | public (long, TaskCompletionSource) CreateAndAdd()
12 | {
13 | var tcs = new TaskCompletionSource();
14 | var id = Interlocked.Increment(ref TaskRegister.idSource);
15 | var retries = 100;
16 | string errorMessage = string.Empty;
17 | while (!this.TryAdd(id, tcs))
18 | {
19 | if (retries < 0)
20 | {
21 | throw new InvalidOperationException(errorMessage);
22 | }
23 |
24 | errorMessage = $"{nameof(TaskRegister)}: Unable to add task id {id} as it already exists. This may happen if a task has not been properly disposed, awaited, or is running in an infinite loop.";
25 | id = Interlocked.Increment(ref TaskRegister.idSource);
26 | retries--;
27 | }
28 |
29 | if (retries < 100) {
30 | Console.Error.WriteLine(errorMessage);
31 | }
32 |
33 | return (id, tcs);
34 | }
35 | }
36 |
37 | public class TaskRegister : TaskRegister {
38 | public static long idSource;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerCore/WebAssemblyBindingsProxy/JSObject.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace BlazorWorker.WorkerCore.WebAssemblyBindingsProxy
4 | {
5 | internal class JSObject : IDisposable
6 | {
7 | public delegate object InvokeDelegate(string method, params object[] parameters);
8 | public delegate void DisposeDelegate();
9 | private readonly InvokeDelegate _invokeMethodDelegate;
10 | private readonly DisposeDelegate _disposeMethodDelegate;
11 |
12 | public JSObject(object target)
13 | {
14 | var type = target.GetType();
15 | var invokeMethod = type.GetMethod(nameof(Invoke));
16 | var disposeMethod = type.GetMethod(nameof(Dispose));
17 | _invokeMethodDelegate = Delegate.CreateDelegate(typeof(InvokeDelegate), target, invokeMethod) as InvokeDelegate;
18 | _disposeMethodDelegate = Delegate.CreateDelegate(typeof(DisposeDelegate), target, disposeMethod) as DisposeDelegate;
19 | }
20 |
21 | public object Invoke(string method, params object[] parameters) => _invokeMethodDelegate(method, parameters);
22 |
23 | public void Dispose() => _disposeMethodDelegate();
24 | }
25 | }
--------------------------------------------------------------------------------
/src/BlazorWorker.WorkerCore/WebAssemblyBindingsProxy/Runtime.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reflection;
3 |
4 | namespace BlazorWorker.WorkerCore.WebAssemblyBindingsProxy
5 | {
6 | internal class Runtime
7 | {
8 | #if NETSTANDARD21
9 | private const string assembly = "WebAssembly.Bindings";
10 | private static readonly string type = $"WebAssembly.{nameof(Runtime)}";
11 | #endif
12 |
13 | #if NET5_0_OR_GREATER
14 | private const string assembly = "System.Private.Runtime.InteropServices.JavaScript";
15 | private static readonly string type = $"System.Runtime.InteropServices.JavaScript.{nameof(Runtime)}";
16 | #endif
17 | private delegate object GetGlobalObjectDelegate(string globalObjectName);
18 |
19 | private static Assembly SourceAssembly => Assembly.Load(assembly)
20 | ?? throw new InvalidOperationException($"Unable to load assembly {assembly}");
21 |
22 | private static GetGlobalObjectDelegate _getGlobalObjectMethod =
23 | SourceAssembly
24 | .GetType(type)?
25 | .GetMethod(nameof(GetGlobalObject))?
26 | .CreateDelegate(typeof(GetGlobalObjectDelegate)) as GetGlobalObjectDelegate;
27 |
28 | public static object GetGlobalObject(string globalObjectName) => _getGlobalObjectMethod?.Invoke(globalObjectName)
29 | ?? throw new InvalidOperationException($"Unable to load method {type}.{nameof(GetGlobalObject)} from assembly {assembly}");
30 | }
31 | }
--------------------------------------------------------------------------------
/src/BlazorWorker/BlazorWorker.Core.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net7.0;net8.0
5 | Tor Knutsson (Tewr)
6 | BlazorWorker
7 | MIT
8 | Use dotnet Web Workers Threads in Blazor
9 | https://github.com/Tewr/BlazorWorker
10 | https://github.com/Tewr/BlazorWorker
11 | WebWorker Worker Process Threading Multithreading Blazor Isolation
12 | Tewr.BlazorWorker.Core
13 | Debug;Release;Nuget
14 | 4.2.0
15 | 4.2.0.0
16 | BlazorWorker.Core.xml
17 | icon.png
18 | false
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | all
43 |
44 |
45 |
46 |
47 | $(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage
48 | true
49 |
50 |
51 |
52 | 1701;1702;1591;CA1416
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/src/BlazorWorker/BlazorWorker.Core.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | BlazorWorker.Core
5 |
6 |
7 |
8 |
9 | Adds as a singleton service
10 | to the specified .
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/BlazorWorker/CoreInstanceService/CoreInstanceHandle.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | namespace BlazorWorker.Core.CoreInstanceService
5 | {
6 | internal class CoreInstanceHandle : IInstanceHandle
7 | {
8 | private Func onDispose;
9 |
10 | public CoreInstanceHandle(Func onDispose)
11 | {
12 | this.onDispose = onDispose;
13 | }
14 |
15 | public async ValueTask DisposeAsync()
16 | {
17 | await this.onDispose();
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/BlazorWorker/CoreInstanceService/ICoreInstanceService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | namespace BlazorWorker.Core.CoreInstanceService
5 | {
6 | public interface ICoreInstanceService
7 | {
8 | Task CreateInstance(Type type);
9 | Task CreateInstance();
10 |
11 | Task CreateInstance(Action options);
12 |
13 | Task CreateInstance(WorkerInitOptions options);
14 | }
15 |
16 | public interface IInstanceHandle : IAsyncDisposable {
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/BlazorWorker/CoreInstanceService/SimpleInstanceServiceExtension.cs:
--------------------------------------------------------------------------------
1 | using BlazorWorker.Core.SimpleInstanceService;
2 | using System;
3 |
4 | namespace BlazorWorker.Core.CoreInstanceService
5 | {
6 | public static class SimpleInstanceServiceExtension
7 | {
8 | public static ICoreInstanceService CreateCoreInstanceService(this IWorker source)
9 | {
10 | return new CoreInstanceService(new SimpleInstanceServiceProxy(source));
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/BlazorWorker/CoreInstanceService/WorkerException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace BlazorWorker.Core.CoreInstanceService
4 | {
5 | public abstract class WorkerException : Exception
6 | {
7 | public WorkerException(string message, string fullMessage) : base(message)
8 | {
9 | FullMessage = fullMessage;
10 | }
11 |
12 | public string FullMessage { get; }
13 |
14 | public override string ToString()
15 | {
16 | return $"{base.ToString()}{Environment.NewLine} --> Worker full exception: {FullMessage}";
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/BlazorWorker/CoreInstanceService/WorkerInstanceDisposeException.cs:
--------------------------------------------------------------------------------
1 | namespace BlazorWorker.Core.CoreInstanceService
2 | {
3 | public class WorkerInstanceDisposeException : WorkerException
4 | {
5 | public WorkerInstanceDisposeException(string message, string fullMessage)
6 | :base($"Error when disposing instance: {message}", fullMessage)
7 | {
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/BlazorWorker/CoreInstanceService/WorkerInstanceInitializeException.cs:
--------------------------------------------------------------------------------
1 | namespace BlazorWorker.Core.CoreInstanceService
2 | {
3 | public class WorkerInstanceInitializeException : WorkerException
4 | {
5 | public WorkerInstanceInitializeException(string message, string fullMessage)
6 | :base($"Error when initializing instance: {message}", fullMessage)
7 | {
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/BlazorWorker/DependencyHintAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 |
4 | namespace BlazorWorker.Core
5 | {
6 | internal class DependencyHintAttribute : Attribute
7 | {
8 | public DependencyHintAttribute(Type dependsOn, params Type[] dependsOnList)
9 | {
10 | DependsOn = new[] { dependsOn }.Concat(dependsOnList).ToArray();
11 | }
12 |
13 | public Type[] DependsOn { get; }
14 | }
15 | }
--------------------------------------------------------------------------------
/src/BlazorWorker/IWorkerFactory.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 |
3 | namespace BlazorWorker.Core
4 | {
5 | public interface IWorkerFactory
6 | {
7 | Task CreateAsync();
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/BlazorWorker/InstanceHandle.cs:
--------------------------------------------------------------------------------
1 | using BlazorWorker.WorkerCore;
2 | using System;
3 |
4 | namespace BlazorWorker.Core
5 | {
6 | public class InstanceHandle : IDisposable
7 | {
8 | public InstanceHandle(
9 | IWorkerMessageService messageService,
10 | Type serviceType,
11 | long identifier,
12 | Action onDispose)
13 | {
14 | MessageService = messageService;
15 | ServiceType = serviceType;
16 | Identifier = identifier;
17 | OnDispose = onDispose;
18 | }
19 |
20 | public IWorkerMessageService MessageService { get; }
21 |
22 | public Type ServiceType { get; }
23 | public long Identifier { get; }
24 | public Action OnDispose { get; }
25 |
26 | public void Dispose()
27 | {
28 | OnDispose?.Invoke();
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/BlazorWorker/MonoTypeHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Linq.Expressions;
4 | using System.Reflection;
5 |
6 | namespace BlazorWorker.Core
7 | {
8 | public static class MonoTypeHelper
9 | {
10 | public static MethodIdentifier GetStaticMethodId(string method)
11 | {
12 | var owningType = typeof(T);
13 | if (!owningType.GetRuntimeMethods().Any(x => x.IsStatic && x.Name == method))
14 | {
15 | throw new ArgumentException($"Method '{method}' is not a static member of type {owningType.Name}", nameof(method));
16 | }
17 | return new MethodIdentifier
18 | {
19 | AssemblyName = owningType.Assembly.GetName().Name,
20 | FullMethodName = $"{owningType.FullName}.{method}"
21 | };
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/BlazorWorker/ScriptLoader.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.JSInterop;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace BlazorWorker.Core
10 | {
11 | public class ScriptLoader
12 | {
13 | private static readonly IReadOnlyDictionary escapeScriptTextReplacements =
14 | new Dictionary { { @"\", @"\\" }, { "\r", @"\r" }, { "\n", @"\n" }, { "'", @"\'" }, { "\"", @"\""" } };
15 |
16 | private readonly IJSRuntime jsRuntime;
17 |
18 | public ScriptLoader(IJSRuntime jSRuntime)
19 | {
20 | this.jsRuntime = jSRuntime;
21 | }
22 |
23 | public async Task InitScript()
24 | {
25 | if (await IsLoaded())
26 | {
27 | return;
28 | }
29 |
30 | string scriptContent;
31 | var resourceName =
32 | "BlazorWorker.Core.BlazorWorker.js";
33 |
34 | var stream = this.GetType().Assembly.GetManifestResourceStream(resourceName);
35 | using (stream)
36 | {
37 | using (var streamReader = new StreamReader(stream))
38 | {
39 | scriptContent = await streamReader.ReadToEndAsync();
40 | }
41 | }
42 |
43 | await ExecuteRawScriptAsync(scriptContent);
44 | var loaderLoopBreaker = 0;
45 | while (!await IsLoaded())
46 | {
47 | loaderLoopBreaker++;
48 | await Task.Delay(100);
49 |
50 | // Fail after 3s not to block and hide any other possible error
51 | if (loaderLoopBreaker > 25)
52 | {
53 | throw new InvalidOperationException("Unable to initialize BlazorWorker.js");
54 | }
55 | }
56 | }
57 | private async Task IsLoaded()
58 | {
59 | return await jsRuntime.InvokeAsync("window.hasOwnProperty", "BlazorWorker");
60 | }
61 | private async Task ExecuteRawScriptAsync(string scriptContent)
62 | {
63 | scriptContent = escapeScriptTextReplacements.Aggregate(scriptContent, (r, pair) => r.Replace(pair.Key, pair.Value));
64 | var blob = $"URL.createObjectURL(new Blob([\"{scriptContent}\"],{{ \"type\": \"text/javascript\"}}))";
65 | var bootStrapScript = $"(function(){{var d = document; var s = d.createElement('script'); s.async=false; s.src={blob}; d.head.appendChild(s); d.head.removeChild(s);}})();";
66 | await jsRuntime.InvokeVoidAsync("eval", bootStrapScript);
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/BlazorWorker/SetupExtensions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.DependencyInjection;
2 |
3 | namespace BlazorWorker.Core
4 | {
5 | public static class SetupExtensions
6 | {
7 | ///
8 | /// Adds as a singleton service
9 | /// to the specified .
10 | ///
11 | ///
12 | public static IServiceCollection AddWorkerFactory(this IServiceCollection services)
13 | {
14 | services.AddSingleton();
15 | return services;
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/BlazorWorker/SimpleInstanceService/SimpleInstanceServiceProxy.cs:
--------------------------------------------------------------------------------
1 | using BlazorWorker.WorkerCore;
2 | using BlazorWorker.WorkerCore.SimpleInstanceService;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Threading.Tasks;
6 |
7 | namespace BlazorWorker.Core.SimpleInstanceService
8 | {
9 | public class SimpleInstanceServiceProxy : ISimpleInstanceService
10 | {
11 | private readonly IWorker worker;
12 | private readonly TaskRegister disposeResultRegister = new();
13 | private readonly TaskRegister initInstanceRegister = new();
14 | private TaskCompletionSource initWorker;
15 |
16 | public bool IsInitialized { get; internal set; }
17 |
18 | public SimpleInstanceServiceProxy(IWorker worker)
19 | {
20 | this.worker = worker;
21 | this.worker.IncomingMessage += OnIncomingMessage;
22 | }
23 |
24 | public async Task InitializeAsync(WorkerInitOptions options = null)
25 | {
26 | if (!IsInitialized)
27 | {
28 | if (!this.worker.IsInitialized)
29 | {
30 | initWorker = new TaskCompletionSource();
31 | await this.worker.InitAsync(options);
32 | if (this.worker is WorkerProxy proxy)
33 | {
34 | proxy.IsInitialized = true;
35 | }
36 | await this.initWorker.Task;
37 | }
38 |
39 | IsInitialized = true;
40 | }
41 | }
42 |
43 | private void OnIncomingMessage(object sender, string message)
44 | {
45 | #if DEBUG
46 | Console.WriteLine($"{nameof(SimpleInstanceServiceProxy)}:{message}");
47 | #endif
48 | if (DisposeResult.CanDeserialize(message)) {
49 | var result = DisposeResult.Deserialize(message);
50 | if (disposeResultRegister.TryRemove(result.CallId, out var taskCompletionSource))
51 | {
52 | taskCompletionSource.SetResult(result);
53 | }
54 | return;
55 | }
56 |
57 | if (InitServiceResult.CanDeserialize(message))
58 | {
59 | initWorker.SetResult(InitServiceResult.Deserialize(message));
60 | return;
61 | }
62 |
63 | if (InitInstanceResult.CanDeserialize(message))
64 | {
65 | var result = InitInstanceResult.Deserialize(message);
66 | if (initInstanceRegister.TryRemove(result.CallId, out var taskCompletionSource))
67 | {
68 | taskCompletionSource.SetResult(result);
69 | }
70 | return;
71 | }
72 | }
73 |
74 | public async Task DisposeInstance(DisposeInstanceRequest request)
75 | {
76 | var (callIdSource, tcs) = disposeResultRegister.CreateAndAdd();
77 | request.CallId = callIdSource;
78 | await this.worker.PostMessageAsync(request.Serialize());
79 | return await tcs.Task;
80 | }
81 |
82 | public async Task InitInstance(InitInstanceRequest request)
83 | {
84 | var (callIdSource, tcs) = initInstanceRegister.CreateAndAdd();
85 | request.CallId = callIdSource;
86 | await this.worker.PostMessageAsync(request.Serialize());
87 | return await tcs.Task;
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/BlazorWorker/WorkerFactory.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.JSInterop;
2 | using System.Threading.Tasks;
3 |
4 | namespace BlazorWorker.Core
5 | {
6 | public class WorkerFactory : IWorkerFactory
7 | {
8 | private readonly IJSRuntime jsRuntime;
9 |
10 | public WorkerFactory(IJSRuntime jsRuntime)
11 | {
12 | this.jsRuntime = jsRuntime;
13 | }
14 |
15 | #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
16 | public async Task CreateAsync()//WorkerInitOptions initOptions)
17 | #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
18 | {
19 | var worker = new WorkerProxy(jsRuntime);
20 | //await worker.InitAsync(initOptions);
21 | return worker;
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/BlazorWorker/WorkerProxy.cs:
--------------------------------------------------------------------------------
1 | using BlazorWorker.WorkerCore;
2 | using Microsoft.JSInterop;
3 | using System;
4 | using System.Threading.Tasks;
5 | namespace BlazorWorker.Core
6 | {
7 | [DependencyHint(typeof(MessageService))]
8 | public class WorkerProxy : IWorker
9 | {
10 | private readonly IJSRuntime jsRuntime;
11 | private readonly ScriptLoader scriptLoader;
12 | private static long idSource;
13 | private bool isDisposed = false;
14 | private static readonly MethodIdentifier messageMethod;
15 | private readonly DotNetObjectReference thisReference;
16 |
17 |
18 | public event EventHandler IncomingMessage;
19 | public bool IsInitialized { get; set; }
20 | static WorkerProxy()
21 | {
22 | var messageServiceType = typeof(MessageService);
23 | messageMethod = MonoTypeHelper.GetStaticMethodId(nameof(MessageService.OnMessage));
24 | //$"[{messageServiceType.Assembly.GetName().Name}]{messageServiceType.FullName}:{nameof(MessageService.OnMessage)}";
25 | }
26 |
27 | public WorkerProxy(IJSRuntime jsRuntime)
28 | {
29 | this.jsRuntime = jsRuntime;
30 | this.scriptLoader = new ScriptLoader(this.jsRuntime);
31 | this.Identifier = ++idSource;
32 | thisReference = DotNetObjectReference.Create(this);
33 | }
34 |
35 | public async ValueTask DisposeAsync()
36 | {
37 | if (!isDisposed)
38 | {
39 | await this.jsRuntime.InvokeVoidAsync("BlazorWorker.disposeWorker", this.Identifier);
40 | thisReference.Dispose();
41 | isDisposed = true;
42 | }
43 | }
44 |
45 | public async Task InitAsync(WorkerInitOptions initOptions)
46 | {
47 | await this.scriptLoader.InitScript();
48 |
49 | await this.jsRuntime.InvokeVoidAsync(
50 | "BlazorWorker.initWorker",
51 | this.Identifier,
52 | thisReference,
53 | new WorkerInitOptions {
54 |
55 | DependentAssemblyFilenames =
56 | WorkerProxyDependencies.DependentAssemblyFilenames,
57 | CallbackMethod = nameof(OnMessage),
58 | MessageEndPoint = messageMethod
59 | }.MergeWith(initOptions));
60 | }
61 |
62 | [JSInvokable]
63 | #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
64 | public async Task OnMessage(string message)
65 | #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
66 | {
67 | IncomingMessage?.Invoke(this, message);
68 | }
69 |
70 | public async Task PostMessageAsync(string message)
71 | {
72 | await jsRuntime.InvokeVoidAsync("BlazorWorker.postMessage", this.Identifier, message);
73 | }
74 |
75 | public async Task PostMessageJsDirectAsync(string message)
76 | {
77 | throw new NotSupportedException("JsDirect calls are only supported in the direction from worker to main js");
78 | }
79 |
80 |
81 |
82 | public long Identifier { get; }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/BlazorWorker/WorkerProxyDependencies.cs:
--------------------------------------------------------------------------------
1 | namespace BlazorWorker.Core
2 | {
3 | public class WorkerProxyDependencies
4 | {
5 | #if NETSTANDARD21
6 | public static readonly string[] DependentAssemblyFilenames = new[]
7 | {
8 | "BlazorWorker.WorkerCore.dll",
9 | "netstandard.dll",
10 | "mscorlib.dll",
11 | "WebAssembly.Bindings.dll",
12 | "System.dll",
13 | "System.Core.dll"
14 | };
15 | #endif
16 |
17 | #if NET5_0_OR_GREATER
18 | public static readonly string[] DependentAssemblyFilenames = new[]
19 | {
20 | "BlazorWorker.WorkerCore.dll",
21 | "netstandard.dll",
22 | "mscorlib.dll",
23 | "System.dll",
24 | "System.Core.dll",
25 | "System.Buffers.dll",
26 | "System.Collections.dll",
27 | "System.Configuration.dll",
28 | "System.Console.dll",
29 | "System.Core.dll",
30 | "System.Diagnostics.Debug.dll",
31 | "System.Diagnostics.DiagnosticSource.dll",
32 | "System.Diagnostics.StackTrace.dll",
33 | "System.Diagnostics.TraceSource.dll",
34 | "System.Dynamic.Runtime.dll",
35 | "System.Globalization.Calendars.dll",
36 | "System.Globalization.Extensions.dll",
37 | "System.Globalization.dll",
38 | "System.Linq.Expressions.dll",
39 | "System.Linq.Queryable.dll",
40 | "System.Linq.dll",
41 | "System.Memory.dll",
42 | "System.Numerics.Vectors.dll",
43 | "System.Numerics.dll",
44 | "System.ObjectModel.dll",
45 | "System.Private.CoreLib.dll",
46 | "System.Private.Runtime.InteropServices.JavaScript.dll",
47 | "System.Private.Uri.dll",
48 | "System.Private.Xml.Linq.dll",
49 | "System.Private.Xml.dll",
50 | "System.Reflection.DispatchProxy.dll",
51 | "System.Reflection.Extensions.dll",
52 | "System.Reflection.Metadata.dll",
53 | "System.Reflection.Primitives.dll",
54 | "System.Reflection.TypeExtensions.dll",
55 | "System.Reflection.dll",
56 | "System.Runtime.Extensions.dll",
57 | "System.Runtime.Handles.dll",
58 | "System.Runtime.InteropServices.RuntimeInformation.dll",
59 | "System.Runtime.InteropServices.dll",
60 | "System.Runtime.Intrinsics.dll",
61 | "System.Runtime.Loader.dll",
62 | "System.Runtime.Numerics.dll",
63 | "System.Runtime.dll",
64 | "System.Threading.Tasks.dll",
65 | "System.Threading.Thread.dll",
66 | "System.Threading.dll",
67 | };
68 | #endif
69 |
70 |
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/BlazorWorker/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Tewr/BlazorWorker/d5b86005df4d1a964beaed3ae3d03936221ee41b/src/BlazorWorker/icon.png
--------------------------------------------------------------------------------
/src/nuget.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/test/BlazorWorker.Extensions.JSRuntimeTests/BlazorWorker.Extensions.JSRuntimeTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net7.0
5 |
6 | false
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/test/BlazorWorker.Extensions.JSRuntimeTests/GenericNonPublicDelegateCacheTest.cs:
--------------------------------------------------------------------------------
1 | using BlazorWorker.Extensions.JSRuntime;
2 | using NUnit.Framework;
3 | using System.Collections.Generic;
4 | using static BlazorWorker.Extensions.JSRuntime.DotNetObjectReferenceTracker;
5 |
6 | namespace BlazorWorker.Extensions.JSRuntimeTests
7 | {
8 | public class GenericNonPublicDelegateCacheTests
9 | {
10 | [SetUp]
11 | public void Setup()
12 | {
13 | }
14 |
15 | [Test]
16 | public void GenericNonPublicDelegateCache_Returns_NonPublic_Delegates()
17 | {
18 | var genericDelegateCache = new GenericNonPublicDelegateCache(typeof(TypeWithPrivateThings));
19 | var instance = new TypeWithPrivateThings();
20 | var delegateTest = genericDelegateCache.GetDelegate, long, List>(instance, "DoThing");
21 |
22 | var result = delegateTest.Invoke(new List() { { 1L }, { 2L }, { 3L } });
23 | Assert.AreEqual(3+100, result);
24 | }
25 |
26 | public delegate long DoThingDelegate(List arg1);
27 |
28 | public class TypeWithPrivateThings {
29 | #pragma warning disable IDE0051 // Remove unused private members - accessed by reflection
30 | private long DoThing(List arg1)
31 | #pragma warning restore IDE0051 // Remove unused private members
32 | {
33 | return arg1.Count + 100;
34 | }
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------