FluentSignals.SignalR 2.1.1

There is a newer version of this package available.
See the version list below for details.
dotnet add package FluentSignals.SignalR --version 2.1.1
                    
NuGet\Install-Package FluentSignals.SignalR -Version 2.1.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="FluentSignals.SignalR" Version="2.1.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="FluentSignals.SignalR" Version="2.1.1" />
                    
Directory.Packages.props
<PackageReference Include="FluentSignals.SignalR" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add FluentSignals.SignalR --version 2.1.1
                    
#r "nuget: FluentSignals.SignalR, 2.1.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package FluentSignals.SignalR@2.1.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=FluentSignals.SignalR&version=2.1.1
                    
Install as a Cake Addin
#tool nuget:?package=FluentSignals.SignalR&version=2.1.1
                    
Install as a Cake Tool

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

  1. Dispose Resources: Always dispose SignalR resources to close connections
  2. Handle Reconnection: Subscribe to connection state changes
  3. Use Filters: Filter messages on the client to reduce processing
  4. Authentication: Configure authentication before starting connection
  5. Error Handling: Always handle connection errors gracefully
  6. Testing: Use mock connections for unit testing

License

Part of the FluentSignals project. See LICENSE for details.

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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.

Version Downloads Last Updated
2.1.5 113 7/17/2025
2.1.4 115 7/17/2025
2.1.3 118 7/15/2025
2.1.2 121 7/15/2025
2.1.1 144 7/8/2025
2.1.0 143 7/8/2025
2.0.0 141 6/29/2025

Initial release of FluentSignals.SignalR - SignalR integration extracted from FluentSignals core library.