JavaScript
Article

Building a Chat Application with SignalR

By Emre Guneyler

SignalR is an open source library for ASP.NET developers. It’s the equivalent of what Socket.IO is for Node.js (if you want, you can check out a comparison on Stack Overflow). SignalR can be used to provide real-time web features to your applications. Usually, if you are into Ember and JavaScript, you might be inclined to choose Socket.IO and stick to JavaScript. One of the reasons why I chose SignalR is that it has a more extended documentation and several resources to refer to. Moreover, you can get all the benefits of ASP.NET world for free.

In this article, I’ll show you how to build a simple chat application using SignalR. If you haven’t done it yet, I suggest you to read my previous one titled “A Chat Application Using Socket.IO” in order to have a more complete overview of these topics and then compare pros and cons of both the approaches.

Getting Started with ember-cli

We’ll start by creating a new Ember application and we’ll make use of ember-cli. As first task, let’s install some dependencies:

$ ember new chatr
$ ember install semantic-ui-ember

Here we’re installing semantic-ui that is a development framework that helps create beautiful, responsive layouts using human-friendly HTML. It’s very similar to Bootstrap, and it’ll help us with the site’s layout and theme. Once done, we have to import it into the application.

Now, add the following lines to the Brocfile.js file:

app.import('bower_components/semantic-ui/dist/semantic.css');
app.import('bower_components/semantic-ui/dist/semantic.js');

We are now ready to create our routes and add some template. To do that, execute the following command:

$ ember g route chat

This will create our Ember route app/routes/chat.js and template app/templates/chat.hbs. Before adding anything to the template, we’ll make use of some Ember components which encapsulate templates and make them reusable. Let’s start with a chat-room component:

$ ember g component chat-room
$ ember g component chat-userlist
$ ember g component chat-area
$ ember g component chat-useritem

As you can see, we have a lot of components. Each component has an associated template file (app/templates/components/chat-room.hbs) and an Ember component script file (app/components/chat-room.js). In this way we can isolate our chat functionality making it easy to test and reason about. At this point, we can add a chat-room to our chat route:

{{#chat-room
  users=room.users
  messages=room.messages
  topic=room.topic
  onSendChat="sendChat"}}{{/chat-room}}

users, messages, and topic are data we pass to our component, while onSendChat is an action that is fired by our component. If you want to deepen these concepts, you can find more information reading the Ember guides.

Finally, we need an Ember controller (to handle the logic of our chat route) and some Ember models too. To do that, execute the following command:

$ ember g controller chat
$ ember g model chat-room
$ ember g model chat-user
$ ember g model chat-message

Models are useful classes inherited from Ember.Object. They will hold our data and provide data bindings to the templates. The same action is done by controllers, which decorate the models and can handle user actions too.

Open the app/controllers/chat.js file and add the following code to it:

export default Ember.Controller.extend({
  initRoom: function(users, messages, topic) {
    var room = Room.create({
      users: users,
      messages: messages,
      topic: topic
    });

    this.set('room', room);
  },

  addMessage: function(msg) {
    var room = this.get('room');
    room.get('messages').addObject(msg);
  },

  userJoin: function(user) {
    var room = this.get('room');
    room.get('users').addObject(user);
  },

  actions: {
    sendChat: function(msg) {
      // use these methods here to test if they are working
      //this.addMessage('ME', msg);
      //this.userJoin(msg);
    }
  }
});

addMessage() and userJoin() are helper methods that we can call whenever we need to add a new chat message or a new user. sendChat is an action handler fired when a user wants to send a message. initRoom() is our constructor to setup the bindings. You can call it with empty data in setupController() hook of our chat route to test if everything is working properly.

Let’s now edit the app/routes/chat.js file by adding the code listed below:

export default Ember.Route.extend({
  setupController: function(controller) {
    // use this method to test everything is working when data is bound.
    //controller.initRoom([],[], 'hello world');
  }
});

Building Server Side with SignalR

Another useful tool we need to use while working with SignalR is Visual Studio. After downloading it, open it and create a new Empty Web Application project, installing the required packages with the Package Manager Console:

Install-Package Microsoft.AspNet.Signalr

