have akka.net and signalr communicating

This commit is contained in:
2024-03-19 17:17:49 -06:00
parent 09e97fb2fc
commit 9ffd60ac84
17 changed files with 322 additions and 29 deletions

View File

@@ -0,0 +1,62 @@
using Akka.Actor;
using Akka.DependencyInjection;
namespace Management.Actors;
public class AkkaService : IHostedService, IActorBridge
{
private ActorSystem? _actorSystem;
private readonly IConfiguration _configuration;
private readonly IServiceProvider _serviceProvider;
private IActorRef? _canvasApiActor;
private readonly IHostApplicationLifetime _applicationLifetime;
public AkkaService(IServiceProvider serviceProvider, IHostApplicationLifetime appLifetime, IConfiguration configuration)
{
_serviceProvider = serviceProvider;
_applicationLifetime = appLifetime;
_configuration = configuration;
}
public Task StartAsync(CancellationToken cancellationToken)
{
var bootstrap = BootstrapSetup.Create();
var dependencyInjectionSetup = DependencyResolverSetup.Create(_serviceProvider);
var mergedSystemSetup = bootstrap.And(dependencyInjectionSetup);
_actorSystem = ActorSystem.Create("canvas-managment-actors", mergedSystemSetup);
// start top level supervisor actor
// working here https://getakka.net/articles/actors/dependency-injection.html#integrating-with-microsoftextensionsdependencyinjection
var apiActorProps = DependencyResolver.For(_actorSystem).Props<CanvasApiActor>();
_canvasApiActor = _actorSystem.ActorOf(apiActorProps, "canvas-api");
// crash if the actor system crashes, awaiting never returns...
#pragma warning disable CA2016 // Forward the 'CancellationToken' parameter to methods
_actorSystem.WhenTerminated.ContinueWith(tr =>
{
_applicationLifetime.StopApplication();
});
#pragma warning restore CA2016 // Forward the 'CancellationToken' parameter to methods
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
// add more methods to interact with actor processes, make them part of an interface
// these are the most generic forwarding of messages
public void Tell(object message)
{
_canvasApiActor.Tell(message);
}
public Task<T> Ask<T>(object message)
{
return _canvasApiActor.Ask<T>(message);
}
}

View File

@@ -0,0 +1,7 @@
namespace Management.Actors;
public interface IActorBridge
{
void Tell(object message);
Task<T> Ask<T>(object message);
}

View File

@@ -5,9 +5,12 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Akka" Version="1.5.18" />
<PackageReference Include="Akka.DependencyInjection" Version="1.5.18" />
<PackageReference Include="BlazorMonaco" Version="3.0.0" />
<PackageReference Include="dotenv.net" Version="3.1.2" />
<PackageReference Include="Markdig" Version="0.31.0" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.3" />
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.7.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.7.0" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.7.0" />

View File

@@ -0,0 +1,64 @@
@page "/test"
@rendermode InteractiveServer
@using Microsoft.AspNetCore.SignalR.Client
@inject CanvasService canvas
@inject CoursePlanner planner
@inject FileStorageManager fileStorageManager
@inject IActorBridge bridge
@inject NavigationManager Navigation
@code {
private HubConnection? hubConnection;
public string? CourseName = "1400";
@* private bool loading = true; *@
protected override async Task OnInitializedAsync()
{
if (planner.LocalCourse == null)
{
System.Diagnostics.Activity.Current = null;
using var activity = DiagnosticsConfig.Source?.StartActivity("Loading Course");
activity?.AddTag("CourseName", CourseName);
var courses = await fileStorageManager.LoadSavedCourses();
planner.LocalCourse = courses.First(c => c.Settings.Name == CourseName);
}
Console.WriteLine(Navigation.BaseUri + "SignalRHub");
hubConnection = new HubConnectionBuilder()
.WithUrl(Navigation.BaseUri + "SignalRHub")
.WithAutomaticReconnect()
.Build();
hubConnection.On("SentFromActor", () =>
{
Console.WriteLine("recieved from actor");
});
await hubConnection.StartAsync();
base.OnInitialized();
@* loading = false; *@
}
private async Task SendAkkaMessage()
{
System.Diagnostics.Activity.Current = null;
using var activity = DiagnosticsConfig.Source?.StartActivity("sending akka message from blazor");
if (planner.LocalCourse != null && planner.LocalCourse.Settings.CanvasId != null && hubConnection?.ConnectionId != null)
{
ulong id = (ulong)planner.LocalCourse.Settings.CanvasId;
var message = new GetModulesMessage(0, id, (string)hubConnection.ConnectionId, activity?.TraceId, ParentSpan:
activity?.SpanId);
var response = await bridge.Ask<CanvasModulesMessage>(message);
Console.WriteLine(response);
}
}
}
<button @onclick=SendAkkaMessage>test akka</button>

View File

@@ -6,12 +6,14 @@
@using Management.Web.Pages.Course.Module.ModuleItems
@using Management.Web.Shared.Components
@inject FileStorageManager fileStorageManager
@inject CanvasService canvas
@inject CoursePlanner planner
@inject NavigationManager navigtion
@inject IConfiguration config
@code {
[Parameter]
public string? CourseName { get; set; }
@@ -22,6 +24,9 @@
{
if (planner.LocalCourse == null)
{
System.Diagnostics.Activity.Current = null;
using var activity = DiagnosticsConfig.Source?.StartActivity("Loading Course");
activity?.AddTag("CourseName", CourseName);
var courses = await fileStorageManager.LoadSavedCourses();
planner.LocalCourse = courses.First(c => c.Settings.Name == CourseName);
}
@@ -38,37 +43,39 @@
}
<PageTitle>@CourseName</PageTitle>
<div style="height: 100vh;">
@if (loading)
{
<Spinner />
}
@if (loading)
{
<Spinner />
}
@if (planner.LocalCourse != null)
{
<div class="pb-3 d-flex justify-content-between" style="height: 4em;">
<div class="my-auto">
<button @onclick="selectNewCourse" class="btn btn-primary">
Select New Course
</button>
<CourseSettings />
<a class="btn btn-outline-secondary" target="_blank"
href="@($"{config["CANVAS_URL"]}/courses/{planner.LocalCourse.Settings.CanvasId}")">
View In Canvas
</a>
<div class="my-auto ms-2 d-inline">
@planner.LocalCourse.Settings.Name
@if (planner.LocalCourse != null)
{
<div class="pb-3 d-flex justify-content-between" style="height: 4em;">
<div class="my-auto">
<button @onclick="selectNewCourse" class="btn btn-primary">
Select New Course
</button>
<CourseSettings />
<a class="btn btn-outline-secondary" target="_blank"
href="@($"{config["CANVAS_URL"]}/courses/{planner.LocalCourse.Settings.CanvasId}")">
View In Canvas
</a>
<div class="my-auto ms-2 d-inline">
@planner.LocalCourse.Settings.Name
</div>
</div>
@if (planner.LoadingCanvasData)
{
<Spinner />
}
</div>
</div>
@if (planner.LoadingCanvasData)
{
<Spinner />
}
</div>
<CourseDetails />
}
<CourseDetails />
}
</div>

View File

@@ -34,7 +34,9 @@
StateHasChanged();
}
private int refreshKey;
}
<PageTitle>Index</PageTitle>
<br>

