Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
By Rachel Appel and Kevin Griffin
The SignalR Hubs API enables connected clients to call methods on the server, facilitating real-time communication. The server defines methods that are called by the client, and the client defines methods that are called by the server. SignalR also enables indirect client-to-client communication, where the SignalR Hub provides the mediation. This approach allows sending messages between individual clients, groups, or to all connected clients. SignalR takes care of everything required to make real-time client-to-server and server-to-client communication possible.
This article describes how to configure hubs, send messages to clients, and allow servers to handle results from clients.
Configure SignalR hubs
Register the services required by SignalR hubs by calling the AddSignalR method in the Program.cs file:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddSignalR();
Configure SignalR endpoints by calling the MapHub method in the Program.cs file:
app.MapRazorPages();
app.MapHub<ChatHub>("/Chat");
app.Run();
Note
ASP.NET Core SignalR server-side assemblies are now installed with the .NET Core SDK. For more information, see SignalR assemblies in shared framework.
Create and use hubs
Create a hub by declaring a class that inherits from Hub. Add public methods to the class to make them callable from clients:
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
=> await Clients.All.SendAsync("ReceiveMessage", user, message);
}
Note
Hub method parameters, return values, and stream items can be C# union types only with the default JsonHubProtocol. The MessagePack and Newtonsoft.Json hub protocols don't support unions.
Use 'Context' object properties and methods
The Hub class includes a Context property that contains the following properties with information about the connection:
| Property | Description |
|---|---|
| ConnectionId | Gets the unique ID for the connection, assigned by SignalR. There's one connection ID for each connection. |
| UserIdentifier | Gets the user identifier. By default, SignalR uses the ClaimTypes.NameIdentifier property from the ClaimsPrincipal associated with the connection as the user identifier. |
| User | Gets the ClaimsPrincipal associated with the current user. |
| Items | Gets a key/value collection that can be used to share data within the scope of this connection. Data can be stored in this collection and it persists for the connection across different hub method invocations. |
| Features | Gets the collection of features available on the connection. This collection isn't currently needed in most scenarios, so detailed documentation isn't yet available. |
| ConnectionAborted | Gets a CancellationToken that notifies when the connection is aborted. |
The Hub.Context property also contains the following methods:
| Method | Description |
|---|---|
| GetHttpContext | Returns the HttpContext for the connection, or null if the connection isn't associated with an HTTP request. For HTTP connections, use this method to get information such as HTTP headers and query strings. |
| Abort | Aborts the connection. |
Use 'Clients' object properties and methods
The Hub class includes a Clients property that contains the following properties for communication between server and client:
| Property | Description |
|---|---|
| All | Calls a method on all connected clients. |
| Caller | Calls a method on the client that invoked the hub method. |
| Others | Calls a method on all connected clients except the client that invoked the method. |
The Hub.Clients property also contains the following methods:
| Method | Description |
|---|---|
| AllExcept | Calls a method on all connected clients except for the specified connections. |
| Client | Calls a method on a specific connected client. |
| Clients | Calls a method on specific connected clients. |
| Group | Calls a method on all connections in the specified group. |
| GroupExcept | Calls a method on all connections in the specified group, except the specified connections. |
| Groups | Calls a method on multiple groups of connections. |
| OthersInGroup | Calls a method on a group of connections, excluding the client that invoked the hub method. |
| User | Calls a method on all connections associated with a specific user. |
| Users | Calls a method on all connections associated with the specified users. |
Each property or method returns an object with a SendAsync method. The SendAsync method receives the name of the client method to call and any parameters.
The object returned by the Client and Caller methods also contain an InvokeAsync method, which can be used to wait for a result from the client.
Send messages to clients
To make calls to specific clients, use the properties of the Clients object. In the following example, there are three hub methods:
- The
SendMessagemethod sends a message to all connected clients by using theClients.Allproperty. - The
SendMessageToCallermethod sends a message back to the caller by using theClients.Callerproperty. - The
SendMessageToGroupmethod sends a message to all clients in theSignalR Usersgroup.
public async Task SendMessage(string user, string message)
=> await Clients.All.SendAsync("ReceiveMessage", user, message);
public async Task SendMessageToCaller(string user, string message)
=> await Clients.Caller.SendAsync("ReceiveMessage", user, message);
public async Task SendMessageToGroup(string user, string message)
=> await Clients.Group("SignalR Users").SendAsync("ReceiveMessage", user, message);
Use strongly typed hubs
A drawback of using the SendAsync method is that it relies on a string to specify the client method to call. This design leaves code open to runtime errors if the method name is misspelled or missing from the client.
An alternative to using the SendAsync method is to strongly type the Hub class with Hub<T>. In the following example, the ChatHub client method is extracted into an interface named IChatClient:
public interface IChatClient
{
Task ReceiveMessage(string user, string message);
}
The interface can be used to refactor the preceding ChatHub example to make it strongly typed:
public class StronglyTypedChatHub : Hub<IChatClient>
{
public async Task SendMessage(string user, string message)
=> await Clients.All.ReceiveMessage(user, message);
public async Task SendMessageToCaller(string user, string message)
=> await Clients.Caller.ReceiveMessage(user, message);
public async Task SendMessageToGroup(string user, string message)
=> await Clients.Group("SignalR Users").ReceiveMessage(user, message);
}
Using Hub<IChatClient> enables compile-time checking of the client methods. This approach prevents issues caused by using strings because Hub<T> can only provide access to the methods defined in the interface. Using a strongly typed Hub<T> disables the ability to use the SendAsync method.
Note
The Async suffix isn't stripped from method names. Unless a client method is defined with .on('MyMethodAsync'), don't use MyMethodAsync as the name.
Request client results
In addition to making calls to clients, the server can request a result from a client. In this scenario, the server uses the ISingleClientProxy.InvokeAsync method and the client returns a result from its .On handler.
There are two ways to use the API on the server.
You can call Client(...) or Caller on the Clients property in a Hub method:
public class ChatHub : Hub
{
public async Task<string> WaitForMessage(string connectionId)
{
var message = await Clients.Client(connectionId).InvokeAsync<string>(
"GetMessage");
return message;
}
}
Or, you can call Client(...) on an instance of IHubContext<T>:
async Task SomeMethod(IHubContext<MyHub> context)
{
string result = await context.Clients.Client(connectionID).InvokeAsync<string>(
"GetMessage");
}
Strongly typed hubs can also return values from interface methods:
public interface IClient
{
Task<string> GetMessage();
}
public class ChatHub : Hub<IClient>
{
public async Task<string> WaitForMessage(string connectionId)
{
string message = await Clients.Client(connectionId).GetMessage();
return message;
}
}
Clients return results in their .On(...) handlers, as shown in the following sections.
.NET client
hubConnection.On("GetMessage", async () =>
{
Console.WriteLine("Enter message:");
var message = await Console.In.ReadLineAsync();
return message;
});
TypeScript client
hubConnection.on("GetMessage", async () => {
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("message");
}, 100);
});
return promise;
});
Java client
hubConnection.onWithResult("GetMessage", () -> {
return Single.just("message");
});
Change the name of a hub method
By default, a server hub method name is the name of the .NET method. To change this default behavior for a specific method, use the HubMethodName attribute. The client should use this name instead of the .NET method name when invoking the method:
[HubMethodName("SendMessageToUser")]
public async Task DirectMessage(string user, string message)
=> await Clients.User(user).SendAsync("ReceiveMessage", user, message);
Inject services into a hub
SignalR registers an IHubActivator<THub> and creates an unregistered hub with ActivatorUtilities for each invocation. Singleton services injected into a hub outlive the hub instance, and injected transient services have a lifetime that matches the lifetime of the hub instance. Scoped service instances are created for each hub invocation and also match the lifetime of the hub; therefore, scoped and transient services exhibit equivalent lifetimes.
Hub constructor service injection is supported. In the following example, IDbContextFactory<TContext> is injected into a hub using a primary constructor to save messages to a database when SendMessage is called.
In the app's Program file using SQL Server as the example database provider and a connection string from configuration:
var connectionString =
builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContextFactory<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
An example hub class:
public class ChatHub(IDbContextFactory<ApplicationDbContext> contextFactory) : Hub
{
public async Task SendMessage(string user, string message)
{
using var context = await contextFactory.CreateDbContextAsync();
var msgEntity = new Message { User = user, Content = message };
context.Messages.Add(msgEntity);
await context.SaveChangesAsync();
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
If you're unable to use a factory and must inject a scoped DbContext directly, the framework automatically creates a dependency injection (DI) scope for the hub method invocation. The framework disposes the context as soon as the invocation/stream completes. However, you must ensure that the hub's methods don't execute concurrent database operations on the same context instance because DbContext isn't thread-safe.
Hub method service injection is also supported. In the following example, a scoped DbContext (ApplicationDbContext) only used for a quick write operation is injected into the specific method that requires it:
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message,
ApplicationDbContext context)
{
context.Messages.Add(new Message { User = user, Content = message });
await context.SaveChangesAsync();
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
To explicitly specify which parameters are resolved from DI in hub methods, specify the [FromServices] attribute or a custom attribute that implements IFromServiceMetadata on the hub method parameters that should be resolved from DI.
Set the DisableImplicitFromServicesParameters property (ASP.NET Core documentation) in case some parameters might come from DI and you instead want them to come from the client.
In the app's Program file:
builder.Services.AddSingleton<SomeCustomType>();
builder.Services.AddSingleton<IDatabaseService, DatabaseServiceImpl>();
builder.Services.AddSignalR(options =>
{
options.DisableImplicitFromServicesParameters = true;
});
In the following hub method, only IDatabaseService is resolved from DI:
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message,
SomeCustomType type,
[FromServices] IDatabaseService dbService)
{
await dbService.SaveMessageAsync(user, message, type);
await Clients.All.SendAsync("ReceiveMessage", user, message, type);
}
}
Note
Implicit parameter inference makes use of IServiceProviderIsService, which is optionally implemented in DI configurations. If the app's DI container doesn't support this feature, injecting services into hub methods using implicit parameter inference isn't supported.
For database operations, adopting the factory pattern is preferred for overlapping operations within one invocation:
- A direct scoped context can run into concurrency issues. Using a factory completely isolates each factory operation.
- For server-side Blazor apps, the factory pattern is recommended. For more information, see ASP.NET Core Blazor with Entity Framework Core (EF Core).
Because each hub method call is executed on a new hub instance, don't store state in a property of the hub class.
Don't instantiate a hub directly via DI. To send messages to a client from elsewhere in your app, use an IHubContext.
Use the await operator when calling an asynchronous method that depends on the hub staying alive. If you call an asynchronous method without await, the call can fail with the hub method completing before the asynchronous method finishes.
Supported: await Clients.All.SendAsync(...);
Not supported: Clients.All.SendAsync(...); (missing await)
For general guidance on DI, see Dependency injection in ASP.NET Core and its linked additional resources.
Keyed services support in dependency injection
The keyed services mechanism allows you to register and retrieve dependency injection services by using keys. A service is associated with a key by calling the AddKeyedSingleton method to register it. As an alternative, you can call the AddKeyedScoped or AddKeyedTransient method.
You access a registered service by specifying the key with the [FromKeyedServices] attribute. The following code shows how to use keyed services:
using Microsoft.AspNetCore.SignalR;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
builder.Services.AddRazorPages();
builder.Services.AddSignalR();
var app = builder.Build();
app.MapRazorPages();
app.MapHub<MyHub>("/myHub");
app.Run();
public interface ICache
{
object Get(string key);
}
public class BigCache : ICache
{
public object Get(string key) => $"Resolving {key} from big cache.";
}
public class SmallCache : ICache
{
public object Get(string key) => $"Resolving {key} from small cache.";
}
public class MyHub : Hub
{
public void SmallCacheMethod([FromKeyedServices("small")] ICache cache)
{
Console.WriteLine(cache.Get("signalr"));
}
public void BigCacheMethod([FromKeyedServices("big")] ICache cache)
{
Console.WriteLine(cache.Get("signalr"));
}
}
Limit per-connection streaming invocations
MaximumParallelInvocationsPerClient controls the number of non-streaming hub method invocations a client can run in parallel before they are queued. It does not apply to streaming hub invocations. Streaming invocations are intentionally excluded because they are expected to be long-running and concurrent, so a client can start any number of concurrent streams regardless of that setting.
To enforce a per-connection limit on streaming invocations, wrap the stream inside the hub method itself using a private helper that increments a counter before yielding items and decrements it in a finally block:
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
public class StreamingHub : Hub
{
private static readonly ConcurrentDictionary<string, int> _activeStreams = new();
private const int MaxConcurrentStreams = 2;
public IAsyncEnumerable<int> Counter(
int count,
int delay,
CancellationToken cancellationToken)
{
return WithLimit(Context.ConnectionId, GetCounter(count, delay, cancellationToken));
}
private async IAsyncEnumerable<int> GetCounter(
int count,
int delay,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
for (var i = 0; i < count; i++)
{
cancellationToken.ThrowIfCancellationRequested();
yield return i;
await Task.Delay(delay, cancellationToken);
}
}
private async IAsyncEnumerable<T> WithLimit(
string connectionId,
IAsyncEnumerable<T> stream,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var current = _activeStreams.AddOrUpdate(
connectionId,
addValue: 1,
updateValueFactory: (_, count) => count + 1);
if (current > MaxConcurrentStreams)
{
Decrement(connectionId);
throw new HubException(
$"The connection is limited to {MaxConcurrentStreams} concurrent streaming invocations.");
}
try
{
await foreach (var item in stream.WithCancellation(cancellationToken))
{
yield return item;
}
}
finally
{
Decrement(connectionId);
}
}
private static void Decrement(string connectionId)
{
while (_activeStreams.TryGetValue(connectionId, out var current))
{
if (current <= 1)
{
if (_activeStreams.TryRemove(new KeyValuePair<string, int>(connectionId, current)))
{
return;
}
}
else if (_activeStreams.TryUpdate(connectionId, current - 1, current))
{
return;
}
}
}
}
The key point is that WithLimit wraps the original IAsyncEnumerable<T> and holds the counter elevated for the full lifetime of the stream, not just until the first item is yielded.
The finally block runs only when the client finishes consuming the stream, cancels it, or the connection drops.
If your streaming hub methods return ChannelReader<T> instead of IAsyncEnumerable<T>, a similar wrapper can be applied. It should use the same _activeStreams dictionary so both stream types share a single connection-level limit rather than each maintaining their own independent count.
Note
The _activeStreams dictionary is static so it is shared across all hub instances. If you prefer DI-managed state, register a singleton service that owns the dictionary and inject it into the hub constructor.
Handle events for a connection
The SignalR Hubs API provides the OnConnectedAsync and OnDisconnectedAsync virtual methods to manage and track connections. Override the OnConnectedAsync virtual method to perform actions when a client connects to the hub, such as adding it to a group:
public override async Task OnConnectedAsync()
{
await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
await base.OnConnectedAsync();
}
Override the OnDisconnectedAsync virtual method to perform actions when a client disconnects. If the client disconnects intentionally, such as by calling connection.stop(), the exception parameter is set to null. However, if the client disconnects due to an error, such as a network failure, the exception parameter contains an exception that describes the failure:
public override async Task OnDisconnectedAsync(Exception? exception)
{
await base.OnDisconnectedAsync(exception);
}
The RemoveFromGroupAsync method doesn't need to be called within the OnDisconnectedAsync method because it's handled automatically.
Handle errors
Exceptions thrown in hub methods are sent to the client that invoked the method. On the JavaScript client, the invoke method returns a JavaScript 'Promise' object. Clients can attach a catch handler to the returned promise or use try/catch with async/await to handle exceptions:
try {
await connection.invoke("SendMessage", user, message);
} catch (err) {
console.error(err);
}
Connections aren't closed when a hub throws an exception. By default, SignalR returns a generic error message to the client, as shown in the following example:
Microsoft.AspNetCore.SignalR.HubException: An unexpected error occurred invoking 'SendMessage' on the server.
Unexpected exceptions often contain sensitive information, such as the name of a database server in an exception triggered when the database connection fails. As a security measure, SignalR doesn't expose these detailed error messages by default. For more information on why exception details are suppressed, see Security considerations in ASP.NET Core SignalR.
If an exceptional condition must be propagated to the client, use the HubException class. If a HubException is thrown in a hub method, SignalR sends the entire exception message to the client in an unmodified form:
public Task ThrowException()
=> throw new HubException("This error will be sent to the client!");
Note
SignalR only sends the Message property of the exception to the client. The stack trace and other properties on the exception aren't available to the client.
Related content
By Rachel Appel and Kevin Griffin
The SignalR Hubs API enables connected clients to call methods on the server, facilitating real-time communication. The server defines methods that are called by the client, and the client defines methods that are called by the server. SignalR also enables indirect client-to-client communication, always mediated by the SignalR Hub, allowing messages to be sent between individual clients, groups, or to all connected clients. SignalR takes care of everything required to make real-time client-to-server and server-to-client communication possible.
Configure SignalR hubs
To register the services required by SignalR hubs, call AddSignalR in Program.cs:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddSignalR();
To configure SignalR endpoints, call MapHub, also in Program.cs:
app.MapRazorPages();
app.MapHub<ChatHub>("/Chat");
app.Run();
Note
ASP.NET Core SignalR server-side assemblies are now installed with the .NET Core SDK. For more information, see SignalR assemblies in shared framework.
Create and use hubs
Create a hub by declaring a class that inherits from Hub. Add public methods to the class to make them callable from clients:
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
=> await Clients.All.SendAsync("ReceiveMessage", user, message);
}
The Context object
The Hub class includes a Context property that contains the following properties with information about the connection:
| Property | Description |
|---|---|
| ConnectionId | Gets the unique ID for the connection, assigned by SignalR. There's one connection ID for each connection. |
| UserIdentifier | Gets the user identifier. By default, SignalR uses the ClaimTypes.NameIdentifier from the ClaimsPrincipal associated with the connection as the user identifier. |
| User | Gets the ClaimsPrincipal associated with the current user. |
| Items | Gets a key/value collection that can be used to share data within the scope of this connection. Data can be stored in this collection and it will persist for the connection across different hub method invocations. |
| Features | Gets the collection of features available on the connection. For now, this collection isn't needed in most scenarios, so it isn't documented in detail yet. |
| ConnectionAborted | Gets a CancellationToken that notifies when the connection is aborted. |
Hub.Context also contains the following methods:
| Method | Description |
|---|---|
| GetHttpContext | Returns the HttpContext for the connection, or null if the connection isn't associated with an HTTP request. For HTTP connections, use this method to get information such as HTTP headers and query strings. |
| Abort | Aborts the connection. |
The Clients object
The Hub class includes a Clients property that contains the following properties for communication between server and client:
| Property | Description |
|---|---|
| All | Calls a method on all connected clients |
| Caller | Calls a method on the client that invoked the hub method |
| Others | Calls a method on all connected clients except the client that invoked the method |
Hub.Clients also contains the following methods:
| Method | Description |
|---|---|
| AllExcept | Calls a method on all connected clients except for the specified connections |
| Client | Calls a method on a specific connected client |
| Clients | Calls a method on specific connected clients |
| Group | Calls a method on all connections in the specified group |
| GroupExcept | Calls a method on all connections in the specified group, except the specified connections |
| Groups | Calls a method on multiple groups of connections |
| OthersInGroup | Calls a method on a group of connections, excluding the client that invoked the hub method |
| User | Calls a method on all connections associated with a specific user |
| Users | Calls a method on all connections associated with the specified users |
Each property or method in the preceding tables returns an object with a SendAsync method. The SendAsync method receives the name of the client method to call and any parameters.
The object returned by the Client and Caller methods also contain an InvokeAsync method, which can be used to wait for a result from the client.
Send messages to clients
To make calls to specific clients, use the properties of the Clients object. In the following example, there are three hub methods:
SendMessagesends a message to all connected clients, usingClients.All.SendMessageToCallersends a message back to the caller, usingClients.Caller.SendMessageToGroupsends a message to all clients in theSignalR Usersgroup.
public async Task SendMessage(string user, string message)
=> await Clients.All.SendAsync("ReceiveMessage", user, message);
public async Task SendMessageToCaller(string user, string message)
=> await Clients.Caller.SendAsync("ReceiveMessage", user, message);
public async Task SendMessageToGroup(string user, string message)
=> await Clients.Group("SignalR Users").SendAsync("ReceiveMessage", user, message);
Strongly typed hubs
A drawback of using SendAsync is that it relies on a string to specify the client method to be called. This leaves code open to runtime errors if the method name is misspelled or missing from the client.
An alternative to using SendAsync is to strongly type the Hub class with Hub<T>. In the following example, the ChatHub client method has been extracted out into an interface called IChatClient:
public interface IChatClient
{
Task ReceiveMessage(string user, string message);
}
This interface can be used to refactor the preceding ChatHub example to be strongly typed:
public class StronglyTypedChatHub : Hub<IChatClient>
{
public async Task SendMessage(string user, string message)
=> await Clients.All.ReceiveMessage(user, message);
public async Task SendMessageToCaller(string user, string message)
=> await Clients.Caller.ReceiveMessage(user, message);
public async Task SendMessageToGroup(string user, string message)
=> await Clients.Group("SignalR Users").ReceiveMessage(user, message);
}
Using Hub<IChatClient> enables compile-time checking of the client methods. This prevents issues caused by using strings, since Hub<T> can only provide access to the methods defined in the interface. Using a strongly typed Hub<T> disables the ability to use SendAsync.
Note
The Async suffix isn't stripped from method names. Unless a client method is defined with .on('MyMethodAsync'), don't use MyMethodAsync as the name.
Client results
In addition to making calls to clients, the server can request a result from a client. This requires the server to use ISingleClientProxy.InvokeAsync and the client to return a result from its .On handler.
There are two ways to use the API on the server, the first is to call Client(...) or Caller on the Clients property in a Hub method:
public class ChatHub : Hub
{
public async Task<string> WaitForMessage(string connectionId)
{
var message = await Clients.Client(connectionId).InvokeAsync<string>(
"GetMessage");
return message;
}
}
The second way is to call Client(...) on an instance of IHubContext<T>:
async Task SomeMethod(IHubContext<MyHub> context)
{
string result = await context.Clients.Client(connectionID).InvokeAsync<string>(
"GetMessage");
}
Strongly-typed hubs can also return values from interface methods:
public interface IClient
{
Task<string> GetMessage();
}
public class ChatHub : Hub<IClient>
{
public async Task<string> WaitForMessage(string connectionId)
{
string message = await Clients.Client(connectionId).GetMessage();
return message;
}
}
Clients return results in their .On(...) handlers, as shown below:
.NET client
hubConnection.On("GetMessage", async () =>
{
Console.WriteLine("Enter message:");
var message = await Console.In.ReadLineAsync();
return message;
});
Typescript client
hubConnection.on("GetMessage", async () => {
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("message");
}, 100);
});
return promise;
});
Java client
hubConnection.onWithResult("GetMessage", () -> {
return Single.just("message");
});
Change the name of a hub method
By default, a server hub method name is the name of the .NET method. To change this default behavior for a specific method, use the HubMethodName attribute. The client should use this name instead of the .NET method name when invoking the method:
[HubMethodName("SendMessageToUser")]
public async Task DirectMessage(string user, string message)
=> await Clients.User(user).SendAsync("ReceiveMessage", user, message);
Inject services into a hub
SignalR registers an IHubActivator<THub> and creates an unregistered hub with ActivatorUtilities for each invocation. Singleton services injected into a hub outlive the hub instance, and injected transient services have a lifetime that matches the lifetime of the hub instance. Scoped service instances are created for each hub invocation and also match the lifetime of the hub; therefore, scoped and transient services exhibit equivalent lifetimes.
Hub constructor service injection is supported. In the following example, IDbContextFactory<TContext> is injected into a hub's constructor to save messages to a database when SendMessage is called.
In the app's Program file using SQL Server as the example database provider and a connection string from configuration:
var connectionString =
builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContextFactory<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
An example hub class:
public class ChatHub : Hub
{
private readonly IDbContextFactory<ApplicationDbContext> contextFactory;
public ChatHub(IDbContextFactory<ApplicationDbContext> contextFactory)
{
this.contextFactory = contextFactory;
}
public async Task SendMessage(string user, string message)
{
using var context = await contextFactory.CreateDbContextAsync();
context.Messages.Add(new Message { User = user, Content = message });
await context.SaveChangesAsync();
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
If you're unable to use a factory and must inject a scoped DbContext directly, the framework automatically creates a dependency injection (DI) scope for the hub method invocation. The framework disposes the context as soon as the invocation/stream completes. However, you must ensure that the hub's methods don't execute concurrent database operations on the same context instance because DbContext isn't thread-safe.
Hub method service injection is also supported. In the following example, a scoped DbContext (ApplicationDbContext) only used for a quick write operation is injected into the specific method that requires it:
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message,
ApplicationDbContext context)
{
context.Messages.Add(new Message { User = user, Content = message });
await context.SaveChangesAsync();
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
To explicitly specify which parameters are resolved from DI in hub methods, specify the [FromServices] attribute or a custom attribute that implements IFromServiceMetadata on the hub method parameters that should be resolved from DI.
Set the DisableImplicitFromServicesParameters property (ASP.NET Core documentation) in case some parameters might come from DI and you instead want them to come from the client.
In the app's Program file:
builder.Services.AddSingleton<SomeCustomType>();
builder.Services.AddSingleton<IDatabaseService, DatabaseServiceImpl>();
builder.Services.AddSignalR(options =>
{
options.DisableImplicitFromServicesParameters = true;
});
In the following hub method, only IDatabaseService is resolved from DI:
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message,
SomeCustomType type,
[FromServices] IDatabaseService dbService)
{
await dbService.SaveMessageAsync(user, message, type);
await Clients.All.SendAsync("ReceiveMessage", user, message, type);
}
}
Note
Implicit parameter inference makes use of IServiceProviderIsService, which is optionally implemented in DI configurations. If the app's DI container doesn't support this feature, injecting services into hub methods using implicit parameter inference isn't supported.
For database operations, adopting the factory pattern is preferred for overlapping operations within one invocation:
- A direct scoped context can run into concurrency issues. Using a factory completely isolates each factory operation.
- For server-side Blazor apps, the factory pattern is recommended. For more information, see ASP.NET Core Blazor with Entity Framework Core (EF Core).
Because each hub method call is executed on a new hub instance, don't store state in a property of the hub class.
Don't instantiate a hub directly via DI. To send messages to a client from elsewhere in your app, use an IHubContext.
Use the await operator when calling an asynchronous method that depends on the hub staying alive. If you call an asynchronous method without await, the call can fail with the hub method completing before the asynchronous method finishes.
Supported: await Clients.All.SendAsync(...);
Not supported: Clients.All.SendAsync(...); (missing await)
For general guidance on DI, see Dependency injection in ASP.NET Core and its linked additional resources.
Handle events for a connection
The SignalR Hubs API provides the OnConnectedAsync and OnDisconnectedAsync virtual methods to manage and track connections. Override the OnConnectedAsync virtual method to perform actions when a client connects to the hub, such as adding it to a group:
public override async Task OnConnectedAsync()
{
await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
await base.OnConnectedAsync();
}
Override the OnDisconnectedAsync virtual method to perform actions when a client disconnects. If the client disconnects intentionally, such as by calling connection.stop(), the exception parameter is set to null. However, if the client disconnects due to an error, such as a network failure, the exception parameter contains an exception that describes the failure:
public override async Task OnDisconnectedAsync(Exception? exception)
{
await base.OnDisconnectedAsync(exception);
}
RemoveFromGroupAsync does not need to be called in OnDisconnectedAsync, it's automatically handled for you.
Handle errors
Exceptions thrown in hub methods are sent to the client that invoked the method. On the JavaScript client, the invoke method returns a JavaScript Promise. Clients can attach a catch handler to the returned promise or use try/catch with async/await to handle exceptions:
try {
await connection.invoke("SendMessage", user, message);
} catch (err) {
console.error(err);
}
Connections aren't closed when a hub throws an exception. By default, SignalR returns a generic error message to the client, as shown in the following example:
Microsoft.AspNetCore.SignalR.HubException: An unexpected error occurred invoking 'SendMessage' on the server.
Unexpected exceptions often contain sensitive information, such as the name of a database server in an exception triggered when the database connection fails. SignalR doesn't expose these detailed error messages by default as a security measure. For more information on why exception details are suppressed, see Security considerations in ASP.NET Core SignalR.
If an exceptional condition must be propagated to the client, use the HubException class. If a HubException is thrown in a hub method, SignalR sends the entire exception message to the client, unmodified:
public Task ThrowException()
=> throw new HubException("This error will be sent to the client!");
Note
SignalR only sends the Message property of the exception to the client. The stack trace and other properties on the exception aren't available to the client.
Additional resources
By Rachel Appel and Kevin Griffin
The SignalR Hubs API enables connected clients to call methods on the server, facilitating real-time communication. The server defines methods that are called by the client, and the client defines methods that are called by the server. SignalR also enables indirect client-to-client communication, always mediated by the SignalR Hub, allowing messages to be sent between individual clients, groups, or to all connected clients. SignalR takes care of everything required to make real-time client-to-server and server-to-client communication possible.
Configure SignalR hubs
To register the services required by SignalR hubs, call AddSignalR in Program.cs:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddSignalR();
To configure SignalR endpoints, call MapHub, also in Program.cs:
app.MapRazorPages();
app.MapHub<ChatHub>("/Chat");
app.Run();
Note
ASP.NET Core SignalR server-side assemblies are now installed with the .NET Core SDK. For more information, see SignalR assemblies in shared framework.
Create and use hubs
Create a hub by declaring a class that inherits from Hub. Add public methods to the class to make them callable from clients:
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
=> await Clients.All.SendAsync("ReceiveMessage", user, message);
}
Inject services into a hub
SignalR registers an IHubActivator<THub> and creates an unregistered hub with ActivatorUtilities for each invocation. Singleton services injected into a hub outlive the hub instance, and injected transient services have a lifetime that matches the lifetime of the hub instance. Scoped service instances are created for each hub invocation and also match the lifetime of the hub; therefore, scoped and transient services exhibit equivalent lifetimes.
Hub constructor service injection is supported. In the following example, IDbContextFactory<TContext> is injected into a hub's constructor and used to save messages to a database when SendMessage is called.
In the app's Program file using SQL Server as the example database provider and a connection string from configuration:
var connectionString =
builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContextFactory<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
An example hub class:
public class ChatHub : Hub
{
private readonly IDbContextFactory<ApplicationDbContext> contextFactory;
public ChatHub(IDbContextFactory<ApplicationDbContext> contextFactory)
{
this.contextFactory = contextFactory;
}
public async Task SendMessage(string user, string message)
{
using var context = await contextFactory.CreateDbContextAsync();
context.Messages.Add(new Message { User = user, Content = message });
await context.SaveChangesAsync();
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
If you're unable to use a factory and must inject a scoped DbContext directly, the framework automatically creates a dependency injection (DI) scope for the hub method invocation. The framework disposes the context as soon as the invocation/stream completes. However, you must ensure that the hub's methods don't execute concurrent database operations on the same context instance because DbContext isn't thread-safe.
For database operations, adopting the factory pattern is preferred for overlapping operations within one invocation:
- A direct scoped context can run into concurrency issues. Using a factory completely isolates each factory operation.
- For server-side Blazor apps, the factory pattern is recommended. For more information, see ASP.NET Core Blazor with Entity Framework Core (EF Core).
Because each hub method call is executed on a new hub instance, don't store state in a property of the hub class.
Don't instantiate a hub directly via DI. To send messages to a client from elsewhere in your app, use an IHubContext.
Use the await operator when calling an asynchronous method that depends on the hub staying alive. If you call an asynchronous method without await, the call can fail with the hub method completing before the asynchronous method finishes.
Supported: await Clients.All.SendAsync(...);
Not supported: Clients.All.SendAsync(...); (missing await)
For general guidance on DI, see Dependency injection in ASP.NET Core and its linked additional resources.
The Context object
The Hub class includes a Context property that contains the following properties with information about the connection:
| Property | Description |
|---|---|
| ConnectionId | Gets the unique ID for the connection, assigned by SignalR. There's one connection ID for each connection. |
| UserIdentifier | Gets the user identifier. By default, SignalR uses the ClaimTypes.NameIdentifier from the ClaimsPrincipal associated with the connection as the user identifier. |
| User | Gets the ClaimsPrincipal associated with the current user. |
| Items | Gets a key/value collection that can be used to share data within the scope of this connection. Data can be stored in this collection and it will persist for the connection across different hub method invocations. |
| Features | Gets the collection of features available on the connection. For now, this collection isn't needed in most scenarios, so it isn't documented in detail yet. |
| ConnectionAborted | Gets a CancellationToken that notifies when the connection is aborted. |
Hub.Context also contains the following methods:
| Method | Description |
|---|---|
| GetHttpContext | Returns the HttpContext for the connection, or null if the connection isn't associated with an HTTP request. For HTTP connections, use this method to get information such as HTTP headers and query strings. |
| Abort | Aborts the connection. |
The Clients object
The Hub class includes a Clients property that contains the following properties for communication between server and client:
| Property | Description |
|---|---|
| All | Calls a method on all connected clients |
| Caller | Calls a method on the client that invoked the hub method |
| Others | Calls a method on all connected clients except the client that invoked the method |
Hub.Clients also contains the following methods:
| Method | Description |
|---|---|
| AllExcept | Calls a method on all connected clients except for the specified connections |
| Client | Calls a method on a specific connected client |
| Clients | Calls a method on specific connected clients |
| Group | Calls a method on all connections in the specified group |
| GroupExcept | Calls a method on all connections in the specified group, except the specified connections |
| Groups | Calls a method on multiple groups of connections |
| OthersInGroup | Calls a method on a group of connections, excluding the client that invoked the hub method |
| User | Calls a method on all connections associated with a specific user |
| Users | Calls a method on all connections associated with the specified users |
Each property or method in the preceding tables returns an object with a SendAsync method. The SendAsync method receives the name of the client method to call and any parameters.
Send messages to clients
To make calls to specific clients, use the properties of the Clients object. In the following example, there are three hub methods:
SendMessagesends a message to all connected clients, usingClients.All.SendMessageToCallersends a message back to the caller, usingClients.Caller.SendMessageToGroupsends a message to all clients in theSignalR Usersgroup.
public async Task SendMessage(string user, string message)
=> await Clients.All.SendAsync("ReceiveMessage", user, message);
public async Task SendMessageToCaller(string user, string message)
=> await Clients.Caller.SendAsync("ReceiveMessage", user, message);
public async Task SendMessageToGroup(string user, string message)
=> await Clients.Group("SignalR Users").SendAsync("ReceiveMessage", user, message);
Strongly typed hubs
A drawback of using SendAsync is that it relies on a string to specify the client method to be called. This leaves code open to runtime errors if the method name is misspelled or missing from the client.
An alternative to using SendAsync is to strongly type the Hub class with Hub<T>. In the following example, the ChatHub client method has been extracted out into an interface called IChatClient:
public interface IChatClient
{
Task ReceiveMessage(string user, string message);
}
This interface can be used to refactor the preceding ChatHub example to be strongly typed:
public class StronglyTypedChatHub : Hub<IChatClient>
{
public async Task SendMessage(string user, string message)
=> await Clients.All.ReceiveMessage(user, message);
public async Task SendMessageToCaller(string user, string message)
=> await Clients.Caller.ReceiveMessage(user, message);
public async Task SendMessageToGroup(string user, string message)
=> await Clients.Group("SignalR Users").ReceiveMessage(user, message);
}
Using Hub<IChatClient> enables compile-time checking of the client methods. This prevents issues caused by using strings, since Hub<T> can only provide access to the methods defined in the interface. Using a strongly typed Hub<T> disables the ability to use SendAsync.
Note
The Async suffix isn't stripped from method names. Unless a client method is defined with .on('MyMethodAsync'), don't use MyMethodAsync as the name.
Change the name of a hub method
By default, a server hub method name is the name of the .NET method. To change this default behavior for a specific method, use the HubMethodName attribute. The client should use this name instead of the .NET method name when invoking the method:
[HubMethodName("SendMessageToUser")]
public async Task DirectMessage(string user, string message)
=> await Clients.User(user).SendAsync("ReceiveMessage", user, message);
Handle events for a connection
The SignalR Hubs API provides the OnConnectedAsync and OnDisconnectedAsync virtual methods to manage and track connections. Override the OnConnectedAsync virtual method to perform actions when a client connects to the hub, such as adding it to a group:
public override async Task OnConnectedAsync()
{
await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
await base.OnConnectedAsync();
}
Override the OnDisconnectedAsync virtual method to perform actions when a client disconnects. If the client disconnects intentionally, such as by calling connection.stop(), the exception parameter is set to null. However, if the client disconnects due to an error, such as a network failure, the exception parameter contains an exception that describes the failure:
public override async Task OnDisconnectedAsync(Exception? exception)
{
await base.OnDisconnectedAsync(exception);
}
RemoveFromGroupAsync does not need to be called in OnDisconnectedAsync, it's automatically handled for you.
Handle errors
Exceptions thrown in hub methods are sent to the client that invoked the method. On the JavaScript client, the invoke method returns a JavaScript Promise. Clients can attach a catch handler to the returned promise or use try/catch with async/await to handle exceptions:
try {
await connection.invoke("SendMessage", user, message);
} catch (err) {
console.error(err);
}
Connections aren't closed when a hub throws an exception. By default, SignalR returns a generic error message to the client, as shown in the following example:
Microsoft.AspNetCore.SignalR.HubException: An unexpected error occurred invoking 'SendMessage' on the server.
Unexpected exceptions often contain sensitive information, such as the name of a database server in an exception triggered when the database connection fails. SignalR doesn't expose these detailed error messages by default as a security measure. For more information on why exception details are suppressed, see Security considerations in ASP.NET Core SignalR.
If an exceptional condition must be propagated to the client, use the HubException class. If a HubException is thrown in a hub method, SignalR sends the entire exception message to the client, unmodified:
public Task ThrowException()
=> throw new HubException("This error will be sent to the client!");
Note
SignalR only sends the Message property of the exception to the client. The stack trace and other properties on the exception aren't available to the client.
Additional resources
By Rachel Appel and Kevin Griffin
View or download sample code (how to download)
What is a SignalR hub
The SignalR Hubs API enables connected clients to call methods on the server, facilitating real-time communication. The server defines methods that are called by the client, and the client defines methods that are called by the server. SignalR also enables indirect client-to-client communication, always mediated by the SignalR Hub, allowing messages to be sent between individual clients, groups, or to all connected clients. SignalR takes care of everything required to make real-time client-to-server and server-to-client communication possible.
Configure SignalR hubs
The SignalR middleware requires some services, which are configured by calling AddSignalR:
services.AddSignalR();
When adding SignalR functionality to an ASP.NET Core app, setup SignalR routes by calling MapHub in the Startup.Configure method's UseEndpoints callback:
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<ChatHub>("/chathub");
});
Note
ASP.NET Core SignalR server-side assemblies are now installed with the .NET Core SDK. For more information, see SignalR assemblies in shared framework.
Create and use hubs
Create a hub by declaring a class that inherits from Hub, and add public methods to it. Clients can call methods that are defined as public:
public class ChatHub : Hub
{
public Task SendMessage(string user, string message)
{
return Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
You can specify a return type and parameters, including complex types and arrays, as you would in any C# method. SignalR handles the serialization and deserialization of complex objects and arrays in your parameters and return values.
Inject services into a hub
SignalR registers an IHubActivator<THub> and creates an unregistered hub with ActivatorUtilities for each invocation. Singleton services injected into a hub outlive the hub instance, and injected transient services have a lifetime that matches the lifetime of the hub instance. Scoped service instances are created for each hub invocation and also match the lifetime of the hub; therefore, scoped and transient services exhibit equivalent lifetimes.
Hub constructor service injection is supported with the factory pattern for creating database contexts using IDbContextFactory<TContext>. In the following example, IDbContextFactory<TContext> is injected into a hub's constructor and used to save messages to a database when SendMessage is called.
In Startup.ConfigureServices using SQL Server as the example database provider and a connection string from configuration:
var connectionString = Configuration.GetConnectionString("DefaultConnection");
services.AddDbContextFactory<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
An example hub class:
public class ChatHub : Hub
{
private readonly IDbContextFactory<ApplicationDbContext> contextFactory;
public ChatHub(IDbContextFactory<ApplicationDbContext> contextFactory)
{
this.contextFactory = contextFactory;
}
public async Task SendMessage(string user, string message)
{
using var context = contextFactory.CreateDbContext();
context.Messages.Add(new Message { User = user, Content = message });
await context.SaveChangesAsync();
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
If you're unable to use a factory and must inject a scoped DbContext directly, the framework automatically creates a dependency injection (DI) scope for the hub method invocation. The framework disposes the context as soon as the invocation/stream completes. However, you must ensure that the hub's methods don't execute concurrent database operations on the same context instance because DbContext isn't thread-safe.
When injecting a scoped DbContext, the framework automatically creates a dependency injection (DI) scope for the hub method invocation. The framework disposes the context as soon as the invocation/stream completes. However, you must ensure that the hub's methods don't execute concurrent database operations on the same context instance because DbContext isn't thread-safe.
When injecting a scoped DbContext, the framework automatically creates a dependency injection (DI) scope for the hub method invocation. The framework disposes the context as soon as the invocation completes. However, you must ensure that the hub's methods don't execute concurrent database operations on the same context instance because DbContext isn't thread-safe.
Note
Upgrade the app to target .NET 5 or later to use the factory pattern for creating database contexts using IDbContextFactory<TContext>.
For database operations, adopting the factory pattern is preferred for overlapping operations within one invocation:
- A direct scoped context can run into concurrency issues. Using a factory completely isolates each factory operation.
- For server-side Blazor apps, the factory pattern is recommended. For more information, see ASP.NET Core Blazor with Entity Framework Core (EF Core).
Because each hub method call is executed on a new hub instance, don't store state in a property of the hub class.
Don't instantiate a hub directly via DI. To send messages to a client from elsewhere in your app, use an IHubContext.
Use the await operator when calling an asynchronous method that depends on the hub staying alive. If you call an asynchronous method without await, the call can fail with the hub method completing before the asynchronous method finishes.
Supported: await Clients.All.SendAsync(...);
Not supported: Clients.All.SendAsync(...); (missing await)
For general guidance on DI, see Dependency injection in ASP.NET Core and its linked additional resources.
The Context object
The Hub class has a Context property that contains the following properties with information about the connection:
| Property | Description |
|---|---|
| ConnectionId | Gets the unique ID for the connection, assigned by SignalR. There's one connection ID for each connection. |
| UserIdentifier | Gets the user identifier. By default, SignalR uses the ClaimTypes.NameIdentifier from the ClaimsPrincipal associated with the connection as the user identifier. |
| User | Gets the ClaimsPrincipal associated with the current user. |
| Items | Gets a key/value collection that can be used to share data within the scope of this connection. Data can be stored in this collection and it will persist for the connection across different hub method invocations. |
| Features | Gets the collection of features available on the connection. For now, this collection isn't needed in most scenarios, so it isn't documented in detail yet. |
| ConnectionAborted | Gets a CancellationToken that notifies when the connection is aborted. |
Hub.Context also contains the following methods:
| Method | Description |
|---|---|
| GetHttpContext | Returns the HttpContext for the connection, or null if the connection isn't associated with an HTTP request. For HTTP connections, you can use this method to get information such as HTTP headers and query strings. |
| Abort | Aborts the connection. |
The Clients object
The Hub class has a Clients property that contains the following properties for communication between server and client:
| Property | Description |
|---|---|
| All | Calls a method on all connected clients |
| Caller | Calls a method on the client that invoked the hub method |
| Others | Calls a method on all connected clients except the client that invoked the method |
Hub.Clients also contains the following methods:
| Method | Description |
|---|---|
| AllExcept | Calls a method on all connected clients except for the specified connections |
| Client | Calls a method on a specific connected client |
| Clients | Calls a method on specific connected clients |
| Group | Calls a method on all connections in the specified group |
| GroupExcept | Calls a method on all connections in the specified group, except the specified connections |
| Groups | Calls a method on multiple groups of connections |
| OthersInGroup | Calls a method on a group of connections, excluding the client that invoked the hub method |
| User | Calls a method on all connections associated with a specific user |
| Users | Calls a method on all connections associated with the specified users |
Each property or method in the preceding tables returns an object with a SendAsync method. The SendAsync method allows you to supply the name and parameters of the client method to call.
Send messages to clients
To make calls to specific clients, use the properties of the Clients object. In the following example, there are three Hub methods:
SendMessagesends a message to all connected clients, usingClients.All.SendMessageToCallersends a message back to the caller, usingClients.Caller.SendMessageToGroupsends a message to all clients in theSignalR Usersgroup.
public Task SendMessage(string user, string message)
{
return Clients.All.SendAsync("ReceiveMessage", user, message);
}
public Task SendMessageToCaller(string user, string message)
{
return Clients.Caller.SendAsync("ReceiveMessage", user, message);
}
public Task SendMessageToGroup(string user, string message)
{
return Clients.Group("SignalR Users").SendAsync("ReceiveMessage", user, message);
}
Strongly typed hubs
A drawback of using SendAsync is that it relies on a magic string to specify the client method to be called. This leaves code open to runtime errors if the method name is misspelled or missing from the client.
An alternative to using SendAsync is to strongly type the Hub with Hub<T>. In the following example, the ChatHub client methods have been extracted out into an interface called IChatClient.
public interface IChatClient
{
Task ReceiveMessage(string user, string message);
}
This interface can be used to refactor the preceding ChatHub example:
public class StronglyTypedChatHub : Hub<IChatClient>
{
public async Task SendMessage(string user, string message)
{
await Clients.All.ReceiveMessage(user, message);
}
public Task SendMessageToCaller(string user, string message)
{
return Clients.Caller.ReceiveMessage(user, message);
}
}
Using Hub<IChatClient> enables compile-time checking of the client methods. This prevents issues caused by using magic strings, since Hub<T> can only provide access to the methods defined in the interface.
Using a strongly typed Hub<T> disables the ability to use SendAsync. Any methods defined on the interface can still be defined as asynchronous. In fact, each of these methods should return a Task. Since it's an interface, don't use the async keyword. For example:
public interface IClient
{
Task ClientMethod();
}
Note
The Async suffix isn't stripped from the method name. Unless your client method is defined with .on('MyMethodAsync'), you shouldn't use MyMethodAsync as a name.
Change the name of a hub method
By default, a server hub method name is the name of the .NET method. However, you can use the HubMethodName attribute to change this default and manually specify a name for the method. The client should use this name, instead of the .NET method name, when invoking the method:
[HubMethodName("SendMessageToUser")]
public Task DirectMessage(string user, string message)
{
return Clients.User(user).SendAsync("ReceiveMessage", user, message);
}
Handle events for a connection
The SignalR Hubs API provides the OnConnectedAsync and OnDisconnectedAsync virtual methods to manage and track connections. Override the OnConnectedAsync virtual method to perform actions when a client connects to the Hub, such as adding it to a group:
public override async Task OnConnectedAsync()
{
await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
await base.OnConnectedAsync();
}
Override the OnDisconnectedAsync virtual method to perform actions when a client disconnects. If the client disconnects intentionally (by calling connection.stop(), for example), the exception parameter will be null. However, if the client is disconnected due to an error (such as a network failure), the exception parameter will contain an exception describing the failure:
public override async Task OnDisconnectedAsync(Exception exception)
{
await Clients.Group("SignalR Users").SendAsync("ReceiveMessage", "I", "disconnect");
await base.OnDisconnectedAsync(exception);
}
RemoveFromGroupAsync does not need to be called in OnDisconnectedAsync, it's automatically handled for you.
Warning
Security warning: Exposing ConnectionId can lead to malicious impersonation if the SignalR server or client version is ASP.NET Core 2.2 or earlier.
Handle errors
Exceptions thrown in your hub methods are sent to the client that invoked the method. On the JavaScript client, the invoke method returns a JavaScript Promise. When the client receives an error with a handler attached to the promise using catch, it's invoked and passed as a JavaScript Error object:
connection.invoke("SendMessage", user, message).catch(err => console.error(err));
If your Hub throws an exception, connections aren't closed. By default, SignalR returns a generic error message to the client. For example:
Microsoft.AspNetCore.SignalR.HubException: An unexpected error occurred invoking 'MethodName' on the server.
Unexpected exceptions often contain sensitive information, such as the name of a database server in an exception triggered when the database connection fails. SignalR doesn't expose these detailed error messages by default as a security measure. For more information on why exception details are suppressed, see Security considerations in ASP.NET Core SignalR.
If you have an exceptional condition you do want to propagate to the client, you can use the HubException class. If you throw a HubException from your hub method, SignalR will send the entire message to the client, unmodified:
public Task ThrowException()
{
throw new HubException("This error will be sent to the client!");
}
Note
SignalR only sends the Message property of the exception to the client. The stack trace and other properties on the exception aren't available to the client.
Additional resources
By Rachel Appel and Kevin Griffin
View or download sample code (how to download)
What is a SignalR hub
The SignalR Hubs API enables connected clients to call methods on the server, facilitating real-time communication. The server defines methods that are called by the client, and the client defines methods that are called by the server. SignalR also enables indirect client-to-client communication, always mediated by the SignalR Hub, allowing messages to be sent between individual clients, groups, or to all connected clients. SignalR takes care of everything required to make real-time client-to-server and server-to-client communication possible.
Configure SignalR hubs
The SignalR middleware requires some services, which are configured by calling AddSignalR:
services.AddSignalR();
When adding SignalR functionality to an ASP.NET Core app, setup SignalR routes by calling UseSignalR in the Startup.Configure method:
app.UseSignalR(route =>
{
route.MapHub<ChatHub>("/chathub");
});
Create and use hubs
Create a hub by declaring a class that inherits from Hub, and add public methods to it. Clients can call methods that are defined as public:
public class ChatHub : Hub
{
public Task SendMessage(string user, string message)
{
return Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
You can specify a return type and parameters, including complex types and arrays, as you would in any C# method. SignalR handles the serialization and deserialization of complex objects and arrays in your parameters and return values.
Inject services into a hub
SignalR registers an IHubActivator<THub> and creates an unregistered hub with ActivatorUtilities for each invocation. Singleton services injected into a hub outlive the hub instance, and injected transient services have a lifetime that matches the lifetime of the hub instance. Scoped service instances are created for each hub invocation and also match the lifetime of the hub; therefore, scoped and transient services exhibit equivalent lifetimes.
Hub constructor service injection is supported. The framework automatically creates a dependency injection (DI) scope for the hub method invocation. The framework disposes the context as soon as the invocation completes. However, you must ensure that the hub's methods don't execute concurrent database operations on the same context instance because DbContext isn't thread-safe.
In the following example, DbContext is injected into a hub's constructor and used to save messages to a database when SendMessage is called.
In Startup.ConfigureServices using SQL Server as the example database provider and a connection string from configuration:
var connectionString = Configuration.GetConnectionString("DefaultConnection");
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
In a hub class:
public class ChatHub : Hub
{
private readonly ApplicationDbContext context;
public ChatHub(ApplicationDbContext context)
{
this.context = context;
}
public async Task SendMessage(string user, string message)
{
context.Messages.Add(new Message { User = user, Content = message });
await context.SaveChangesAsync();
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
Because each hub method call is executed on a new hub instance, don't store state in a property of the hub class.
Don't instantiate a hub directly via DI. To send messages to a client from elsewhere in your app, use an IHubContext.
Use the await operator when calling an asynchronous method that depends on the hub staying alive. If you call an asynchronous method without await, the call can fail with the hub method completing before the asynchronous method finishes.
Supported: await Clients.All.SendAsync(...);
Not supported: Clients.All.SendAsync(...); (missing await)
For general guidance on DI, see Dependency injection in ASP.NET Core and its linked additional resources.
The Context object
The Hub class has a Context property that contains the following properties with information about the connection:
| Property | Description |
|---|---|
| ConnectionId | Gets the unique ID for the connection, assigned by SignalR. There's one connection ID for each connection. |
| UserIdentifier | Gets the user identifier. By default, SignalR uses the ClaimTypes.NameIdentifier from the ClaimsPrincipal associated with the connection as the user identifier. |
| User | Gets the ClaimsPrincipal associated with the current user. |
| Items | Gets a key/value collection that can be used to share data within the scope of this connection. Data can be stored in this collection and it will persist for the connection across different hub method invocations. |
| Features | Gets the collection of features available on the connection. For now, this collection isn't needed in most scenarios, so it isn't documented in detail yet. |
| ConnectionAborted | Gets a CancellationToken that notifies when the connection is aborted. |
Hub.Context also contains the following methods:
| Method | Description |
|---|---|
| GetHttpContext | Returns the HttpContext for the connection, or null if the connection isn't associated with an HTTP request. For HTTP connections, you can use this method to get information such as HTTP headers and query strings. |
| Abort | Aborts the connection. |
The Clients object
The Hub class has a Clients property that contains the following properties for communication between server and client:
| Property | Description |
|---|---|
| All | Calls a method on all connected clients |
| Caller | Calls a method on the client that invoked the hub method |
| Others | Calls a method on all connected clients except the client that invoked the method |
Hub.Clients also contains the following methods:
| Method | Description |
|---|---|
| AllExcept | Calls a method on all connected clients except for the specified connections |
| Client | Calls a method on a specific connected client |
| Clients | Calls a method on specific connected clients |
| Group | Calls a method on all connections in the specified group |
| GroupExcept | Calls a method on all connections in the specified group, except the specified connections |
| Groups | Calls a method on multiple groups of connections |
| OthersInGroup | Calls a method on a group of connections, excluding the client that invoked the hub method |
| User | Calls a method on all connections associated with a specific user |
| Users | Calls a method on all connections associated with the specified users |
Each property or method in the preceding tables returns an object with a SendAsync method. The SendAsync method allows you to supply the name and parameters of the client method to call.
Send messages to clients
To make calls to specific clients, use the properties of the Clients object. In the following example, there are three Hub methods:
SendMessagesends a message to all connected clients, usingClients.All.SendMessageToCallersends a message back to the caller, usingClients.Caller.SendMessageToGroupsends a message to all clients in theSignalR Usersgroup.
public Task SendMessage(string user, string message)
{
return Clients.All.SendAsync("ReceiveMessage", user, message);
}
public Task SendMessageToCaller(string user, string message)
{
return Clients.Caller.SendAsync("ReceiveMessage", user, message);
}
public Task SendMessageToGroup(string user, string message)
{
return Clients.Group("SignalR Users").SendAsync("ReceiveMessage", user, message);
}
Strongly typed hubs
A drawback of using SendAsync is that it relies on a magic string to specify the client method to be called. This leaves code open to runtime errors if the method name is misspelled or missing from the client.
An alternative to using SendAsync is to strongly type the Hub with Hub<T>. In the following example, the ChatHub client methods have been extracted out into an interface called IChatClient.
public interface IChatClient
{
Task ReceiveMessage(string user, string message);
}
This interface can be used to refactor the preceding ChatHub example:
public class StronglyTypedChatHub : Hub<IChatClient>
{
public async Task SendMessage(string user, string message)
{
await Clients.All.ReceiveMessage(user, message);
}
public Task SendMessageToCaller(string user, string message)
{
return Clients.Caller.ReceiveMessage(user, message);
}
}
Using Hub<IChatClient> enables compile-time checking of the client methods. This prevents issues caused by using magic strings, since Hub<T> can only provide access to the methods defined in the interface.
Using a strongly typed Hub<T> disables the ability to use SendAsync. Any methods defined on the interface can still be defined as asynchronous. In fact, each of these methods should return a Task. Since it's an interface, don't use the async keyword. For example:
public interface IClient
{
Task ClientMethod();
}
Note
The Async suffix isn't stripped from the method name. Unless your client method is defined with .on('MyMethodAsync'), you shouldn't use MyMethodAsync as a name.
Change the name of a hub method
By default, a server hub method name is the name of the .NET method. However, you can use the HubMethodName attribute to change this default and manually specify a name for the method. The client should use this name, instead of the .NET method name, when invoking the method:
[HubMethodName("SendMessageToUser")]
public Task DirectMessage(string user, string message)
{
return Clients.User(user).SendAsync("ReceiveMessage", user, message);
}
Handle events for a connection
The SignalR Hubs API provides the OnConnectedAsync and OnDisconnectedAsync virtual methods to manage and track connections. Override the OnConnectedAsync virtual method to perform actions when a client connects to the Hub, such as adding it to a group:
public override async Task OnConnectedAsync()
{
await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
await base.OnConnectedAsync();
}
Override the OnDisconnectedAsync virtual method to perform actions when a client disconnects. If the client disconnects intentionally (by calling connection.stop(), for example), the exception parameter will be null. However, if the client is disconnected due to an error (such as a network failure), the exception parameter will contain an exception describing the failure:
public override async Task OnDisconnectedAsync(Exception exception)
{
await Clients.Group("SignalR Users").SendAsync("ReceiveMessage", "I", "disconnect");
await base.OnDisconnectedAsync(exception);
}
RemoveFromGroupAsync does not need to be called in OnDisconnectedAsync, it's automatically handled for you.
Warning
Security warning: Exposing ConnectionId can lead to malicious impersonation if the SignalR server or client version is ASP.NET Core 2.2 or earlier.
Handle errors
Exceptions thrown in your hub methods are sent to the client that invoked the method. On the JavaScript client, the invoke method returns a JavaScript Promise. When the client receives an error with a handler attached to the promise using catch, it's invoked and passed as a JavaScript Error object:
connection.invoke("SendMessage", user, message).catch(err => console.error(err));
If your Hub throws an exception, connections aren't closed. By default, SignalR returns a generic error message to the client. For example:
Microsoft.AspNetCore.SignalR.HubException: An unexpected error occurred invoking 'MethodName' on the server.
Unexpected exceptions often contain sensitive information, such as the name of a database server in an exception triggered when the database connection fails. SignalR doesn't expose these detailed error messages by default as a security measure. For more information on why exception details are suppressed, see Security considerations in ASP.NET Core SignalR.
If you have an exceptional condition you do want to propagate to the client, you can use the HubException class. If you throw a HubException from your hub method, SignalR will send the entire message to the client, unmodified:
public Task ThrowException()
{
throw new HubException("This error will be sent to the client!");
}
Note
SignalR only sends the Message property of the exception to the client. The stack trace and other properties on the exception aren't available to the client.
Additional resources
ASP.NET Core