This command will install SignalR and all its dependencies including “Microsoft.Owin”. Then, we go on creating our OWIN startup class to bootstrap our server. To do that, we’ll have the following code in the App_Start/Startup.cs file:

public class Startup {
  public void Configuration(IAppBuilder app) {
    app.MapSignalR();
  }
}

That’s it for running an OWIN based web server. We add SignalR middleware to OWIN, but you can add other middlewares as required (such as authentication or Web APIs). Now our SignalR application can be started by pressing F5. We are not hosting any data, so the browser won’t show anything useful to us. This JavaScript code is dynamically generated by SignalR and ready to be used by our Ember application. It provides us the JavaScript methods that will further call methods on server side.

Creating Lobby Hub

A Hub class is used to communicate with the client. It can call methods on the client and defines methods that are called from the client. SignalR creates a new Hub class every time a new client connects or makes a method call to the server. At this point, let’s see how we can create a lobby Hub:

public class Lobby : Hub {
    private IChatRRepository _repository;

    public Lobby(IChatRRepository repository) {
      _repository = repository;
    }

    public void Join(string name) {
      ChatUser currentUser = new ChatUser(name, Context.ConnectionId);
      _repository.AddUser(currentUser);

      var users = _repository.Users.ToList();
      var topic = "Welcome to EmberJS on SignalR";

      Clients.Caller.lobbyEntered(topic, users);
    }

    public void SendChat(string msg) {
      ChatUser user = _repository.GetUserById(Context.ConnectionId);
      Clients.All.chatSent(user.Name, msg);
    }

    public override Task OnDisconnected(bool stopCalled) {
      _repository.RemoveUser(Context.ConnectionId);
      return base.OnDisconnected(stopCalled);
    }
  }

On every client request, SignalR instantiates a new Hub instance. Hubs don’t maintain any client state. For this reason, we need a database of some sort to keep track of the client state. The IChatRepository interface offers us the required state by providing methods such as AddUser(), RemoveUser(), and Users() to retrieve all the users. The Hub methods can be called from the client, and the Hub class can call client methods using the Clients property.

Below you can find a list that specifies which clients will receive the method call:

  • Clients.All.someMethod(): All connected clients
  • Clients.Caller.someMethod(): Only the calling client
  • Clients.Others.someMethod(): All clients except the caller
  • Clients.Client(Context.ConnectionId).someMethod(): A specific client

As you can see, it has an intuitive API. someMethod() is dynamically dispatched, so it can be anything. For more information about the Hubs API, please refer to the guides.

Back to our example, we have two Hub methods: Join() and SendChat(). When a client connects, it will call Join() with a username. We add the user to our repository and call the lobbyEntered() method on Clients.Caller.

The SendChat() method is called when the client sends a chat message. We retrieve the caller from the repository and broadcast the message to all connected clients by calling the Clients.All.chatSent() method. In turn, it calls the chatSent() method on all the connected clients.

Finally, there are some methods such as OnConnected() and OnDisconnected() that we can override to get notified when a user connects/disconnects. For more information about SignalR API take a look at the SignalR Guide.

Setting up SignalR on Client Side with Ember

Now, let’s turn back to our client application and integrate SignalR. Firstly, install SignalR using Bower:

$ bower install signalr --save

Next, import it into our application. To do that, open again the Brocfile.js file and add the following line:

app.import('bower_components/signalr/jquery.signalR.js');

Finally include the http://localhost:<port>/signalr/hubs script in your app/index.html page:

<script src="assets/vendor.js"></script>
<script src="http://localhost:53246/signalr/hubs"></script>
<script src="assets/chatr.js"></script>

During this phase, pay attention to the order of the elements, that here is crucial since SignalR is exported as a jQuery plugin. So we need to include jQuery first (inside assets/vendor.js), then the dynamic script file at /signalr/hubs, and finally our application on top of it (assets/chatr.js).

Injecting SignalR into Chat Route Using Ember Initializer

When our application starts, we have to create a SignalR connection and later use it in our controllers. The architecture here is up to you. We’ll use an Ember initializer to inject SignalR into our routes. Let’s see how to create it using the previously cited ember-cli.