View File

@@ -11,8 +11,10 @@ global using Management.Services.Canvas;
global using Management.Web.Shared;
global using Management.Web.Shared.Components;
using dotenv.net;
using Management.Actors;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.ResponseCompression;
using OpenTelemetry;
using OpenTelemetry.Logs;
using OpenTelemetry.Metrics;
@@ -91,6 +93,18 @@ builder.Services.AddScoped<DragContainer>();
builder.Services.AddSingleton<FileConfiguration>();
// exposing actor service to controllers
builder.Services.AddSingleton<IActorBridge, AkkaService>();
// starting actor service while enabling it to use dependency injection
builder.Services.AddHostedService<AkkaService>(sp => (AkkaService)sp.GetRequiredService<IActorBridge>());
builder.Services.AddResponseCompression(opts =>
{
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[] { "application/octet-stream" });
});
builder.Services.AddSignalR(e =>
{
e.MaximumReceiveMessageSize = 102400000;
@@ -113,8 +127,10 @@ app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseResponseCompression();
app.MapBlazorHub();
app.MapHub<SignalRHub>("/SignalRHub");
app.MapFallbackToPage("/_Host");

View File

@@ -8,3 +8,5 @@
@using Microsoft.JSInterop
@using Management.Web
@using Management.Web.Shared
@using Management.Actors
@using static Microsoft.AspNetCore.Components.Web.RenderMode