DotNetify version 3.1 is out, and with it a new capability to write multicast view model classes on cross-platform ASP.NET server.
What is a multicast view model? Assuming youâre already familiar with dotNetify and its server-side MVVM approach to web app development, itâs a server-side view model where you can share the same instance with multiple SignalR client connections. Any data update to the view model, either by the server or any of the client, will be simultaneously sent to all.
Why multicast view model?
There are many applications for this, practically everything that requires real-time communication to or among multiple clients, i.e. a simple IoT broadcast, chat apps, multiplayer games, and all kinds of online collaborative tools.
But surely this can all be done with just a SignalR hub class like every chat tutorial love to show? The answer is yes, but⌠letâs take a look at any of those chat app examples. It is deceivingly simple, for if you continue to go down that path and start to build more advanced features, you would likely end up with complex, tightly-coupled codeâââboth on client and serverâââthat arenât easy to write tests for!
MVVM design pattern helps keep your code highly testable by separating the business logic from infrastructural concerns. DotNetify abstracts out the nitty-gritty of SignalR that for the most part you wouldnât even know itâs there, so you could concentrate on building the features your customer wants.
How it works
This is how you define a multicast view model:
public class MyChatApp : MulticastVM{}
Itâs a plain C# class that inherits from a dotNetifyâs base class. For the client-side scriptâââweâll use React for this, weâll add a component to connect to this view model:
Whenever this component is rendered on multiple browsers, the server will always give it the same view model instance.
Letâs add the textboxes for some basic chatting:
And hereâs the complete server-side view model that will produce the same result as the official SignalR chat example:
public class MyChatApp : MulticastVM{public List<string> Messages { get; } = new List<string>();public Action<string> Message => this.AddList("Messages", msg);}
Notice that weâve completely removed direct SignalR method invocations from the code, and use property / command bindings to get the data flowing seamlessly between the server and all the client browsers that are connecting to this view model.
When you do need to get more information on the connecting clients, you could inject IPrincipalContextAccessor
and IConnectionContext
to the view model. These interfaces provide ambient context on the currently connecting client, such as the SignalR connection ID, claims-based identity, IP address used, and so on.
Partitioning instances
Beyond just a single instance, some use cases will want to have a different instance for a different group of connections. For example, an authenticated user can simultaneously connect from a desktop and a smartphone and share one view model instance thatâs not shareable with other users.
SignalR has the capability to define and broadcast to groups, which is scalable to multiple servers when Redis is configured. With MulticastVM
, defining a group is as simple as overriding the GroupName
property:
public class PerUserVM : MulticastVM{private readonly IPrincipalAccessor _principalAccessor;
public override string GroupName{get => _principalAccessor.Principal.Identity.Name;}
public PerUserVM(IPrincipalAccessor principalAccessor){_principalAccessor = principalAccessor;}}
The GroupName
value will be dynamic, based on the context of the calling connection. The way this works is, when an authenticated client with the identity name UserA connects, a new instance will be created and associated with the clientâs group name âUserAâ. When UserA again connects from a different device, thus a different connection, it will be given any existing instance with matching group name. And since the identity name is the same, both devices will use the same view model instance. But when UserB connects, there will be no matching group name, and another instance will be created and associated with âUserBâ group name.
Using this mechanism, you could extend this to check the group membership of a connecting user from a data store, and create partitioned view model instances for any particular group.
Direct message
What if we need to send a message to only certain users? With IConnectionContext
you would be able to associate the connected users with their SignalR connection IDs, and once you have these, use Send
base method to send messages to one or more connections of your choosing:
Send(new List<string>{ connId, ... }, stateName, stateValue);
How to get started
If youâre new to dotNetify, you probably have a lot of questions on the example code above. Do visit dotnetify.net website, where the APIs are explained in details. There is plenty of other materials there: live demo, templates, and even UI toolkit for React. Everything is free and open source.
Thankâsâ for reading!