SignalR

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
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.
Info
Channel: Coding Tutorials
Views: 2,480
Rating: undefined out of 5
Keywords: asp.net, SignalR, websockets, rock paper scissors, c#, JavaScript, captioned
Id: oQ7OSX7bRto
Channel Id: undefined
Length: 34min 40sec (2080 seconds)
Published: Fri Feb 04 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.