FluentSignals.SignalR
2.1.5
dotnet add package FluentSignals.SignalR --version 2.1.5
NuGet\Install-Package FluentSignals.SignalR -Version 2.1.5
<PackageReference Include="FluentSignals.SignalR" Version="2.1.5" />
<PackageVersion Include="FluentSignals.SignalR" Version="2.1.5" />
<PackageReference Include="FluentSignals.SignalR" />
paket add FluentSignals.SignalR --version 2.1.5
#r "nuget: FluentSignals.SignalR, 2.1.5"
#:package FluentSignals.SignalR@2.1.5
#addin nuget:?package=FluentSignals.SignalR&version=2.1.5
#tool nuget:?package=FluentSignals.SignalR&version=2.1.5
FluentSignals.SignalR
SignalR integration for FluentSignals providing real-time reactive resources with automatic reconnection and state management.
Installation
dotnet add package FluentSignals.SignalR
Features
- ๐ Real-time Updates - Reactive resources that update via SignalR
- ๐ Auto-reconnection - Automatic reconnection with configurable retry
- ๐ก Connection State - Observable connection state management
- ๐ฏ Filtered Subscriptions - Subscribe to specific message types
- ๐พ Message Buffering - Buffer messages during disconnection
- ๐งช Testable - Mock-friendly design for unit testing
Quick Start
using FluentSignals.SignalR;
// Create a SignalR resource
var stockPrices = new ResourceSignalR<StockPrice>(
hubUrl: "/hubs/stock",
eventName: "ReceiveStockPrice"
);
// Subscribe to updates
stockPrices.Subscribe(price =>
{
Console.WriteLine($"{price.Symbol}: ${price.Price}");
});
// Monitor connection state
stockPrices.ConnectionState.Subscribe(state =>
{
Console.WriteLine($"Connection: {state}");
});
// Start the connection
await stockPrices.StartAsync();
// Stop when done
await stockPrices.StopAsync();
Advanced Usage
Filtered Subscriptions
// Only receive updates for specific stocks
var techStocks = new ResourceSignalR<StockPrice>(
hubUrl: "/hubs/stock",
eventName: "ReceiveStockPrice",
filter: price => price.Sector == "Technology"
);
// Or use method chaining
var appleStock = new ResourceSignalR<StockPrice>("/hubs/stock", "ReceiveStockPrice")
.WithFilter(price => price.Symbol == "AAPL");
Connection Configuration
var resource = new ResourceSignalR<WeatherUpdate>(
hubUrl: "/hubs/weather",
eventName: "WeatherUpdate",
configureConnection: connection =>
{
connection.WithAutomaticReconnect(new[]
{
TimeSpan.Zero,
TimeSpan.FromSeconds(2),
TimeSpan.FromSeconds(10),
TimeSpan.FromSeconds(30)
});
connection.ServerTimeout = TimeSpan.FromSeconds(30);
connection.KeepAliveInterval = TimeSpan.FromSeconds(15);
}
);
Authentication
var secureResource = new ResourceSignalR<SecureData>(
hubUrl: "/hubs/secure",
eventName: "ReceiveData",
configureConnection: connection =>
{
connection.AccessTokenProvider = async () =>
{
var token = await GetAccessTokenAsync();
return token;
};
}
);
Multiple Event Handlers
public class ChatResource : ResourceSignalR<ChatMessage>
{
public ChatResource(string hubUrl) : base(hubUrl, "ReceiveMessage")
{
// Handle multiple events
Connection.On<UserInfo>("UserJoined", user =>
{
Console.WriteLine($"{user.Name} joined the chat");
});
Connection.On<string>("UserLeft", userId =>
{
Console.WriteLine($"User {userId} left the chat");
});
Connection.On<TypingIndicator>("UserTyping", indicator =>
{
Console.WriteLine($"{indicator.UserName} is typing...");
});
}
// Send messages
public async Task SendMessageAsync(string message)
{
await Connection.InvokeAsync("SendMessage", message);
}
public async Task NotifyTypingAsync()
{
await Connection.InvokeAsync("NotifyTyping");
}
}
Error Handling and Logging
var resource = new ResourceSignalR<Notification>(
hubUrl: "/hubs/notifications",
eventName: "ReceiveNotification",
logger: loggerFactory.CreateLogger<ResourceSignalR<Notification>>()
);
// Handle connection errors
resource.ConnectionError.Subscribe(error =>
{
logger.LogError(error, "SignalR connection error");
});
// Handle reconnection events
resource.Connection.Reconnecting += error =>
{
logger.LogWarning("SignalR reconnecting: {Error}", error?.Message);
return Task.CompletedTask;
};
resource.Connection.Reconnected += connectionId =>
{
logger.LogInformation("SignalR reconnected: {ConnectionId}", connectionId);
return Task.CompletedTask;
};
Message Buffering
public class BufferedResource<T> : ResourceSignalR<T>
{
private readonly Queue<T> _messageBuffer = new();
private readonly int _maxBufferSize;
public BufferedResource(string hubUrl, string eventName, int maxBufferSize = 100)
: base(hubUrl, eventName)
{
_maxBufferSize = maxBufferSize;
}
protected override void OnMessageReceived(T message)
{
if (ConnectionState.Value != HubConnectionState.Connected)
{
// Buffer messages during disconnection
_messageBuffer.Enqueue(message);
if (_messageBuffer.Count > _maxBufferSize)
{
_messageBuffer.Dequeue();
}
}
else
{
// Process buffered messages
while (_messageBuffer.Count > 0)
{
base.OnMessageReceived(_messageBuffer.Dequeue());
}
base.OnMessageReceived(message);
}
}
}
Integration with Blazor
@using FluentSignals.SignalR
@implements IAsyncDisposable
<h3>Live Stock Prices</h3>
@if (_stockResource?.ConnectionState.Value == HubConnectionState.Connected)
{
@if (_stockResource.Value != null)
{
<div class="stock-price">
<span class="symbol">@_stockResource.Value.Symbol</span>
<span class="price">$@_stockResource.Value.Price.ToString("F2")</span>
<span class="change @(_stockResource.Value.Change >= 0 ? "positive" : "negative")">
@(_stockResource.Value.Change >= 0 ? "+" : "")@_stockResource.Value.Change.ToString("F2")%
</span>
</div>
}
}
else
{
<div class="connection-status">
Connecting... (@_stockResource?.ConnectionState.Value)
</div>
}
@code {
private ResourceSignalR<StockPrice>? _stockResource;
protected override async Task OnInitializedAsync()
{
_stockResource = new ResourceSignalR<StockPrice>(
"/hubs/stock",
"ReceiveStockPrice",
filter: price => price.Symbol == "AAPL"
);
_stockResource.Subscribe(_ => InvokeAsync(StateHasChanged));
await _stockResource.StartAsync();
}
public async ValueTask DisposeAsync()
{
if (_stockResource != null)
{
await _stockResource.StopAsync();
_stockResource.Dispose();
}
}
}
Testing
public class SignalRResourceTests
{
[Fact]
public async Task ResourceSignalR_UpdatesValueOnMessage()
{
// Arrange
var mockConnection = new Mock<HubConnection>();
var resource = new TestableResourceSignalR<TestData>(mockConnection.Object);
var testData = new TestData { Id = 1, Name = "Test" };
// Act
await resource.SimulateMessage(testData);
// Assert
Assert.Equal(testData, resource.Value);
Assert.False(resource.IsLoading.Value);
}
}
public class TestableResourceSignalR<T> : ResourceSignalR<T>
{
public TestableResourceSignalR(HubConnection connection)
: base("/test", "TestEvent")
{
// Override with mock connection
Connection = connection;
}
public Task SimulateMessage(T message)
{
OnMessageReceived(message);
return Task.CompletedTask;
}
}
Best Practices
- Dispose Resources: Always dispose SignalR resources to close connections
- Handle Reconnection: Subscribe to connection state changes
- Use Filters: Filter messages on the client to reduce processing
- Authentication: Configure authentication before starting connection
- Error Handling: Always handle connection errors gracefully
- Testing: Use mock connections for unit testing
License
Part of the FluentSignals project. See LICENSE for details.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net9.0
- FluentSignals (>= 2.1.5)
- Microsoft.AspNetCore.SignalR.Client (>= 9.0.6)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on FluentSignals.SignalR:
Package | Downloads |
---|---|
FluentSignals.Blazor
Blazor integration for FluentSignals - A reactive state management library. Includes SignalBus for component communication, HTTP resource components, typed resource factories, and Blazor-specific helpers. |
GitHub repositories
This package is not used by any popular GitHub repositories.
v2.1.3: Version bump to align with core library updates. Maintains all SignalR integration features with automatic reconnection and state management.