Web applications have increasingly turned to real-time data to provide more dynamic and useful features - for example chat, collaborative editing, and real-time analytics. This trend is evident in the .NET world. While .NET is great, real-time .NET is even better.
Similar to the popularity of AJAX leading to more single-page applications and fewer page refreshes, the recent addition of WebSockets and similar real-time protocols in mainstream browsers has lead to more real-time data connections and less “request data on page load and force the user to refresh if they want updated data” applications.
In this article, you’ll learn a simple way to add real-time functionality to your .NET application. The article will introduce two technologies — SignalR on the server and can-connect-signalr on the client — which make setting up real-time connections both simple and quick. We’ll show how to use both of these libraries by making a simple chat application.
While the browser library we’re using is part of the CanJS library, it can be used independently in conjunction with React, AngularJS, or any other JavaScript framework.
Here’s an outline of what this article will cover:
- Introduction to SignalR
- Introduction to can-connect and can-connect-signalr
- Creating a simple chat application
Introduction to SignalR
SignalR is an open-source ASP.NET library designed to create real-time communication between server and client.
Client and server both use remote procedure calls (RPCs) to invoke methods on the client from the server and vice versa.
It uses the best transport available on each browser connection, whether that’s WebSockets in newer browsers or HTTP long-polling in older browsers.
SignalR is built to scale to as many simultaneous clients as needed.
Introduction to can-connect and can-connect-signalr
can-connect is a JavaScript data model library. It helps developers set up model layers that connect to data sources (such as RESTful APIs), provides caching and other performance optimizations, and supports real-time data connections.
can-connect-signalr is a plugin (also called a behavior) for can-connect, which creates a connection to a SignalR backend (called a Hub). SignalR has a client-side script component, which is loaded by can-connect-signalr.
In the next section, we’ll see an example of putting these two pieces together to add real-time behavior to an app.
Creating a simple chat application
To illustrate the relative ease with which real-time can be added to any .NET application, we’ll walk through a short tutorial showing how to create a chat app.
The application is hosted here if you’d like to try it out (open two browsers, type a message, and observe the real-time data).
The JavaScript client-side code is available here and the .NET server-side code is available here.
Step 1: Create a SignalR Hub
The SignalR Hub is the server-side code that pushes updates to the client. The meat of the Hub is located here.
The basic CRUD (create, read, update, delete) methods that need to be implemented include:
- Create
- Update
- Destroy
- GetList
- Get
Here is a pseudo-code version of these methods:
public class MessageHub : Hub
{
// Method should take whatever data is required to create an instance
public MessageModel MessageHubCreate( MessageModel message )
{
PERSIST_TO_DATABASE( message );
message.id // type must have a unique id property
// Any RPC calls to the client related to creation go here
Clients.All.messageHubCreated(message);
return message;
}
// Method should take whatever data is required to update an instance
public MessageModel MessageHubUpdate( MessageModel message )
{
UPDATE_DATABASE( message );
// Any RPC calls to the client related to update go here
Clients.All.messageHubUpdated(message);
return message;
}
// Method should take whatever data is required to destroy an instance
public MessageModel MessageHubDestroy( MessageModel model )
{
DELETE_FROM_DATABASE( model );
// Any RPC calls to the client related to destroy go here
Clients.All.messageDestroyed(model);
}
// Method should take whatever data is required to obtain a list (if any)
public List MessageHubGetList( MessageQueryParams queryParams )
{
List messages = GET_DATA_FROM_DATABASE( queryParams );
return messages;
}
// Method should take whatever data is required to obtain a specific item
public MessageModel MessageHubGet( int id )
{
MessageModel message = GET_RECORD_FROM_DATABASE( id );
return message;
}
}
The naming convention for Hub methods is <HUBNAME>Hub[Create|Update|Delete|Get|GetList].
Also note that the Clients.All
methods directly invoke client-side methods on all connected clients. In the code above, these client callbacks are invoked:
- messageHubCreated
- messageHubUpdated
- messageHubDestroyed
The naming convention for Client callback methods is <hubnameLowerCased>[HubCreated|HubUpdated|HubDestroyed].
Step 2: Create a can-connect-signalr
Client
The file that imports can-connect-signalr
and connects to the Hub is located here.
As mentioned earlier, this plugin can be used with any JavaScript framework, but this example application is using CanJS.
The signalR-related code is simple and straightforward. First, we must import the relevant modules:
import connect from "can-connect";
import signalR from "can-connect-signalr";
Then we must connect to a hub:
Message.connection = connect([signalR], {
Map: Message,
List: Message.List,
signalR: {
url: 'http://signalrdonejs.azurewebsites.net',
name: "MessageHub"
}
});
The Map
and List
properties in the connection point to constructor functions that are called when objects or arrays of objects are passed to the client. In our CanJS example app, we set up a simple constructor using DefineMap:
const Message = DefineMap.extend({
name: "string",
message: "string",
id: "number"
});
The code above is configuring the type of the expected JSON properties. The List constructor simply configures all objects to be of Message type:
Message.List = DefineList.extend({
'#': Message
});
That’s it! That’s the minimum needed to connect a real-time SignalR Hub to a JavaScript client.
By default, the naming conventions described in Step 1 are assumed. If you’d like to change those names, that can be done in the signalR config object.
Message
can retrieve, create, update and delete Message
s as follows:
Method | Description | Details |
---|---|---|
|
retrieves a list of messages | This calls MessageHub 's public List messageHubGetListData(MessageQueryParams queryParams) method which is expected to return a list of matched messages. |
|
gets a single message | This calls MessageHub 's public MessageModel messageHubGetData( int id ) method which is expected to return a single message. |
|
creates messages | This calls MessageHub 's public MessageModel messageHubCreate( MessageModel message ) method with the serialized properties of the client message. MessageHubCreate is expected to persist the message, add a unique [can-connect/base/base.id] property and value, and return the Message 's new data. It should also notify clients that a message was created. |
|
updates a message | This calls MessageHub 's public MessageModel messageHubUpdate( MessageModel message ) method which is expected to update the persisted representation of the message and return the Message 's new data. It should also notify clients that a message was updated. |
|
deletes a message | This calls MessageHub 's public MessageModel messageHubDestroy( MessageModel message ) method which is expected to delete the persisted representation of the message and return the Message 's updated data. It should also notify clients that a message was destroyed. |
Next, we’ll use the Message
API in our application code.
Step 3: Use the Message API in the Application
The final step is to write your app!
Message.getList()
will retrieve all the Message objects from the Hub.
In the chat app, you might write the following to define a ViewModel that captures all the messages:
Note that because getList is asynchronous, we must use a special syntax to resolve the promise. Here’s the ViewModel code in the example application.
// Import the model
import Message from 'ccsr/models/message';
export const ViewModel = DefineMap.extend({
messages: function(lastValue, resolve){
Message.getList().then(function(messages){
resolve(messages);
});
},
},
Then, in a template, you can render the properties of messages:
{{#messages}}
<div class="message">
<span class="name">{{name}}</span>
<p class="message">{{{message}}}</p>
</div>
{{/messages}}
In the example app, the chat-messages
component’s template renders the messages array, and the message-entry
component’s template renders the individual messages.
To create a message, use new Message().save()
. The example app creates new messages in the chat-input-form
(source) as follows:
new Message({
name: this.messageName,
message: this.messageBody
}).save();
That’s it! Your app is now wired up and live. Any real-time changes are going to be automatically reflected across all clients.
The lifecycle of a chat message is as follows:
- A connected client calls
new Message().save()
to create a new message - This client’s
can-connect-signalr
layer connects to the Hub and sends a request that invokes the MessageCreate method. - The Hub persists the change and then calls
Clients.All.messageHubCreated(message)
. - All connected clients (including the one that originally started this process) receive a message through their socket connection (or other transport). A new message object is created in this callback. The message invokes messageHubCreated with the new message object.
can-connect-signalr
adds the newly created message to any Message.List in the application, and if live-binding templates are used, the DOM automatically reflects the change.
Little Code, Impactful Feature
What’s striking about this particular example is how little code is needed to wire up a web application with real-time data. Your existing .NET application might only require a bit of massaging to enable this powerful feature.
I would encourage you to check out the example app’s Hub and Client code for more in-depth sample code. When you’re ready, you can grab can-connect-signalr
from NPM, and start adding real-time data to your .NET application!
Previous Post