diff --git a/docs/guide/messaging/transports/signalr.md b/docs/guide/messaging/transports/signalr.md
index 50e32befc..43729c7d9 100644
--- a/docs/guide/messaging/transports/signalr.md
+++ b/docs/guide/messaging/transports/signalr.md
@@ -107,6 +107,37 @@ return await app.RunJasperFxCommands(args);
snippet source | anchor
+## Custom hubs
+
+If the default `WolverineHub` isn't enough, you can provide a custom Hub that will be used for all received messages:
+
+
+
+```cs
+builder.Services.AddSignalR();
+builder.Host.UseWolverine(opts =>
+{
+ opts.ServiceName = "Server";
+
+ // Hooking up the SignalR messaging transport
+ // in Wolverine using a custom hub
+ opts.UseSignalR();
+
+ // A message for testing
+ opts.PublishMessage().ToSignalR();
+});
+
+var app = builder.Build();
+
+// Syntactic sugar, really just doing:
+// app.MapHub("/messages");
+app.MapWolverineSignalRHub();
+```
+snippet source | anchor
+
+
+Custom hubs must still inherit from `WolverineHub`. It's possible to override `ReceiveMessage`, but if you don't invoke the base functionality you're gonna have a bad time.
+
## Messages and Serialization
For the message routing above, you'll notice that I utilized a marker interface just to facilitate message routing
diff --git a/src/Samples/WolverineChat/Program.cs b/src/Samples/WolverineChat/Program.cs
index e4436dd24..e76592821 100644
--- a/src/Samples/WolverineChat/Program.cs
+++ b/src/Samples/WolverineChat/Program.cs
@@ -68,9 +68,16 @@
app.UseAuthorization();
+#if NET9_0_OR_GREATER
app.MapStaticAssets();
app.MapRazorPages()
.WithStaticAssets();
+#endif
+#if NET8_0
+app.UseStaticFiles();
+app.MapRazorPages();
+#endif
+
// This line puts the SignalR hub for Wolverine at the
// designated route for your clients
diff --git a/src/Transports/SignalR/Wolverine.SignalR.Tests/WebSocketTestContext.cs b/src/Transports/SignalR/Wolverine.SignalR.Tests/WebSocketTestContext.cs
index 249abdddf..b971af240 100644
--- a/src/Transports/SignalR/Wolverine.SignalR.Tests/WebSocketTestContext.cs
+++ b/src/Transports/SignalR/Wolverine.SignalR.Tests/WebSocketTestContext.cs
@@ -1,6 +1,7 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Wolverine.Runtime;
@@ -14,7 +15,7 @@ namespace Wolverine.SignalR.Tests;
public abstract class WebSocketTestContext : IAsyncLifetime
{
protected WebApplication theWebApp;
- private readonly int Port = PortFinder.GetAvailablePort();
+ protected readonly int Port = PortFinder.GetAvailablePort();
protected readonly Uri clientUri;
private readonly List _clientHosts = new();
@@ -34,12 +35,12 @@ public async Task InitializeAsync()
});
#endregion
-
+
builder.Services.AddSignalR();
builder.Host.UseWolverine(opts =>
{
opts.ServiceName = "Server";
-
+
// Hooking up the SignalR messaging transport
// in Wolverine
opts.UseSignalR();
@@ -54,11 +55,11 @@ public async Task InitializeAsync()
});
var app = builder.Build();
-
+
// Syntactic sure, really just doing:
// app.MapHub("/messages");
app.MapWolverineSignalRHub();
-
+
await app.StartAsync();
// Remember this, because I'm going to use it in test code
@@ -76,13 +77,13 @@ public async Task StartClientHost(string serviceName = "Client")
.UseWolverine(opts =>
{
opts.ServiceName = serviceName;
-
+
opts.UseClientToSignalR(Port);
-
+
opts.PublishMessage().ToSignalRWithClient(Port);
-
+
opts.PublishMessage().ToSignalRWithClient(Port);
-
+
opts.Publish(x =>
{
x.MessagesImplementing();
@@ -91,7 +92,7 @@ public async Task StartClientHost(string serviceName = "Client")
}).StartAsync();
#endregion
-
+
_clientHosts.Add(host);
return host;
@@ -106,7 +107,96 @@ public async Task DisposeAsync()
await clientHost.StopAsync();
}
}
-
+
+}
+
+public abstract class WebSocketTestContextWithCustomHub : IAsyncLifetime where THub : WolverineHub
+{
+ protected WebApplication theWebApp;
+ protected readonly int Port = PortFinder.GetAvailablePort();
+ protected readonly Uri clientUri;
+
+ private readonly List _clientHosts = new();
+
+ public WebSocketTestContextWithCustomHub()
+ {
+ clientUri = new Uri($"http://localhost:{Port}/messages");
+ }
+
+ public async Task InitializeAsync()
+ {
+ var builder = WebApplication.CreateBuilder();
+
+ builder.WebHost.ConfigureKestrel(opts =>
+ {
+ opts.ListenLocalhost(Port);
+ });
+
+ #region sample_custom_signalr_hub
+ builder.Services.AddSignalR();
+ builder.Host.UseWolverine(opts =>
+ {
+ opts.ServiceName = "Server";
+
+ // Hooking up the SignalR messaging transport
+ // in Wolverine using a custom hub
+ opts.UseSignalR();
+
+ // A message for testing
+ opts.PublishMessage().ToSignalR();
+ });
+
+ var app = builder.Build();
+
+ // Syntactic sugar, really just doing:
+ // app.MapHub("/messages");
+ app.MapWolverineSignalRHub();
+ #endregion
+
+ await app.StartAsync();
+
+ // Remember this, because I'm going to use it in test code
+ // later
+ theWebApp = app;
+ }
+
+ // This starts up a new host to act as a client to the SignalR
+ // server for testing
+ public async Task StartClientHost(string serviceName = "Client")
+ {
+ var host = await Host.CreateDefaultBuilder()
+ .UseWolverine(opts =>
+ {
+ opts.ServiceName = serviceName;
+
+ opts.UseClientToSignalR(Port);
+
+ opts.PublishMessage().ToSignalRWithClient(Port);
+
+ opts.PublishMessage().ToSignalRWithClient(Port);
+
+ opts.Publish(x =>
+ {
+ x.MessagesImplementing();
+ x.ToSignalRWithClient(Port);
+ });
+ }).StartAsync();
+
+ _clientHosts.Add(host);
+
+ return host;
+ }
+
+ public virtual async Task DisposeAsync()
+ {
+ await theWebApp.StopAsync();
+
+ foreach (var clientHost in _clientHosts)
+ {
+ await clientHost.StopAsync();
+ }
+ }
+
}
public record ToFirst(string Name) : WebSocketMessage;
diff --git a/src/Transports/SignalR/Wolverine.SignalR.Tests/custom_hub.cs b/src/Transports/SignalR/Wolverine.SignalR.Tests/custom_hub.cs
new file mode 100644
index 000000000..39ec94fbd
--- /dev/null
+++ b/src/Transports/SignalR/Wolverine.SignalR.Tests/custom_hub.cs
@@ -0,0 +1,80 @@
+using JasperFx.Core;
+using Microsoft.AspNetCore.SignalR;
+using Microsoft.Extensions.Logging;
+using Shouldly;
+using Wolverine.SignalR.Client;
+using Wolverine.SignalR.Internals;
+using Wolverine.Tracking;
+
+namespace Wolverine.SignalR.Tests;
+
+public class custom_hub : WebSocketTestContextWithCustomHub
+{
+ public static List ReceivedJson = new();
+
+ public override async Task DisposeAsync()
+ {
+ ReceivedJson.Clear();
+ await base.DisposeAsync();
+ }
+
+ [Fact]
+ public async Task publishing_from_server_uses_custom_hub()
+ {
+ var client = await StartClientHost();
+
+ var tracked = await theWebApp
+ .TrackActivity()
+ .IncludeExternalTransports()
+ .AlsoTrack(client)
+ .Timeout(10.Seconds())
+ .SendMessageAndWaitAsync(new FromSecond("Hollywood Brown"));
+
+ var record = tracked.Received.SingleRecord();
+ record.ServiceName.ShouldBe("Client");
+ record.Envelope.ShouldNotBeNull();
+ record.Envelope.Destination.ShouldBe(new Uri($"signalr-client://localhost:{Port}/messages"));
+ record.Message.ShouldBeOfType()
+ .Name.ShouldBe("Hollywood Brown");
+ }
+
+ [Fact]
+ public async Task publishing_from_client_uses_custom_hub()
+ {
+ // This is an IHost that has the SignalR Client
+ // transport configured to connect to a SignalR
+ // server in the "theWebApp" IHost
+ using var client = await StartClientHost();
+
+ var tracked = await client
+ .TrackActivity()
+ .IncludeExternalTransports()
+ .AlsoTrack(theWebApp)
+ .Timeout(10.Seconds())
+ .ExecuteAndWaitAsync(c => c.SendViaSignalRClient(clientUri, new ToSecond("Hollywood Brown")));
+
+ var record = tracked.Received.SingleRecord();
+ record.ServiceName.ShouldBe("Server");
+ record.Envelope.ShouldNotBeNull();
+ record.Envelope.Destination.ShouldBe(new Uri("signalr://wolverine"));
+ record.Message.ShouldBeOfType()
+ .Name.ShouldBe("Hollywood Brown");
+
+ ReceivedJson.Count.ShouldBe(1);
+ }
+}
+
+public class CustomWolverineHub(SignalRTransport endpoint, ILogger logger) : WolverineHub(endpoint)
+{
+ public override Task OnConnectedAsync()
+ {
+ logger.LogInformation("Client connected with ID {ConnectionId}", Context.ConnectionId);
+ return base.OnConnectedAsync();
+ }
+
+ public override Task ReceiveMessage(string json)
+ {
+ custom_hub.ReceivedJson.Add(json);
+ return base.ReceiveMessage(json);
+ }
+}
diff --git a/src/Transports/SignalR/Wolverine.SignalR/Client/SignalRClientEndpoint.cs b/src/Transports/SignalR/Wolverine.SignalR/Client/SignalRClientEndpoint.cs
index c02f4f70e..a9e0323fe 100644
--- a/src/Transports/SignalR/Wolverine.SignalR/Client/SignalRClientEndpoint.cs
+++ b/src/Transports/SignalR/Wolverine.SignalR/Client/SignalRClientEndpoint.cs
@@ -1,8 +1,8 @@
-using System.Diagnostics;
-using System.Text.Json;
using JasperFx.Core;
+using Microsoft.AspNetCore.SignalR;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.Logging;
+using System.Text.Json;
using Wolverine.Configuration;
using Wolverine.Runtime;
using Wolverine.Runtime.Interop;
@@ -23,8 +23,8 @@ internal static Uri TranslateToWolverineUri(Uri uri)
{
return new Uri($"{SignalRClientTransport.ProtocolName}://{uri.Host}:{uri.Port}/{uri.Segments.Last()}");
}
-
- public SignalRClientEndpoint(Uri uri, SignalRClientTransport parent) : base(TranslateToWolverineUri(uri),EndpointRole.Application)
+
+ public SignalRClientEndpoint(Uri uri, SignalRClientTransport parent) : base(TranslateToWolverineUri(uri), EndpointRole.Application)
{
_parent = parent;
SignalRUri = uri;
@@ -49,7 +49,7 @@ public override async ValueTask BuildListenerAsync(IWolverineRuntime
_mapper ??= BuildCloudEventsMapper(runtime, JsonOptions);
Logger = runtime.LoggerFactory.CreateLogger();
-
+
await _connection.StartAsync();
_connection.On(SignalRTransport.DefaultOperation, [typeof(string)], (args =>
@@ -61,13 +61,13 @@ public override async ValueTask BuildListenerAsync(IWolverineRuntime
Logger.LogDebug("Received an empty message, ignoring");
return Task.CompletedTask;
}
-
+
return ReceiveAsync(json);
}));
return this;
}
-
+
internal async Task ReceiveAsync(string json)
{
if (Receiver == null || _mapper == null) return;
@@ -88,7 +88,7 @@ internal async Task ReceiveAsync(string json)
Logger?.LogError(e, "Unable to receive a message from SignalR");
}
}
-
+
public IReceiver? Receiver { get; private set; }
protected override ISender CreateSender(IWolverineRuntime runtime)
@@ -142,7 +142,7 @@ public async ValueTask SendAsync(Envelope envelope)
{
if (_mapper == null || _connection == null)
throw new InvalidOperationException($"SignalR Client {Uri} is not initialized");
-
+
var json = _mapper.WriteToString(envelope);
await _connection.InvokeAsync(nameof(WolverineHub.ReceiveMessage), json);
diff --git a/src/Transports/SignalR/Wolverine.SignalR/Client/SignalRClientExtensions.cs b/src/Transports/SignalR/Wolverine.SignalR/Client/SignalRClientExtensions.cs
index aad848c52..c732eaadd 100644
--- a/src/Transports/SignalR/Wolverine.SignalR/Client/SignalRClientExtensions.cs
+++ b/src/Transports/SignalR/Wolverine.SignalR/Client/SignalRClientExtensions.cs
@@ -1,5 +1,6 @@
using System.Text.Json;
using JasperFx.Core.Reflection;
+using Microsoft.AspNetCore.SignalR;
using Wolverine.Configuration;
namespace Wolverine.SignalR.Client;
@@ -38,9 +39,7 @@ public static Uri UseClientToSignalR(this WolverineOptions options, string url,
/// Default is messages. Route pattern where you have mapped the WolverineHub
///
public static Uri UseClientToSignalR(this WolverineOptions options, int port, string route = "messages")
- {
- return options.UseClientToSignalR($"http://localhost:{port}/{route}");
- }
+ => options.UseClientToSignalR($"http://localhost:{port}/{route}");
///
/// Send a message via a SignalR Client for the given server Uri in the format "http://localhost:[port]/[hub url]"
@@ -79,7 +78,7 @@ public static void ToSignalRWithClient(this IPublishToExpression publishing, int
var url = $"http://localhost:{port}/{relativeUrl}";
publishing.ToSignalRWithClient(url);
}
-
+
///
/// Route messages via a SignalR Client pointed at the supplied absolute Url
///
diff --git a/src/Transports/SignalR/Wolverine.SignalR/Client/SignalRClientTransport.cs b/src/Transports/SignalR/Wolverine.SignalR/Client/SignalRClientTransport.cs
index 081c22345..da4173a60 100644
--- a/src/Transports/SignalR/Wolverine.SignalR/Client/SignalRClientTransport.cs
+++ b/src/Transports/SignalR/Wolverine.SignalR/Client/SignalRClientTransport.cs
@@ -1,4 +1,3 @@
-using System.Diagnostics;
using JasperFx.Core;
using Wolverine.Transports;
@@ -7,7 +6,7 @@ namespace Wolverine.SignalR.Client;
public class SignalRClientTransport : TransportBase
{
public static readonly string ProtocolName = "signalr-client";
-
+
public Cache Clients { get; }
public SignalRClientTransport() : base(ProtocolName, "SignalR Client", ["signalr"])
diff --git a/src/Transports/SignalR/Wolverine.SignalR/Internals/SignalREnvelope.cs b/src/Transports/SignalR/Wolverine.SignalR/Internals/SignalREnvelope.cs
index 88a696273..be4b7566a 100644
--- a/src/Transports/SignalR/Wolverine.SignalR/Internals/SignalREnvelope.cs
+++ b/src/Transports/SignalR/Wolverine.SignalR/Internals/SignalREnvelope.cs
@@ -4,9 +4,9 @@ namespace Wolverine.SignalR.Internals;
public class SignalREnvelope : Envelope
{
- public IHubContext HubContext { get; }
+ public IHubContext HubContext { get; }
- public SignalREnvelope(HubCallerContext context, IHubContext hubContext)
+ public SignalREnvelope(HubCallerContext context, IHubContext hubContext)
{
HubContext = hubContext;
ConnectionId = context.ConnectionId;
diff --git a/src/Transports/SignalR/Wolverine.SignalR/Internals/SignalRListenerConfiguration.cs b/src/Transports/SignalR/Wolverine.SignalR/Internals/SignalRListenerConfiguration.cs
index 32a89c3dd..f08de5a1f 100644
--- a/src/Transports/SignalR/Wolverine.SignalR/Internals/SignalRListenerConfiguration.cs
+++ b/src/Transports/SignalR/Wolverine.SignalR/Internals/SignalRListenerConfiguration.cs
@@ -1,3 +1,4 @@
+using Microsoft.AspNetCore.SignalR;
using System.Text.Json;
using Wolverine.Configuration;
diff --git a/src/Transports/SignalR/Wolverine.SignalR/Internals/SignalRSubscriberConfiguration.cs b/src/Transports/SignalR/Wolverine.SignalR/Internals/SignalRSubscriberConfiguration.cs
index 80933e35f..a17b79a8f 100644
--- a/src/Transports/SignalR/Wolverine.SignalR/Internals/SignalRSubscriberConfiguration.cs
+++ b/src/Transports/SignalR/Wolverine.SignalR/Internals/SignalRSubscriberConfiguration.cs
@@ -1,3 +1,4 @@
+using Microsoft.AspNetCore.SignalR;
using System.Text.Json;
using Wolverine.Configuration;
diff --git a/src/Transports/SignalR/Wolverine.SignalR/Internals/SignalRTransport.cs b/src/Transports/SignalR/Wolverine.SignalR/Internals/SignalRTransport.cs
index 58353885f..f94eec864 100644
--- a/src/Transports/SignalR/Wolverine.SignalR/Internals/SignalRTransport.cs
+++ b/src/Transports/SignalR/Wolverine.SignalR/Internals/SignalRTransport.cs
@@ -63,12 +63,15 @@ ValueTask ITransport.InitializeAsync(IWolverineRuntime runtime)
_mapper ??= BuildCloudEventsMapper(runtime, JsonOptions);
Logger ??= runtime.LoggerFactory.CreateLogger();
- HubContext ??= runtime.Services.GetRequiredService>();
+
+ var hubContextType = typeof(IHubContext<>).MakeGenericType(HubType);
+ HubContext ??= (IHubContext)runtime.Services.GetRequiredService(hubContextType);
return new ValueTask();
}
- public IHubContext? HubContext { get; private set; }
+ public IHubContext? HubContext { get; private set; }
+ public Type HubType { get; internal set; } = typeof(WolverineHub);
bool ITransport.TryBuildStatefulResource(IWolverineRuntime runtime, out IStatefulResource? resource)
{
@@ -147,6 +150,7 @@ protected override bool supportsMode(EndpointMode mode)
public bool SupportsNativeScheduledSend => false;
public Uri Destination => Uri;
+
public async Task PingAsync()
{
try
diff --git a/src/Transports/SignalR/Wolverine.SignalR/Internals/WebSocketRouting.cs b/src/Transports/SignalR/Wolverine.SignalR/Internals/WebSocketRouting.cs
index a9abac586..7e9997e2e 100644
--- a/src/Transports/SignalR/Wolverine.SignalR/Internals/WebSocketRouting.cs
+++ b/src/Transports/SignalR/Wolverine.SignalR/Internals/WebSocketRouting.cs
@@ -34,12 +34,12 @@ internal static ILocator DetermineLocator(Envelope envelope)
public interface ILocator
{
- IClientProxy Find(IHubContext context) where T : WolverineHub;
+ IClientProxy Find(IHubContext context) where T : Hub;
}
public record Connection(string ConnectionId) : ILocator
{
- public IClientProxy Find(IHubContext context) where T : WolverineHub
+ public IClientProxy Find(IHubContext context) where T : Hub
{
return context.Clients.Client(ConnectionId);
}
@@ -52,7 +52,7 @@ public override string ToString()
public record All : ILocator
{
- public IClientProxy Find(IHubContext context) where T : WolverineHub
+ public IClientProxy Find(IHubContext context) where T : Hub
{
return context.Clients.All;
}
@@ -60,7 +60,7 @@ public IClientProxy Find(IHubContext context) where T : WolverineHub
public record Group(string GroupName) : ILocator
{
- public IClientProxy Find(IHubContext context) where T : WolverineHub
+ public IClientProxy Find(IHubContext context) where T : Hub
{
return context.Clients.Group(GroupName);
}
diff --git a/src/Transports/SignalR/Wolverine.SignalR/SignalRWolverineExtensions.cs b/src/Transports/SignalR/Wolverine.SignalR/SignalRWolverineExtensions.cs
index 72c941850..77bf31aaa 100644
--- a/src/Transports/SignalR/Wolverine.SignalR/SignalRWolverineExtensions.cs
+++ b/src/Transports/SignalR/Wolverine.SignalR/SignalRWolverineExtensions.cs
@@ -11,16 +11,19 @@ namespace Wolverine.SignalR;
public static class SignalRWolverineExtensions
{
- /// Quick access to the Rabbit MQ Transport within this application.
- /// This is for advanced usage
+ ///
+ /// Quick access to the SignalR Transport within this application.
+ /// This is for advanced usage
///
///
///
- internal static SignalRTransport SignalRTransport(this WolverineOptions endpoints, BrokerName? name = null)
+ internal static SignalRTransport SignalRTransport(this WolverineOptions endpoints, BrokerName? name = null) where THub : WolverineHub
{
var transports = endpoints.As().Transports;
- return transports.GetOrCreate(name);
+ var transport = transports.GetOrCreate(name);
+ transport.HubType = typeof(THub);
+ return transport;
}
///
@@ -50,6 +53,15 @@ public static SignalRMessage ToWebSocketGroup(this T Message, string Group
/// Optionally configure the SignalR HubOptions for Wolverine
///
public static SignalRListenerConfiguration UseSignalR(this WolverineOptions options, Action? configure = null)
+ => options.UseSignalR(configure);
+
+ ///
+ /// Adds the WolverineHub to this application for SignalR message processing
+ ///
+ ///
+ /// Optionally configure the SignalR HubOptions for Wolverine
+ ///
+ public static SignalRListenerConfiguration UseSignalR(this WolverineOptions options, Action? configure = null) where THub : WolverineHub
{
if (configure == null)
{
@@ -61,7 +73,7 @@ public static SignalRListenerConfiguration UseSignalR(this WolverineOptions opti
}
- var transport = options.SignalRTransport();
+ var transport = options.SignalRTransport();
options.Services.AddSingleton(s =>
s.GetRequiredService().Options.Transports.GetOrCreate());
@@ -77,13 +89,23 @@ public static SignalRListenerConfiguration UseSignalR(this WolverineOptions opti
/// Optionally configure the Azure SignalR options for Wolverine
///
public static SignalRListenerConfiguration UseAzureSignalR(this WolverineOptions options, Action? configureHub = null, Action? configureSignalR = null)
+ => options.UseAzureSignalR(configureHub, configureSignalR);
+
+ ///
+ /// Adds a custom Wolverine SignalR hub to this application for Azure SignalR message processing
+ ///
+ ///
+ /// Optionally configure the SignalR HubOptions for Wolverine
+ /// Optionally configure the Azure SignalR options for Wolverine
+ ///
+ public static SignalRListenerConfiguration UseAzureSignalR(this WolverineOptions options, Action? configureHub = null, Action? configureSignalR = null) where THub : WolverineHub
{
configureHub ??= _ => { };
configureSignalR ??= _ => { };
options.Services.AddSignalR(configureHub).AddAzureSignalR(configureSignalR);
- var transport = options.SignalRTransport();
+ var transport = options.SignalRTransport();
options.Services.AddSingleton(s =>
s.GetRequiredService().Options.Transports.GetOrCreate());
@@ -98,17 +120,25 @@ public static SignalRListenerConfiguration UseAzureSignalR(this WolverineOptions
///
///
public static HubEndpointConventionBuilder MapWolverineSignalRHub(this IEndpointRouteBuilder endpoints, string route = "messages")
+ => endpoints.MapWolverineSignalRHub(route);
+
+ ///
+ /// Syntactical shortcut to register a custom Wolverine SignalR Hub for sending
+ /// messages to this server. Equivalent to MapHub(route).
+ ///
+ ///
+ ///
+ public static HubEndpointConventionBuilder MapWolverineSignalRHub(this IEndpointRouteBuilder endpoints, string route = "messages") where THub : WolverineHub
{
- return endpoints.MapHub(route);
+ return endpoints.MapHub(route);
}
///
- /// Create a subscription rule that publishes matching messages to the SignalR Hub of type "T"
+ /// Create a subscription rule that publishes matching messages to the default Wolverine SignalR Hub
///
///
- ///
///
- public static SignalRSubscriberConfiguration ToSignalR(this IPublishToExpression publishing)
+ public static SignalRSubscriberConfiguration ToSignalR(this IPublishToExpression publishing)
{
var transports = publishing.As().Parent.Transports;
var transport = transports.GetOrCreate();
diff --git a/src/Transports/SignalR/Wolverine.SignalR/WolverineHub.cs b/src/Transports/SignalR/Wolverine.SignalR/WolverineHub.cs
index e0c6b83ea..2a05e77db 100644
--- a/src/Transports/SignalR/Wolverine.SignalR/WolverineHub.cs
+++ b/src/Transports/SignalR/Wolverine.SignalR/WolverineHub.cs
@@ -4,7 +4,7 @@
namespace Wolverine.SignalR;
///
-/// Base class for Wolverine enabled SignalR Hubs
+/// Base class for Wolverine enabled SignalR Hubs
///
public class WolverineHub : Hub
{
@@ -16,7 +16,7 @@ public WolverineHub(SignalRTransport endpoint)
}
[HubMethodName("ReceiveMessage")]
- public Task ReceiveMessage(string json)
+ public virtual Task ReceiveMessage(string json)
{
return _endpoint.ReceiveAsync(Context, json);
}