SignalR (OrchardCore.SignalR)¶
The SignalR module provides the shared infrastructure required to host and consume SignalR hubs in Orchard Core. Any module that declares a hub can depend on this module instead of registering SignalR itself, which keeps hub hosting, client resources, authentication, and scale-out backplanes consistent across the application.
The scale-out backplanes ship as separate modules so the base OrchardCore.SignalR module stays free of Redis and Azure dependencies. Install only the backplane you need:
| Module | Feature | Purpose |
|---|---|---|
OrchardCore.SignalR |
OrchardCore.SignalR |
Base SignalR hosting, client resources, and the authorization policy used by secured hubs. |
OrchardCore.SignalR.Redis |
OrchardCore.SignalR.Redis |
Redis scale-out backplane. Brings the Redis dependencies. |
OrchardCore.SignalR.Azure |
OrchardCore.SignalR.Azure |
Azure SignalR Service backplane. Brings the Azure dependencies. |
Features¶
OrchardCore.SignalR— Registers SignalR with a camel-cased JSON protocol, the SignalR JavaScript client as a named resource (signalr), and theSignalRauthorization policy used by secured hubs.OrchardCore.SignalR.Redis— Uses Redis as the SignalR backplane, enabling multi-instance deployments. Each tenant's traffic is isolated on a dedicated Redis channel prefix. Provided by the separateOrchardCore.SignalR.Redismodule and depends onOrchardCore.Redis.OrchardCore.SignalR.Azure— Uses the Azure SignalR Service as the backplane, enabling multi-instance deployments. Provided by the separateOrchardCore.SignalR.Azuremodule.
Declaring a hub in another module¶
Declare the hub as usual:
using Microsoft.AspNetCore.Authorization;
namespace MyModule.Hubs;
[Authorize]
public sealed class MyHub : Hub
{
}
The feature that hosts the hub depends on OrchardCore.SignalR and maps the hub in its Startup:
public override void Configure(IApplicationBuilder app, IEndpointRouteBuilder routes, IServiceProvider serviceProvider)
{
routes.MapHub<MyHub>("/hubs/my-hub");
}
Configuring hub options¶
Per-hub behavior (for example, allowing long-running operations or tuning keep-alive) is configured by binding HubOptions<T> in ConfigureServices:
services.Configure<HubOptions<MyHub>>(options =>
{
options.ClientTimeoutInterval = TimeSpan.FromMinutes(10);
options.KeepAliveInterval = TimeSpan.FromSeconds(15);
});
Global options that apply to every hub are configured with the parameterless HubOptions instead:
services.Configure<HubOptions>(options =>
{
options.MaximumReceiveMessageSize = 128 * 1024;
});
Backplane configuration¶
Each backplane is a separate module and only registers once its feature is enabled. The Redis backplane (OrchardCore.SignalR.Redis) reuses the OrchardCore.Redis connection string (OrchardCore_Redis:Configuration):
{
"OrchardCore_Redis": {
"Configuration": "your-redis-host:6379"
}
}
The Azure SignalR Service backplane (OrchardCore.SignalR.Azure) uses its own connection string:
{
"SignalR": {
"Azure": {
"ConnectionString": "Endpoint=https://<your-service>.service.signalr.net;AccessKey=...;Version=1.0;",
"ApplicationName": "OrchardCore"
}
}
}
ApplicationName is optional and defaults to OrchardCore. Set it to a unique value when multiple Orchard Core deployments share the same Azure SignalR Service. It must start with a letter and contain only letters, numbers, and underscores. Orchard Core appends a stable tenant identifier so hubs remain isolated between tenants.
If a backplane feature is enabled but its connection string is missing, a warning is logged at startup and SignalR keeps working in single-instance (in-memory) mode — messages then only reach clients connected to the same instance.
Enable only one backplane feature per tenant. Enabling both the Azure and Redis backplanes causes tenant startup to fail with a configuration error rather than selecting one provider unpredictably.
Client integration¶
There are two independent ways to talk to a hub from the browser, and they do not overlap — pick the one that matches how your script is delivered.
Bundled apps (Vite/TypeScript)¶
Vue/TypeScript apps that are bundled with Vite (such as the Media gallery) should not use the signalr resource below. Instead, reuse the reusable client layer in Bloom under @bloom/services/signalr:
signalr-app— aHubConnectionwrapper that handles bearer-token negotiation, transport selection, automatic reconnect, and incoming-message interception.eventbus— a mitt-based bus that re-emits hub messages (andentity-created/updated/deletedevents) as decoupled application events.useSignalRService— a composable that bootstraps a connection from an API endpoint and token.
import SignalRApp from "@bloom/services/signalr/signalr-app";
import { signalRReceivedData } from "@bloom/services/signalr/eventbus";
Vite bundles @microsoft/signalr into the app, so bundled apps never load the module's signalr resource.
Server-rendered pages¶
For a server-rendered view or shape template that is not part of a bundled app, load the client that ships with this module as the named signalr resource (with a CDN fallback), then use the global signalR object:
<script asp-name="signalr" at="Foot"></script>
The client is vendored under wwwroot/Scripts/ and is produced from the @microsoft/signalr dependency declared in Assets/package.json by running yarn build at the repository root.
A script that opens a connection should declare depends-on="signalr" so the client is guaranteed to be on the page first:
<script type="text/javascript" at="Foot" depends-on="signalr">
document.addEventListener("DOMContentLoaded", function () {
var connection = new signalR.HubConnectionBuilder()
.withUrl("/hubs/my-hub")
.build();
connection.start()
.then(function () { console.log("Connected to the SignalR hub."); })
.catch(function (error) { console.error("Connection failed:", error.message); });
});
</script>