There is a multi-tenant SaaS app where I want to create groups for each tenantId
s in the SignalR Hub's OnConnectedAsync
/OnDisconnectedAsync
hook.
The problem is that ITenancyContext<ApplicationTenant>
is registered as a scoped service, which means it is only available within the scope of a request. In the context of a SignalR hub, it is not associated with a request, and thus it is not available.
So how do I make it available? Authorize it and enrich the claims somehow?
public sealed class NotificationHub : Hub
{
readonly ILogger<NotificationHub> _logger;
readonly Guid _tenantId;
public NotificationHub(ILogger<NotificationHub> logger, ITenancyContext<ApplicationTenant> tenancyContext) => (_logger, _tenantId) = (logger, tenancyContext.Tenant.Id);
public override async Task OnConnectedAsync()
{
await JoinGroup(_tenantId.ToString());
_logger.LogInformation("{ConnectionId} has connected to the hub. TenantId: {TenantId}", Context.ConnectionId, _tenantId);
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception? exception)
{
await LeaveGroup(_tenantId.ToString());
_logger.LogInformation("{ConnectionId} was disconnected from the hub. TenantId: {TenantId}", Context.ConnectionId, _tenantId);
await base.OnDisconnectedAsync(exception);
}
Task JoinGroup(string groupName) => Groups.AddToGroupAsync(Context.ConnectionId, groupName);
Task LeaveGroup(string groupName) => Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);
}
I think we can create a middleware to implement it. Inject ITenancyContext<ApplicationTenant>
in this middlerware, let call it TenantMiddleware
.
public class TenantMiddleware
{
private readonly ITenancyContext<ApplicationTenant> _tenancyContext;
public TenantMiddleware(ITenancyContext<ApplicationTenant> tenancyContext)
{
_tenancyContext = tenancyContext;
}
public async Task InvokeAsync(HttpContext context, Func<Task> next)
{
var user = context.User.Identity as ClaimsIdentity;
if (user != null)
{
var TenantId= _tenancyContext.Tenant.Id;
user.AddClaim(new Claim("TenantId", TenantId.ToString()));
}
await next();
}
}
Then we can use it in your NotificationHub
class.
public override async Task OnConnectedAsync()
{
// Get TenantId like below.
var user = Context.User.Identity as ClaimsIdentity;
var tenantIdClaim = user?.FindFirst("TenantId");
_tenantId = tenantIdClaim != null ? Guid.Parse(tenantIdClaim.Value) : Guid.Empty;
...
await JoinGroup(_tenantId.ToString());
_logger.LogInformation("{ConnectionId} has connected to the hub. TenantId: {TenantId}", Context.ConnectionId, _tenantId);
await base.OnConnectedAsync();
}