mirror of
https://github.com/alexmickelson/canvasManagement.git
synced 2026-03-25 23:28:33 -06:00
have akka.net and signalr communicating
This commit is contained in:
62
Management.Web/AkkaService.cs
Normal file
62
Management.Web/AkkaService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
7
Management.Web/IActorBridge.cs
Normal file
7
Management.Web/IActorBridge.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Management.Actors;
|
||||
|
||||
public interface IActorBridge
|
||||
{
|
||||
void Tell(object message);
|
||||
Task<T> Ask<T>(object message);
|
||||
}
|
||||
@@ -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" />
|
||||
|
||||
64
Management.Web/Pages/CanvasRequestsQueue.razor
Normal file
64
Management.Web/Pages/CanvasRequestsQueue.razor
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -34,7 +34,9 @@
|
||||
StateHasChanged();
|
||||
}
|
||||
private int refreshKey;
|
||||
|
||||
}
|
||||
|
||||
<PageTitle>Index</PageTitle>
|
||||
|
||||
<br>
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
|
||||
@@ -8,3 +8,5 @@
|
||||
@using Microsoft.JSInterop
|
||||
@using Management.Web
|
||||
@using Management.Web.Shared
|
||||
@using Management.Actors
|
||||
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
||||
|
||||
50
Management/Actors/CanvasApiActor.cs
Normal file
50
Management/Actors/CanvasApiActor.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System.Diagnostics;
|
||||
using System.Net.Http.Headers;
|
||||
using Akka.Actor;
|
||||
using Akka.DependencyInjection;
|
||||
using Management.Services.Canvas;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
namespace Management.Actors;
|
||||
|
||||
|
||||
// RecieveActor configures messages in constructor
|
||||
// UntypedActor configures messages in an onrecieved function
|
||||
public class CanvasApiActor : ReceiveActor
|
||||
{
|
||||
private readonly IServiceScope _scope;
|
||||
private readonly ILogger<CanvasApiActor> _logger;
|
||||
private readonly IHubContext<SignalRHub> _hub;
|
||||
public CanvasApiActor(IServiceProvider serviceProvider) // props go here
|
||||
{
|
||||
_scope = serviceProvider.CreateScope();
|
||||
_logger = _scope.ServiceProvider.GetRequiredService<ILogger<CanvasApiActor>>();
|
||||
_hub = _scope.ServiceProvider.GetRequiredService<IHubContext<SignalRHub>>();
|
||||
|
||||
_logger.LogInformation("creating canvas actor");
|
||||
|
||||
var canvasService = _scope.ServiceProvider.GetRequiredService<CanvasService>();
|
||||
ReceiveAsync<GetModulesMessage>(async m =>
|
||||
{
|
||||
using var activity = m.Activity("canvas actor getting modules from canvas api");
|
||||
|
||||
var modules = await canvasService.Modules.GetModules(m.CanvasCourseId);
|
||||
Sender.Tell(new CanvasModulesMessage(m.RequestId, m.CanvasCourseId, modules, activity?.TraceId, activity?.SpanId));
|
||||
|
||||
await _hub.Clients.Client(m.ClientConnectionId).SendAsync("SentFromActor");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
protected override void PostStop()
|
||||
{
|
||||
_scope.Dispose();
|
||||
base.PostStop();
|
||||
}
|
||||
|
||||
// used to wrap the arguments in a comprehension for future instanciation of the actor
|
||||
// does this work with DI?
|
||||
// public static Props Props(CanvasService canvasService) =>
|
||||
// Akka.Actor.Props.Create(() => new CanvasApiActor(canvasService));
|
||||
|
||||
}
|
||||
15
Management/Actors/CanvasSupervisor.cs
Normal file
15
Management/Actors/CanvasSupervisor.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Akka.Actor;
|
||||
using Akka.DependencyInjection;
|
||||
|
||||
namespace Management.Actors;
|
||||
|
||||
public class CanvasSupervisor : ReceiveActor
|
||||
{
|
||||
// private IActorRef canvasApiActor;
|
||||
|
||||
// public CanvasSupervisor()
|
||||
// {
|
||||
// // DependencyResolver
|
||||
|
||||
// }
|
||||
}
|
||||
12
Management/Actors/Messages/CanvasModulesMesasge.cs
Normal file
12
Management/Actors/Messages/CanvasModulesMesasge.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System.Diagnostics;
|
||||
using CanvasModel.Modules;
|
||||
|
||||
namespace Management.Actors;
|
||||
|
||||
public sealed record CanvasModulesMessage(
|
||||
long RequestId,
|
||||
ulong CanvasCourseId,
|
||||
IEnumerable<CanvasModule> CanvasModules,
|
||||
ActivityTraceId? ParentTrace,
|
||||
ActivitySpanId? ParentSpan
|
||||
) : ITraceableMessage;
|
||||
11
Management/Actors/Messages/GetModulesMessage.cs
Normal file
11
Management/Actors/Messages/GetModulesMessage.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Management.Actors;
|
||||
|
||||
public sealed record GetModulesMessage(
|
||||
long RequestId,
|
||||
ulong CanvasCourseId,
|
||||
string ClientConnectionId,
|
||||
ActivityTraceId? ParentTrace,
|
||||
ActivitySpanId? ParentSpan
|
||||
) : ITraceableMessage;
|
||||
8
Management/Actors/Messages/ITraceableMessage.cs
Normal file
8
Management/Actors/Messages/ITraceableMessage.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
public interface ITraceableMessage
|
||||
{
|
||||
public ActivitySpanId? ParentSpan {get;}
|
||||
public ActivityTraceId? ParentTrace {get;}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,23 @@
|
||||
using System.Diagnostics;
|
||||
using System.Security.Policy;
|
||||
|
||||
public static class DiagnosticsConfig
|
||||
{
|
||||
public const string SourceName = "canvas-management-source";
|
||||
public static ActivitySource Source = new ActivitySource(SourceName);
|
||||
public readonly static ActivitySource Source = new(SourceName);
|
||||
|
||||
public static Activity? Activity(this ITraceableMessage message, string activityName)
|
||||
{
|
||||
if (message.ParentTrace != null && message.ParentSpan != null)
|
||||
{
|
||||
ActivityContext parentContext = new ActivityContext(
|
||||
(ActivityTraceId)message.ParentTrace,
|
||||
(ActivitySpanId)message.ParentSpan,
|
||||
ActivityTraceFlags.Recorded
|
||||
);
|
||||
|
||||
return Source?.StartActivity(activityName, ActivityKind.Internal, parentContext);
|
||||
}
|
||||
return Source?.StartActivity(activityName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,6 @@ public class CoursePlanner
|
||||
get => _localCourse;
|
||||
set
|
||||
{
|
||||
using var activity = DiagnosticsConfig.Source?.StartActivity("Loading Course");
|
||||
if (value == null)
|
||||
{
|
||||
_localCourse = null;
|
||||
|
||||
@@ -7,7 +7,11 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Akka" Version="1.5.18" />
|
||||
<PackageReference Include="Akka.DependencyInjection" Version="1.5.18" />
|
||||
<PackageReference Include="Markdig" Version="0.31.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Common" Version="8.0.3" />
|
||||
<PackageReference Include="microsoft.extensions.configuration.abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" />
|
||||
<PackageReference Include="RestSharp" Version="108.0.3" />
|
||||
|
||||
15
Management/SignalRHub.cs
Normal file
15
Management/SignalRHub.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
public class SignalRHub : Hub
|
||||
{
|
||||
public async Task SendMessage(string user, string message)
|
||||
{
|
||||
await Clients.All.SendAsync("ReceiveMessage", user, message);
|
||||
}
|
||||
public override Task OnConnectedAsync()
|
||||
{
|
||||
var connectionId = Context.ConnectionId;
|
||||
// Store the connection ID for later use, e.g., in a database or in-memory store
|
||||
return base.OnConnectedAsync();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user