Introduction
SignalR's main goal is to deliver a real-time experience over HTTP, where both clients and servers can participate in a conversation as senders and listeners at the same time (known as full-duplex communication). A client initiates the flow by starting a new connection, but after that, both types of actors are equally capable of sending and receiving bits of the conversation.
In order to deliver such an experience, SignalR comes with two distinct APIs: one called persistent connection, which we can consider to be the low-level API, and one called Hubs, which is built on top of the former and brings a higher-level set of concepts and an easier and straightforward experience for the developer. We'll talk about Hubs first as we want to make our way through the SignalR features, starting from the simplest to use to the more sophisticated.
A Hub can be seen as a set of methods exposed by a server that any client can connect to, in order to perform actions on that server or to retrieve data from it. If you are familiar with the MVC pattern, it looks like a controller because it offers a way to expose the server-side functionalities through methods whose names we are free to define as we prefer. The usual way to exploit this capability is to use names related to our business logic, and SignalR's Hubs are no different; they enable us to do so on top of a bidirectional communication framework.
Previously, I decided to define a Hub as a set of methods and not just an object or a type (which it is technically), because I wanted to highlight the fact that, when connected to it, we should not expect to have any strong reference or link to a particular instance of a Hub. The Hub life cycle is not under our control; we should avoid putting any instance state in it and expect it to be kept across all the calls. If we keep these concepts in mind, we will be in a better position to write good SignalR code.
In the same way, a client has no power to select a specific Hub instance to talk to. A server should avoid talking to specific clients, and talk to specific sets of clients instead. A Hub should refer to clients according to the following sets that are offered by the SignalR platform:
- The caller
- Every connected client
- Every connected client but the caller
- Every client belonging to a specific group (more on what a group is will be covered later on)
- Exclusion lists that could be passed to most of the previous sets to identify exceptions
There are some other variants of this schema; however, in general, these should be the targets of any server-side calls. The main reason for this choice is scalability; the less state about the connected clients we have to maintain on the server, the more concurrent clients SignalR can serve.
This is the guiding principle that we should try to follow, although there might be the need for slightly different approaches. This is also a simplified description of the problems and of the features around SignalR. The implications could be complex and they are not in the scope of this book. The discussion will try to stay as much as possible on the general case; therefore, the recipes in this chapter will adhere to some general guidelines about how to write good standard clients and good standard hubs. You might have to review and adapt them in case your requirements happen to be more complex.
This chapter will be about how to create Hubs, how to add methods to them, and how a Hub can interact with its connected clients. We will also briefly describe the client-side code necessary to have fully working recipes, but for more detail about the client bits, please refer to Chapter 3, Using the JavaScript Hubs Client API and Chapter 4, Using the .NET Hubs Client API.
For the following recipes, we will always start from empty web application projects, and we'll use simple HTML pages to host the client code. Creating such a project is a fairly common task, so we are going to skip the details about it. If you want more information, you can refer to Appendix A, Creating Web Projects, at the end of the book. The same strategy will apply when creating recurring assets such as a Startup
class or a Hub. We will just briefly mention the need for them. You could go back to Chapter 1, Understanding the Basics, for some more information about the steps required in Visual Studio to get there.
For clarity in the explanation, we may sometimes introduce some intermediate variables in the code to make any related explanation clearer. Of course, you'd want to remove them from your production code and refactor them to a more streamlined version.