Blog

A Multi-Tenant Real Time App With Apprenda and SignalR

Avatar

By Atos Apprenda Support

SignalR is arguably one of the neatest (and most useful for a certain class of apps) libraries to come along in a while. It’s a framework for “real time” web functionality in ASP.NET. Using SignalR, you can build applications with server components that update web clients in “real time” via push mechanisms. However, because clients (for example, web browsers and mobile phones) have varying capabilities for the connectivity necessary to do this, SignalR handles SignalR.WebSockets.0.3.3 (1)graceful degradation depending on the client. For example, is the client incapable of keeping a constant TCP channel open with WebSocket? SignalR automagically falls back to client-side polling. The effect is that you write code for real time information sharing, and SignalR figures out the rest. In typically thorough form, Scott Hanselman wrote a great post about using SignalR to build a (minimal) chat client here.

Scott picked the quintessential “real time” app – chat. He did it in 12 (or 9) lines of code. I decided to roll with that example and make it multi-tenant (capable of hosting isolated chat “rooms” for each tenant). I’ll show you how, but first, here’s why:

In the contexts of SaaS and multi-departmental enterprise apps, a major architectural tenet that presents some of the more interesting complexities is multi-tenancy. Multi-tenancy has historically been dealt with in ways that are now considered poorly scalable and horribly inefficient. Isolated databases, for example; or worse yet – individual virtual machines. At Apprenda we’re all about multi-tenancy in the runtime. Providing multi-tenancy as a deploy-time platform service to guest applications on the platform not only optimizes the multi-tenancy implementation, but it trivializes the development burden in doing so. Our platform literally transforms app data models into multi-tenant capable databases, and then surfaces contextual APIs that a developer can simply tap into in their code.

In Scott’s example chat app, he uses a SignalR hub and some client side JavaScript to broadcast messages to any and all clients that are connected to the hub.

public class Chat : Hub {

public void Distribute(string message) {

Clients.receive(Caller.name, message);

}

}

When this code is called on the server side, SignalR takes care of calling the ‘receive’ function on all clients (who then perform some action in JavaScript. In this case, update the chat window).

1

Now, SignalR has a notion of Groups, allowing clients to segregate themselves into logical groupings. The server side hub can then call client functions on selective clients. The most fundamental example of this would be if Scott wanted to add chat rooms to his example. He’d allow clients to enter chat rooms (SignalR groups) and then only the clients in that room would be updated. We’re going to leverage that capability and use the Apprenda multi-tenant TenantContext as our grouping mechanism.

The first small bit of code we need to write is the mechanism by which a client will register with the hub. Luckily, all the work is done here on the server side in the hub since the Apprenda contextual API is a .NET runtime API. It actually requires no code change to the client! In the hub, when a new client connects, we’ll group them with other clients from the tenant.

public override Task OnConnected() {
JoinRoom();
return base.OnConnected();
}

public Task JoinRoom(){
return Groups.Add(Context.ConnectionId, TenantContext.Current.TenantId.ToString());
}

The first method there is an override of the base Hub class’s OnConnected() method – this fires when any client connects to the hub. Our override calls our JoinRoom() method that contains a whopping one line of code – Groups.Add() – which takes the current connection id from SignalR, and the TenantContext id from Apprenda. Voila, the new connection is now grouped with other connections from the same tenant.

The next step is to make a slight modification to Scott’s Distribute() method that actually send the chat message to the listening clients.

public void Distribute(string message){
Clients.Group(TenantContext.Current.TenantId.ToString()).receive(Caller.name, message)
}

All we’ve done here is change the original method to scope it down to the SignalR group of connections that are registered in the group named by the Apprenda TenantContext id (which is a unique id for every tenant on the platform). We’ve essentially just created a chat room engine where the room discriminator is keyed from the Apprenda runtime API. We could just as easily do this with another one of the Apprenda contexts if we want to provide different scoping.

2

One way I can envision this being used is as a real-time notification engine across all your Apprenda-hosted multi-tenant SaaS or enterprise apps. Beyond chat and social implications, real time data is a growing force in enterprise business requirements and, thus, software design. SignalR is an extremely elegant framework to work with that does some incredibly powerful things. Coupling it with the Apprenda runtime APIs just adds worlds of possibilities to your development tool belt. This is what modern apps are all about, and whether you’re building a public-facing SaaS app or a private cloud, enterprise-line of business apps, these are the types of technologies that should be making their way into your app portfolio. Especially because frameworks like SignalR running in your apps on the Apprenda platform make it so darn easy to adopt.

Avatar
Atos Apprenda Support

5
View Comments
  1. James McLachlanMay 1, 2015

    I looked into SignalR in a multi-tenant situation too, and the challenge for us would be to limit message group access to certain users. That is, keep members of one tenant from receiving messages for another tenant. You can do custom authorization, but it’s not clear that you can use it to limit access to individual groups within a hub. Perhaps the solution is to add hubs dynamically per tenant, if that’s possible.

  2. MarkJune 23, 2015

    James, I think Matt is actually giving you the way to do it. I stumbled onto Groups facing this same challenge and wanted to see how others implemented this. I was shocked to find very little results for SignalR multi-tenant. We are not using Apprenda but we do have client/environment info in our authentication token so can use that. The security context is applied all the way up the stack so there is now way a tenant could see another tenant data by pushing out through Groups.

    Matt, glad to have some reassurance we’re not the only ones leveraging Groups for this real world case.

  3. Matt AmmermanJune 25, 2015

    @Mark – Groups seemed like a logical approach to tenant isolation – and you got it, the point is that you need a unique identifier for each Group. When running on Apprenda, our APIs provide those identifiers at various scopes (user, tenant, application, etc.) Glad to see you used the same approach!

    @James – Isolation via Hubs wouldn’t be necessary if you use Groups as isolation… Groups will only broadcast to the clients in a Hub that are within that Group, thus we broadcast *only* to the users within a particular tenant.

  4. Matt AmmermanOctober 17, 2016

    Hey Jason – thanks for the response.

    In my example I’m actually using Apprenda’s multi-tenancy constructs the security mechanism, as that is what determines which SignalR group a client joins. Arguably the example could be expanded to increase security (validation of group membership, more strict membership requirements), but the approach is fundamentally sound in that it primarily bases group membership on the tenant a user/client belongs to, then simply uses the SignalR groups as the broadcast mechanism. Totally agreed that if clients were put in groups arbitrarily upon join/rejoin this would not be close to a secure implementation.

Comments are closed.