
Introduction
In many web projects there are elements whose value must be changed frequently. These can be counters, indicators, notifications and similar elements. Whether to show actual values ​​after updating the page, or can you implement auto-update of all this data? For us, the answer is obvious: if it is possible to dynamically change elements, then there is no space left for updating pages.
For small projects that are not subject to critical loads, as long as they do not have several thousand users online at peak times, it would be an acceptable solution to use AJAX. The logic for this solution is as follows: the client polls the server at specified intervals, looking for updates on the page, if the server reports that there is updated data, then javascript has updated the page elements or displays a notification. The action that will be most appropriate.
But for large projects a solution with AJAX, where each client will poll the server, this will create too much load. Of course, we can optimize our capacities and create a chain of servers throughout the country that will be ready to handle all customer requests. This is not our method. We want the server to notify the client about the new data. This practice is used in desktop applications — a server to which clients connect using sockets. This logic would be useful to us in the web. There are already Websockets with which you can work, even .Net took the support of websockets under the wing. But, objectively, it’s still too early to talk about everyday use of websockets. Need something else. It is possible to use longpolling, where we will open a connection on the client and we will not close it at all, waiting for events from the server. No, we continue to look further.
We paid attention to SignalR. We describe how it works. SignalR can use both websockets and longpolling as a transport. Transportation can be set, but you can leave at the mercy of SignalR, who himself will choose the right one. If you can use websocket, then it will work through websocket, if there is no such possibility, then it will go down further until it finds an acceptable transport.
')
Consider the work of SignalR in more detail when implementing a specific task. What we have: a project, with registered users, each user has a personal account, in which there is a section of messages addressed to this user. There is also a counter new messages. We want that as soon as one user (user1) sends a message to another user (user2), on the open page of user2 the counter of new messages is immediately updated.
To begin implementation, we need to connect SignalR to our project. How to do this can be viewed on
the signalR page , where you can also find the necessary documentation.
If you use NuGet, then it will be enough to execute:
Install-Package Microsoft.AspNet.SignalR -pre
We work
SignalR will use server and client parts. On the server, the hub itself is the essence. This is the Class that will allow us to describe the behavior of SignalR. So it looks like in our example:
[HubName("msg")] public class Im : Hub { public Task Send(dynamic message) { return Clients.All.SendMessage(message); } public void Register(long userId) { Groups.Add(Context.ConnectionId, userId.ToString(CultureInfo.InvariantCulture)); } public Task ToGroup(dynamic id, string message) { return Clients.Group(id.ToString()).SendMessage(message); } } }
We have created several methods.
- Send - will send a message to all customers.
- Register - will help SignalR to find a particular user who needs to send a message. Here you can see the use of Groups, tell about it later.
- ToGroup - will send a message to a particular user group. Saying “users” we mean “group uniting connections”.
Let's look at an example of client code, and begin to understand what's what:
<script src="http://code.jquery.com/jquery-1.8.2.min.js" type="text/javascript"></script> <script src="Scripts/jquery.signalR-1.0.0-rc1.min.js" type="text/javascript"></script> <script src="/signalr/hubs" type="text/javascript"></script> <script type="text/javascript"> $(function () {
Read more about client code. SignalR will generate a hub for us. You can watch it for curiosity
/ signalr / hubsIn the code, when starting the hub (the
start method is used for this), we refer to the
register method. We already use it in server code. How a
UserId or other identifier will be obtained is not important now. We use the simplest solution for ease of understanding. If we did everything correctly, then when accessing the page with our client code, the hub starts, and the register method informs the server that the user userId has connected.
Next, we have the task to tell SignalR about the user with the
UserId identifier, this is necessary so that SignalR can send a message to this particular user when it is needed. It should be noted that when starting the hub, SignalR operates with its connection identifier, and the
UserId is not used. Therefore, we will do so that SignalR can associate its connection identifier with the desired user. Let's see what we can do in the SignalR server code.
The following features are available to us:
Important point: at each new access to the page (reloading the page), including opening it in a new tab, creates a connection with the new connection identifier SignalR. This means that the
UserId will not be enough to notify the user in each open browser tab. We need to notify all the ConnectionId belonging to the
UserId user. For this, in our example of the hub class, in the Register method we added a group with the name
UserId, each new ConnectionId. Now we can operate
UserId as a group name and notify all the ConnectionId of the user.
Private practice
Let's look at another situation.
User2 wrote a message to
user1 and clicked the "Send" button. What are our further actions? You can write additional methods of the hub class and send messages using SignalR. The hub will accept the message, process and notify the user. In addition to sending the message itself with this action, as a rule, additional logic is connected: writing a message to the database, logging, sending a message for moderation, and much more. There may be more complex examples. Therefore, we will limit ourselves to using the hub only for its intended purpose: receiving and sending a message. First we will use the good old AJAX and send a message from the user to HttpHandler. Then we will do with it everything that is necessary (we will write it down in the database or send it for moderation) and eventually send it to the hub, which
notifies user
user1 . But there is a difficulty - HttpHandler is in the depths of one of the many libraries, in a completely different project. We use the SignalR features to eliminate this complexity. Create a proxy class for connecting to the hub:
static HubConnection connection; static IHubProxy hub; static string Url = "http://im.myProjectSite.com";
Take a look at using the
Invoke method. We call our hub's
ToGroup method, which will send the desired message to all connections (
connectioId ) associated with the desired
UserId . Here we also used
static objects. It is enough to initialize the proxy to the hub when the application starts, for example, in
global.asax and, if necessary, call the method in which
Invoke occurs.
With the advent of SignalR in .Net, there is a need to add a few more lines of code:
RouteTable.Routes.MapHubs("~/signalr"); RouteTable.Routes.MapHubs(); GlobalHost.HubPipeline.EnableAutoRejoiningGroups();
Using SignalR routing is somewhat beyond the scope of our task. We only say that
EnableAutoRejoiningGroups will help us not to lose the connection in the group for the user when creating a new connection.
What are you sure to come across and how to solve it
After we have dealt with the examples described and collected a demo project (or embedded in an existing one), we are trying to test it for operability. If everything is done correctly, the user will be notified of the new message, as we wanted. We can even open several browser tabs to make sure that the alert comes in all tabs (all user
connectionId ). But as soon as we open a little more tabs, we will find that nothing works on the N tab. For different browsers, N is different (4 or 6, too small). This restriction does not allow creating more than N simultaneous connections to one hub. In some projects, even large ones, you can see a solution in which the user is informed that he has already opened a similar dialogue somewhere and offers to either switch back to the old one or turn off the old one and switch to this one. The restriction should not affect the user, he can open as many tabs as he wants and we will show notifications in each of them. For this to work, we need to somehow make it clear to the browser that different hubs are being used, and not for one. Earlier, when initializing the hub, we specified it as
Url: im.myProjectSite.com . Create ordinary mirrors:
im1.myProjectSite.com im2.myProjectSite.com im3.myProjectSite.com
And in the client code, we will substitute the address of the hub according to a certain algorithm. The easiest way is every time you access the page (when you open it in a new tab), substitute
im (j + 1) , and
j = 1 again after
im3 . In this example, we increased the limit to 3N.
Conclusion
We have described only general principles using the simplest examples.
When used in your projects, do not forget to pay special attention to safety. If you look at the
Register method of our hub, it will become obvious that anyone can call this method not under their
UserId , which we do not want at all.
The author of the article: Dmitry Lubensky, leading developer Dnevnik.ru. Responsible for the work of the billing system and participates in the development of international direction.