Course contentsShow
← C#
Lesson 1469 of 1,90813. Real-time Communication with SignalRFree lesson

Dependency Injection within Hub Scopes

Manage transient, scoped, and singleton service lifetimes appropriately within Hub operations.

In this lesson, you will learn how to correctly manage service lifetimes when injecting dependencies into your SignalR Hubs to ensure both performance and data integrity.

The Hub Lifecycle Challenge

In SignalR, a new Hub instance is created for every single method invocation. Unlike a standard Controller where a scope might persist through a single HTTP request, Hubs have a transient nature. When you inject services into a Hub, their lifetime behavior depends entirely on how they were registered in your IServiceCollection.

Understanding Lifetime Implications

  • Singleton: Services registered as singletons (like an IConfiguration or a global caching service) are safe to inject. They persist across all Hub instances and all connections.
  • Transient: These are created fresh for every injection. If you inject a transient service into your Hub, you get a new instance every time a method is called. This is fine for lightweight, stateless logic.
  • Scoped: This is the most critical area. In SignalR, the "scope" is tied to the Hub method invocation. If you inject a scoped service (like a database DbContext), it will be created when the Hub method starts and disposed of when the method finishes.

Implementation Example

Consider a scenario where you need to log user actions in a database during a real-time chat:

public class ChatHub : Hub
{
    private readonly AppDbContext _dbContext;

    // The DbContext is scoped to this specific method call
    public ChatHub(AppDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public async Task SendMessage(string user, string message)
    {
        _dbContext.Logs.Add(new Log { User = user, Timestamp = DateTime.UtcNow });
        await _dbContext.SaveChangesAsync(); // Transaction ends here
        await Clients.All.SendAsync("ReceiveMessage", user, message);
    }
}

Because the Hub is instantiated per-call, you should never store "connection-specific" state inside private class fields of the Hub. If you need to maintain state across multiple method calls for a single connection, use the Context.Items dictionary or a separate persistent store, rather than relying on service fields which will be destroyed and recreated.

Key Takeaway: Since Hub instances are transient, treat every method invocation as an isolated unit of work; inject scoped services to ensure they are properly cleaned up after each individual message process.