$ ember g initializer signalr

Let’s now initializing SignalR and injecting it into our routes. The following snippet goes into the app/initializer/signalr.js file:

import SignalRConnection from 'chatr/utils/net/chatr-realtime';

export function initialize(container, application) {
  var realtime = new SignalRConnection('http:/localhost:<port>/signalr');
  
  application.register('realtime:signalr', realtime, { instantiate: false });

  application.inject('route:chat', 'signalr', 'realtime:signalr');
}

SignalRConnection is a wrapper class around SignalR which will surely make our life easier. We create it and inject into chat route using dependency injection. Again, if you need more info, please refer to the complete Ember guides available.

You can check out SignalRConnection class to see how it’s implemented. Here we have two methods of interest:

configureHubs(ctrl) {
  this.OnLobby = new LobbyCallbacks(this, ctrl);

  var lobby = Ember.$.connection.lobby;

  lobby.client['lobbyEntered'] = this.OnLobby['lobbyEntered'];
  lobby.client['chatSent'] = this.OnLobby['chatSent'];
}

Before we start the SignalR connection, we need to set client methods that server can call on the lobby hub. Ember.$.connection is our SignalR connection, while Ember.$.connection.lobby is our lobby hub. These are defined in dynamically generated SignalR code. We set the methods by assigning them to the client property on our lobby hub, that is Ember.$.connection.lobby.client property.

In our example, they are defined in LobbyCallbacks class:

start(name) {
  var self = this;

  var hub = Ember.$.connection.hub;

  hub.error(function(reason) {
    console.log('connection error: ' + reason);
  });

  return hub.start({
    withCredentials: false
  }).then(function() {
    console.log('connected');
    Ember.$.connection.lobby.server.join(name);
  });
}

After defining the client methods, we can start the application by using this method. First we get a reference to Ember.$.connection.hub and here we set the error hook to get notified about any connection errors. Finally, we run a start call in order to start the connection, having a promise in return.

Once connected, we call Ember.$.connection.lobby.server.join(). This method will call the Join() method on the server side Lobby hub. For more information about SignalR client API, please visit SignalR Guides.

Dealing with CORS

At this point we can connect to our server from our Ember application. However, we might encounter some browser errors like the following:

XMLHttpRequest cannot load http://localhost:53246/signalr/negotiate?clientProtocol=1.5&connectionData=%5B%7B%22name%22%3A%22lobby%22%7D%5D&_=1433597715652. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://192.168.56.103:4200' is thus, so not allowed access.

This error may be caused by the fact that your server and client are on different domains. You’ll need to allow CORS on your server to get around it. So, let’s install the package on Visual Studio Package Manager Console:

Install-Package Microsoft.Owin.Cors

Then, configure Owin middleware to allow cross domain requests (edit the App_Start/Startup.cs file):

public void Configuration(IAppBuilder app) {
  app.Map("/signalr", map =>
  {
    var corsPolicy = new CorsPolicy
    {
      AllowAnyHeader = true,
      AllowAnyMethod = true
    };

    // Add the domain where your client is hosted on.
    corsPolicy.Origins.Add("http://192.168.56.103:4200");
    map.UseCors(new CorsOptions
    {
      PolicyProvider = new CorsPolicyProvider {
      PolicyResolver =
        r => Task.FromResult(corsPolicy)
      }
    });

    map.RunSignalR(config);
  });
}

Conclusions

In this article we’ve seen how to glue SignalR with Ember and create a chat application in few simple steps. If you want to see it in action, there’s an interesting live demo at chatembar, and if you want to put your hands in the project, the full source code is available on GitHub, both client side and server side. Moreover, you can refer to another great example of collaborative chat application using SignalR called JabbR.

There are few points I didn’t have the chance to cover in this article that I strongly suggest you to deepen: OWIN and authentication. The good news is that SignalR doesn’t need any special authorization since it works with existing ASP.NET authentication solutions such as ASP.NET Identity.

If you want to know more, here you are some useful resources about Owin, SignalR and ASP.NET Identity:

No Reader comments

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in JavaScript, once a week, for free.