This week, we're going to be looking at something
I've wanted to talk to you about for quite a while, which is the idea of SignalR. And SignalR
is a library - in fact, a couple of libraries, one for the server side, one for the client
side - produced by Microsoft that's layered on top of a more fundamental protocol on the web,
known as WebSockets. And WebSockets addresses a problem that was around in the earliest days of
the web, which is the fact that when using HTTP, what you have is a request/response system. So a
browser makes a request to the server, the server does whatever processing is appropriate, and
then returns a result. But it's always instigated by the browser - by the client. Traditionally,
there's not been any way that if some information comes into the server, by some other means, then
it can push it out and just wake up the browser and say, here I've got some new information for
you. Now, although lots and lots of websites can work without that, anything that has that
sort of interaction - typical example is, say, a chat application where somebody on one browser
types in a message and that immediately appears for another user on another browser - that
needs this kind of two-way communication, the problem used to be fixed. Sometimes
there's what's known as polling, whereby one of the browsers would maybe every five seconds, or
whatever the time period you want, make a request out to the server, and the server would make a
response. But the problem with that would be that if there was no information coming, then you'd be
making this request, every five seconds: Have you got anything for me? Have you got anything for me?
Have you got anything for me? Nothing coming back, it's a waste of bandwidth. On the other hand,
if there is some information, you might miss it, or you might at least delay it by those five
seconds until you make the next request. So what WebSockets, which have been around in its final
version really since 2011 - so just over 10 years now - what this does is gives us a full two way
communication that we can have between the server and the browser. Now, most examples that you get
of WebSockets, or SignalR, if you look on the web, are based on that idea of a chat application,
which is a pretty obvious example of these sorts of things. But I wanted to do something a bit
different and so what I've got is an example where we are doing Rock-Paper-Scissors.
So I showed you Rock-Paper-Scissors, in some earlier videos, specifically, when
we're looking at the Strategy pattern. The slight difference here is that was a situation
where you were playing against the computer, whereas here, what we want is two human players, on
different browsers, different sides of the world, able to play Rock-Paper-Scissors through a
server. And because that needs to push data that's done using SignalR. So let's actually cheat
this a bit and look at the end product that I'm trying to put together. Because I think that'll
make it a bit clearer as to what we're trying to achieve. So if you look at what I've got here,
here, we've just got a page in Chrome connected to our server. And what I'll also do is bring up
a second browser, this time, we've got Edge, and I'm going to point it at exactly the same
website. So we'll just copy the URL out of there. And we see the same sort of thing. Let's just
position these so that we can look at them side by side. And let's put in here the name of one
of our players. So we're going to have ‘Barney’ there. You see it says waiting for another player.
And then if in here I have ‘Betty’ and enter that. Then you can see on the Barney side, we're playing
against Betty on the Betty side we're playing against Barney. And then we can choose what we're going
to go for. So I'll click ‘Paper’ on this side. And you can see it obviously doesn't show Betty what
Barney has selected, it just says ‘Your opponent has chosen.’ So we're waiting, we'll go for Rock
on that side. And then it gives you the result. In this case, Paper beats Rock. So Barney beat
Betty and the score is one-nil, and it will just carry on like that. So that's the end product,
just to show you what we're trying to achieve because there's quite a few steps to build up to
this. Now let's look at the code that we're going to put together. And I've already put together
quite a bit of this. Basically, I've put together the essential game logic, which is all here in
this folder called GameLogic. So what I've got is we've got the signs, that's the enum for Rock,
Paper and Scissors and setting up the rules as to what beats what and what’s a draw. We also have
the idea of a Player that just has a Name and then what they've thrown, as it's called, whether
that's Rock or Paper or Scissors. What their current score is. We've then got the idea of the Game
itself. So that's where most of the logic goes. Don't need to worry too much about that. I'm not
going to go through this in detail, but if you've got any questions do put them in the comments
if when you're looking at the code and there’s any of this you don't follow. But we've also
got the Game and then we've got the GameGroup, which is where we pair up the two players. So for
any one player, we wait to the second one before we begin the game. And all of that is really
logic that could have been implemented in any technology, it doesn't have to be to do
with SignalR. So that's why I prewritten that, it’s just logic, it's not technology. The
one thing in here that I have put in however, that is kind of specific to what we'll need in
SignalR is that this GameGroup has a Name. And all it is is a GUID, so just each GameGroup
has got a unique name. But what we'll see when we come to the SignalR technology itself is that
we can divide our connections - so the individuals, Betty and Barney and individuals who have
connected - we can divide them into groups. So if this were a chat application, you could
have groups representing different chat rooms. But here, the group represents the pair
of players playing a particular game. So that's our starting point. We've also got a very
simple controller that just returns an index page, and then an index page. Here, we have all of
that logic that we're dealing with. So basically, the index page has three sections. So we've got
that welcome that just says ‘Enter your name’, then we've got the next phase where we're waiting
for another player to connect. And then finally, we've got this section where we're actually
playing. And we've got the three buttons, Rock, Paper, and Scissors. And we've got the
various fields where we're going to display the score information and that sort of thing.
And then finally, we've got some JavaScript. And here, you can see, we've got the various bits
of control. So there, we've got the ‘register’, so when they click the button to register,
that's where we're going to go. We also have the clicks for the clicking Rock,
clicking Paper and clicking Scissors, and then various messages, we pop up there.
And we can display there's been a winner, there's been a draw, and then we can switch
through those three phases. Remember, where we had the welcome or the waiting or the
playing that we have the three sections here, so only one of those is ever going to display.
But at the moment, if we just start that up, we'll see if we drag the browser over that
we've got that basic start. But at the moment, nothing is working at all, although we can
press the button, it's not doing anything, because we haven't done the work. So
let's get that working. And with SignalR, there are two sides of the coin. You've got
to write some server code and you've got to write some client code. So let's begin with
the server code. And what in SignalR terms we deal with on the server side is what Microsoft
term a Hub. So Hub is the server connection to which you can talk to and receive messages
from in SignalR. So let's pop in a folder that we'll call Hubs. And then into that folder,
we're going to have a new class that we'll call GameHub. And so we've got the class.
And what we'll do is derive that from a base class called Hub. And we will get hold of
the namespace for that. So you can see there it is in ‘Microsoft.AspNetCore.SignalR’. So thing to
notice, you don't need a separate NuGet package in here, it's actually part of the basics that
you get out of the box when you create the MVC application. So that's our Hub. And then what
we're going to do in our Hub, is we're going to give it a private static GameManager.
So you'll see GameManager was one of those classes we've got inside the GameLogic.
So that's what got there. And we'll just call that ‘_manager’, and then initialise that,
just so we can create an object, then we've got to think about what are the incoming messages we're
going to get from the client. And if you just think about the way this game works, there are two
things, basically, that the client can do. They can either register when they enter their name
and start the game, or they can choose Rock, Paper or Scissors. So those are the two public methods,
we need our Hub. So the first one we'll do is the ‘Register’. So we're going to say ‘public async
Task’, and then we're going to just call that ‘Register’. And that's going to take the ‘name’
that the user has entered on the client side. And then here, we're going to say ‘var group =’ and
then ‘_manager.Register’. So, as I say, all this logic has already been written for us. So there
we are registering the name. If we look at that GameManager Register, one thing you can see it
does have some thread safety in there because we could get multiple requests at the same time. But
basically, that will either start a group, if this is the first user, or complete the group, if it's
the second user, and it returns that group. So we get back the group. And the reason we need to get
back the group is to get hold of that group name, because what we now do is we say ‘await’ and
then ‘Groups’, which is a property we've got from that Hub base class, that is the list of
groups we've got associated with this particular Hub. And then we say ‘.AddToGroupAsync’. So what
we're doing here is we're going to add the current user, whoever has made this Register request
to the group whose name, as we said, is just a GUID, but a unique name for the group. So
we do the ‘AddToGroupAsync’. And then to get the current user, so we don't want the current
user’s name, we actually want their identity. So we use another thing called ‘Context’, which
again, is a property from the Hub base class, ‘.ConnectionId’. So that's the unique
Id that it's assigned to the user. And then that is going to be associated
with this unique name - so ‘group.Name’. And that is really all we need to do there. So
we've registered that, and then associated this user with the group that we've got there. And
then the other thing we need to do, of course, we don't know at the moment, whether this is the
first user, in which case the group is incomplete, or the second one, in which case it's complete. So
then, having got back that group, we can say, ‘if (group.Full)’ and then we're going to decide
whether we need to wait for a second player, or the game can get going. And so if the group
is full, we know that we are ready to go. So we're going to say ‘await’ and then ‘Clients’
- again, a property from the Hub base class – ‘.Groups’. So we're now going to select all of the
clients in this current group. So ‘(group.Name)’, so there should be two of them at this stage, the
two who have registered and been put together into this game, and then we are going to send those
a message. So we say ‘SendAsync’, and then the message I'm going to send them, it's just a
string, we're going to call it ‘GameStarted’, and we'll see on the client side, that we'll have
to respond to that. And then we can send as many or as few additional parameters here as we like,
we are entirely structuring this for ourselves. So what I've decided is that on a GameStarted, I want
to send the two player names. So I'm going to say ‘group.Game.Player1.Name’. And then I'll also
send along there ‘group.Game.Player2.Name’. And so that has notified the client that we're
ready to actually start playing the game. On the other hand, if it's not full, so if there's
only one player in there, then we do the same sort of thing. So we're going to say ‘await’, but in
this case, we just say ‘Clients.Caller’ - so we don't want to send it to the whole group, we
just want to send it to the person who called this Register. So send it straight back. And then
on that one, we say ‘SendAsync’. And in this case, again, I've pre-planned what these are going to be
called. So this one is called ‘WaitingForPlayer’. And so that will just tell them, we're waiting for
the second player to connect. So already, you can see we've got the idea of an incoming message.
So we just have a public method, the parameters are whatever you like. So obviously, the browser
has got to provide a name, but as many or as few, whatever type you like. And then we can see
the idea of configuring a group. So putting these clients into groups, and then sending
messages back, either we can send a message to all the members of a particular group,
or we can send it back to the caller. And there's various other ones as well we can
do for that. But that's basically our Register. So that was the first of the operations that the
browser can perform that the messages it can send to us. The second one, remember, was when the
user actually selects Rock, Paper or Scissors, so that other one will put in is going to be,
again, ‘public async Task’ - so these are always async - and then this one, we're going to
call ‘Throw’. So that's just the term we use for when they select Rock or Paper or Scissors.
And what we're going to have there is a string, which is going to be the ‘groupName’, so that
groupName that we've just been talking about, and then the player who's made the throw, and
then also what their throw was, so ‘string selection’. So that's whether it's Rock
or Paper or Scissors. So we're saying which game is this a part of, which player within
the game, what is the actual move they've made. And then what we're going to do we need to get hold of
the game. So we say ‘var game =’ again ‘_manager’. And then we do a ‘.Throw’ and pass in all of that
information. So we're going to do ‘groupName’, and we're going to do ‘player’ and then we have
a slight problem with the selection that you can see; the selection's coming in as a string, but
we want it as the enum Sign. So we're just going to do an ‘Enum.Parse’ and pass it to this Sign
type. And then we're going to take the ‘selection’ and then also, just to make things a
bit more helpful, we're going to ignore the case on that. And so that's what we've got there.
What this does - what that Throw does - is it looks up the game from this dictionary of
games based on the groupName, it makes the move, and then it just returns the game, because what
we now need to do in here is start dealing with the status of the game. So at this point, it
could be that both players have made their move, in which case the game is complete. Or it could
be that only one player has, say, gone Scissors, and we're waiting for that one. So what we now do
is we say ‘if (game.Pending)’ - so that tells us if we're still waiting for the second player
to make their move. And if that's the case, then we're going to do an ‘await’, we're going
to do a ‘Clients.Group’. So we're going to send a message out to everybody in this particular
game group. And obviously, that's with groupName in there. And then we do the ‘SendAsync’.
And this one, we're going to just call ‘Pending’. And then we're going to send out the name of the
player that we're waiting for. So we can see that game has this ‘WaitingFor’. So if of player one's
played that will return the name of player two, and vice versa. And then if it's
not pending, so if the game is complete - either a win or a draw - then it's a
bit more complicated. So what we're going to do is we're going to get hold of the winner. So
that's going to be ‘game.Winner’ that we can see we've got there, we're going to get hold of the
explanation. So the thing about that winner is, notice, it's a nullable string. So it'll either
give us back a winner, or if it's a draw, it's going to give us back null. And
then we've got this explanation. And that is going to be ‘game.Explanation’. And what that
is, if we just take a quick peek, is basically it tells us ‘Rock beats Scissors’, or ‘Paper beat
Stone’, or it's a draw or whatever it may be, something like that. So that's the explanation.
Then we say, ‘game.Reset()’. And what that does is it increments the score appropriately
- so either player one or player two, or neither in the case of a draw - and then just
resets their Throws, so it's no longer remembering the Rock or the Paper, it sets that back to null
ready for the next game. So we do that Reset, then we've now just got to decide whether it was a win
or draw. So if we say ‘if (winner == null)’ which means it's a draw, there is no winner. Then we're
going to have - and we can do a bit of copying for that - so we're going to send a message out
to the group, so both players in the game. And the message in this case is ‘Drawn’. And
then we're also going to send the explanation. And we're also going to send the overall scores so
they can see how they're doing. On the other hand, if we have got a winner, then again, we can steal
a load of that. But in this case, we say ‘Won’, and we pass through the name of the winner.
Again, the explanation - so what beat what and again - the game scores. Okay, and
really, that's it for the logic of our GameHub. So the actual game logic we saw we'd
already written, we don't worry about that. But that's what we've got. We've just
got the two possible incoming messages, so the Register and the Throw. And then we've
got these various outgoing messages, GameStarted, WaitingForPlayer, Pending, Drawn and Won. So
when we write our client code in the browser, we're going to have to deal with all of those.
Only a little bit more to do now on the server and that's essentially just a bit of registration.
So what we need to do is go into our Program where we’re doing the start-up, and then we've got to
do a ‘builder.Services.’ and then ‘AddSignalR’. So that just registers that we're going to use
SignalR. And then down here, we also we're going to say ‘app.’ and then we have a ‘MapHub’. So
that Hub is the Hub we're talking about in terms of the GameHub. And that's a generic, so there, we
put in GameHub, and just get hold of the namespace for that. And then we register that with the
endpoint that's going to be used from the browser. So we're just saying there ‘/gameHub’. And that's
very commonly what you'll have, it'll be exactly the same name as the class. But they don't have
to be associated, you could have all sorts of other things you want. And so that really should
be that all working. And we give that a quick test build. Seems happy. So that's okay. So
that's the server side. Now we've got to deal with the client side. And the client side needs
to have, obviously, a JavaScript library to help it to understand SignalR. Now I mentioned
SignalR is a layer on top of WebSockets. There are lots of JavaScript libraries out
there that put a layer on top of WebSockets, so that you can use them in various environments,
we're going to use this week, the SignalR one, just to keep it simple, to keep it all consistent.
But as you can imagine, there's no requirement at all that whoever's writing your server is the same
person who's writing your client, they may want to use an entirely different technologies. So
in the next video, we're looking at alternative client side implementation. But for now, we're
going to go just with the Microsoft one. So what we can do for this, if we go to wwwroot, and then
all sorts of ways we can get hold of a client-side library, we could use a Content Delivery Network,
but I'm just going to do ‘Add’, and then go for ‘Client-Side Library…’ And then we'll go
for this ‘unpkg’ as our provider. And then lots of choices to go for, but I'm going
to go for ‘microsoft’ and then ‘/SignalR’. And you can see it's given me that option. So we'll
go for SignalR. And it actually shows us quite a lot of files in there. But I'm just going to
keep this simple. So all I'm interested in is to go for this ‘signalr.js’, don't
even need the minified version, though we would need that in a more practical
application, we'll just go for that ‘signalr’. And so there you can see that's included right down
there. There, we've got our ‘signalr’. Okay, so now we've got that in there under the wwwroot,
which means it will be served up as a static file to any browsers. Let's now put it in our browser
code. So we saw we've already got this ‘game.js’. And we can see that in the index view down
there, we're getting hold of that ‘game.js’, but what I also need to do is get hold of that
SignalR library that I just put in there. So what we're going to do here is we're going
to do our ‘/microsoft/signalr/dist/browser’ and then ‘/signalr.js’. So that will make
use of the library. And then all we've got to do is hook that up into our actual ‘game.js’. So
the very first thing we're going to have to do, we're going to say ‘let’ and then we're going to
have a ‘connection’, which is our connection back to the server. And so we're going to say ‘= new
signalR.’ And then we have this thing called a ‘HubConnectionBuilder()’. And then we say
‘.withUrl’. And then in there, we put our ‘/gameHub’. So remember, that's
what we had in the Program when we'd registered our Hub. So that's what we're
looking for up here. And then we finally just do a ‘.build()’ on that. So that has made
our connection back to the server. We then need to start that up. So we're
going to say ‘connection.start()’. And then this uses a promise, which we looked
at ages ago when we were looking at JavaScript. So we have a ‘then’ and then in there, we have an
arrow function with no parameters. And all that's going to do is call this ‘initialize’. So I've
already got an ‘initialize’ method. You can see previously I was just calling it straight off.
We don't want to do that now. We only want to call the ‘initialize’ once the connection has been
established, that we're doing with that ‘start’. Then you can see in the ‘initialize’ at the moment
I just set up the button clicks and that sort of thing. But the extra thing I've got to do now
is actually put in all of those responses to the various methods we're sending out. So remember, we
had things like our Won, our Drawn, our Pending, our WaitingForPlayer, our GameStarted. So we've
got to now put in responses for those. So what I'm going to do is I say ‘connection.on’, and then
the name of one of these. So let's go for that first one, WaitingForPlayer. So that's what's
going to happen when WaitingForPlayer gets sent. And then what we do with that is … second
parameter in here is going to be again, a lambda, a lambda taking no parameters, because
WaitingForPlayer didn't send any additional parameters. So you've got to match these up by
hand. But that's what we're doing. So that's no parameters in there. And then all I'm going to
do there is call this method called ‘switchTo’, which I've declared down here, and
we're going to switch to a screen called ‘rps-waiting’. So ‘rps’: Rock, Paper,
Scissors. And then that ‘rps-waiting’ is simply this area here is now going to be displayed,
where it says 'Waiting for another player to connect’. So that's our WaitingForPlayer, then we've
got to go through all of those other ones. So we're going to have a ‘connection.on’. And then
we will have our ‘GameStarted’. And ‘GameStarted’, if we look back … actually, I just noticed
I made a mistake, because GameStarted needs to take three parameters: the names of the two
players plus the name of the group itself. And this is the sort of problem we have - that there's
no strong typing on these methods. So we've got to manually, effectively, get these parameters
matched up. So that would have hit a bug if we got that far, but I managed to spot that early.
So we've got them in there. So those are going to be the parameter to the arrow function – so ‘(p1,
p2,’ and ‘id’ for the group name. And we're just going to forward those to the function we've
already written called ‘startGame’, ‘(p1, p2, id)’. And again, we can see that we've already
got that ‘startGame’ down here that just sets everything up, tells you who your opponent is,
that sort of thing. Next one we're going to do so another ‘connection.on’. And this one is going to
be for Pending. So this is when we're waiting for the other user to make their move.
Again, if we look at Pending here, we can just see it takes the one parameter:
who we’re waiting for. So we can just say, ‘waitingFor =>’ and then we have a method, again
predefined, called ‘pending’ that just takes that. So there's our ‘pending’ down there. So either we
say your opponent has chosen, so they're waiting for you, or we're waiting for them. So that's what
we've got on that one. And then just a couple more to do, we've got to have our ‘connection.on’. And
then if it's a draw. And again, if we just look back here, the Drawn takes the explanation and
the scores. So in here, we put ‘explanation, scores’ as the two parameters coming out. And then
again, we've got the real hard work already done. So that just takes ‘explanation’ and
‘scores’ and basically just displays those on the screen. And then the very last
one ‘connection.on’. And this was called ‘Won’. And again, winner, explanation and scores.
So we're going to have ‘(winner, explanation, scores)’. And, again, already predefined
a method for that one. That takes winner, explanation and scores. And if we just scroll
down to that, there we can see it just again prints out the results. So we've now hooked up the five
messages that we can get from the server from the GameHub, and mapped them onto some JavaScript that
we've written in advance. What we haven't done though yet is called any of the methods actually
available on the GameHub. So on the GameHub, remember, we've got this Register, and we've
got this Throw. And so those we now have to call from our JavaScript obviously through that
connection, so that's what we need to do. So next thing we've got to do is, in here in, our
‘register’ function, so that's what happens when they click the button. And you can see we've
just got hold of the ‘myName’, which we've popped into there. So that's the name they've
entered. But then we've got to send this name to the server. So we do ‘connection’ again. But
in this case, we call a method called ‘invoke’. So we had ‘on’, which is setting up the function
that will get called when a message comes from the GameHub, but ‘invoke’ is what we have to send
to that. And then we're calling it ‘Register’ because again, that's what it was called there, and
we can see it's expecting one parameter. So that's what we do, we send along the one parameter, which
is just that ‘myName’ that we've just set up. But then we just got the one other thing,
which is worry about what might happen if that goes wrong. And so again, we're dealing
with promises here, so I can do a ‘catch’ on that. And in the event of a catch, what I'm going to
do is just take whatever the error message was, and then we have a ‘rps-error’ div that we've got
in there. And I'm going to set that ‘.text’ on that, and then just pass in that error message. So
that's done our Register. And then remember, our Register, of course, is going to send back either
the GameStarted or the WaitingForPlayer, so that'll move things on through there. And then the other thing
we've got to do, remember, as well as the register, we've got to have the ‘Throw’. And so that's going
to be down here in our ‘throwHand’. So I'm sure you can work out what's happening here now. We
do a ‘connection.invoke’, it's called ‘Throw’. And this one as parameters takes the ‘groupName’,
the ‘player’ and the ‘selection’. Well, at the moment, we don't actually have the group
name. So let's just hook that one up as well, we nearly had it there. Remember, the GameStarted
started is what sends us the group name is what we've called ‘id’ there. So let's just
up here, do a let ‘groupId =’ and then just set that to blank to start with. But
then on the startGame, what we need to do is take that ‘id’ and say ‘gameId =id;’ So that
will now hook up that. And so that means in our ‘throw’, we can now pass in those parameters.
So the groupName the player and selection, so the groupName is going to be ‘gameId’,
the player is just going to be ‘myName’ that we've already set up, and then the
actual throw is going to be the ‘selection’ that we've got coming in there. So that's sending those
three parameters that we need. And then we again, just need to put on there a ‘catch’. And in fact,
it can be exactly the same as that one that's just popping out the error message. And so that really should
be it. So in our Hub, we had the endpoints ‘Register’ and ‘Throw’. And we've invoked those
at the appropriate time. And then we also had the various ‘SendAsync’s. And we've hooked
those up. And if that's all done correctly, we should see this now starts
working. So let's run that up. So there we've got the Chrome again, let's bring
up Edge and go to the same page. And then let's just hook that up side-by-side. And then let's
enter ‘Barney’ in that one. Click on ‘Enter’, seems to be working – ‘Waiting for another
player to connect.’ In here, we'll put ‘Betty’, enter that and then we can see we're ‘Waiting for
Betty’, ‘Betty wins’ ‘Rock beats Scissors’. So quite a lot of complexity in the game logic there.
But in fact, the SignalR is relatively simple. Create a Hub on your server side derived from the
Hub base class, you have your incoming methods, in our case it was Register and Throw, you make
sure you register your Hub as part of your Program as part of the start-up. And then also you can
send async messages out. And the JavaScript has to register how it's going to respond to those
and make the program run. So hope you enjoyed that. As I say, although on the server side,
using SignalR is pretty much what you always going to do in a Microsoft environment. On the
client side, in the browser, you could be using all sorts of different technologies. And so next
time, we'll look at how you could have consumption of a .NET SignalR server, but consuming it
in an Angular front end. So do look forward to that. If you enjoyed that, do click ‘like’. Do
click ‘subscribe’ and I'll see you next time.