Hello there,
I am trying to read data from a weight measurement device and send data to a server instantly with SignalR. The architecture consists of a console application that collects the measurement data and an ASP.NET Core API with a hub that distributes the data to connected clients (Blazor Server).
I have implemented this setup and tested it on my local machine. However, since I am not very familiar with SignalR, I believe there are several areas that require refactoring. Feedback on my code would be greatly appreciated.
One issue I have noticed is that the OnInitializedAsync method in App.razor is invoked twice when starting the Blazor Server application:
Initializing app...
Connecting to the hub...
BaseUri: https://localhost:7008
Connected to the hub successfully!
App initialized successfully!
Event handler removed successfully.
Initializing app...
Connecting to the hub...
BaseUri: https://localhost:7008
Connected to the hub successfully!
App initialized successfully!
Here are the relevant code snippets for the Blazor server application:
App.razor
@using WeighingDeviceDashboard.Service
@inject WeighingHubService WeighingHubService
<CascadingAuthenticationState>
<Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
@code {
private bool _isInitialized = false;
protected override async Task OnInitializedAsync()
{
if (!_isInitialized)
{
Console.WriteLine("Initializing app...");
await WeighingHubService.Connect();
Console.WriteLine("App initialized successfully!");
_isInitialized = true;
}
else
{
Console.WriteLine("App already initialized.");
}
}
}
Program.cs
using WeighingDeviceDashboard.Service;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddScoped<WeighingHubService>(sp => new WeighingHubService(
sp.GetService<IConfiguration>()));
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.Run();
WeighingHubService.cs
using Microsoft.AspNetCore.SignalR.Client;
namespace WeighingDeviceDashboard.Service
{
public class WeighingHubService
{
private readonly IConfiguration _configuration;
private HubConnection _hubConnection;
private bool _isConnected = false;
private readonly object _connectionLock = new object();
public WeighingHubService(IConfiguration? configuration)
{
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
}
public async Task Connect()
{
lock (_connectionLock)
{
if (_isConnected)
{
Console.WriteLine("Already connected to the hub.");
return;
}
}
try
{
Console.WriteLine("Connecting to the hub...");
_hubConnection = new HubConnectionBuilder()
.WithUrl($"{GetBaseUri()}/weighingHub")
.Build();
_hubConnection.On<Measurement>("ReceiveMeasurement", async (measurement) =>
{
if (MeasurementReceived != null)
{
await MeasurementReceived.Invoke(measurement);
}
});
await _hubConnection.StartAsync();
Console.WriteLine("Connected to the hub successfully!");
lock (_connectionLock)
{
_isConnected = true;
}
}
catch (Exception ex)
{
// Log the exception or handle it as needed
Console.WriteLine($"Error connecting to the hub: {ex.Message}");
//throw;
}
}
public event Func<Measurement, Task> MeasurementReceived;
private string GetBaseUri()
{
var baseUri = _configuration["BaseUri"];
Console.WriteLine($"BaseUri: {baseUri}");
if (string.IsNullOrEmpty(baseUri))
{
throw new InvalidOperationException("BaseUri configuration is missing or empty.");
}
return baseUri;
}
}
}
Index.razor
@page "/"
@using WeighingDeviceDashboard.Service
@inject WeighingHubService WeighingHubService
@implements IDisposable
<h1>Weighing Device Dashboard</h1>
@if (string.IsNullOrEmpty(_currentMeasurement))
{
<p>No measurements received yet.</p>
}
else
{
<p>Current Measurement: @_currentMeasurement</p>
}
@code {
private string _currentMeasurement = "";
private bool _isSubscribed = false;
private Func<Measurement, Task> _measurementReceivedHandler;
protected override async Task OnInitializedAsync()
{
if (!_isSubscribed)
{
_measurementReceivedHandler = async (measurement) =>
{
if (measurement != null)
{
try
{
Console.WriteLine($"Received measurement: {measurement.Value} {measurement.Unit}");
_currentMeasurement = $"{measurement.Value} {measurement.Unit}";
await InvokeAsync(StateHasChanged);
}
catch (Exception ex)
{
Console.WriteLine($"Error updating measurement: {ex.Message}");
}
}
else
{
Console.WriteLine("Received null measurement.");
}
};
WeighingHubService.MeasurementReceived += _measurementReceivedHandler;
_isSubscribed = true;
}
}
public void Dispose()
{
if (_isSubscribed && _measurementReceivedHandler != null)
{
WeighingHubService.MeasurementReceived -= _measurementReceivedHandler;
_isSubscribed = false;
_measurementReceivedHandler = null;
Console.WriteLine("Event handler removed successfully.");
}
else
{
Console.WriteLine("Event handler removal failed. Check for null references or subscription status.");
}
}
}