UPDATED 28-08-2018: Added guidance to send message to specific client and not just for all + more screenshots on how _signalContextHub was added.
This is a small blogpost explaining how to use SignalR for ASP Core 2.1 to send a message (or a signal 🙂 ) from the server (controller action) back to the client view. Alot of posts explains how to make various bidirectional chats using SignalR to send messages from client to the server (from view to controller and back again), but the other way around (from server to client) is useful if you want to show progress bars or start a long running task while continually report to the user how it’s going.
Using MVC or Razor Pages can be somewhat linear: User submits something -> a controller action behind the scene handles the input and executes something (ex. a remote runbook) -> when all is done the result is returned to the user (the view). But if you want to report back to the user in-between (before returning) like sending a message to the user that the remote runbook actually started (200 OK) then it’s not possible due to the linearity of the Model-View-Controller flow. Now a simple OK result could be handled by an AJAX call, but then you might be forced to mix javascript clients and C# SDK clients and what if they need to share stuff with each other, it could easily get messy. Personally if I can avoid javascript I usually do that (atleast avoid making business logic, handle page layout etc. is another thing) – signalR to the rescue 🙂
For a more in-depth explanation you can check out this blogpost here. The following is the quick version for ASP.NET Core 2.1 and could be seen as a minified version (cheatsheet?) to get you up and running fast
Initial setup:
Setup is more or less taken from the official Microsoft documentation here
Create a new web app if you don’t have one already
Create folder called Hub (folder is optional) and a new class file in that folder, in this example I have called mine MyHub.cs
This class needs to derive from the Hub class and is just empty for now since we don’t need to call any methods from client, only from server:
You can read documentation here on different methods to implement in this class
In startup.cs:
Javascript setup
Install SignalR client library by opening Package Manager Console in Visual Studio and run the following commands:
npm init -y npm install @aspnet/signalr
You might get some lock errors, but there will be a signalr.js file located here: <NameOfYourProject>\node_modules\@aspnet\signalr\dist\browser\signalr.js
Create a signalr folder at : wwwroot\lib\ within your project and place the file there:
Add client side listener method:
Create a custom javascriptfile at wwwroot\js\ called anything you want, mine is called signal.js (very generic I know, could also be called progressBar.js to state the intention of what you are doing)
Add the following:
// The following sample code uses modern ECMAScript 6 features // that aren't supported in Internet Explorer 11. // To convert the sample for environments that do not support ECMAScript 6, // such as Internet Explorer 11, use a transpiler such as // Babel at http://babeljs.io/. // // See Es5-chat.js for a Babel transpiled version of the following code: //Create connection and start it const connection = new signalR.HubConnectionBuilder() .withUrl("/myHub")*/ //This is the URL from Startup.cs Configure method for route mapping. We're using the base class here .configureLogging(signalR.LogLevel.Information) .build(); connection.start().catch(err => console.error(err.toString())); //Signal method invoked from server connection.on("initSignal", (message) => { console.log("We got signal! and the message is: " + message); //Update paragraph tag with the message sent $("#jobstatus").html(message); });
The first part is making the connection and starting it, the second method is the listener to which we can send messages to from the server instantly!
Check out the documentation here for more options available from the client-side
Javascript references
So now we just need to refer our two javascript files signalr.js and <customfile>.js.
In your view, ex. index.cshtml, paste in the following at the end:
<script src="~/lib/signalr/signalr.js"></script> <script src="~/js/signal.js"></script>
First the library and second your custom file
Server side communication
The fun part! In your Controller action we can now directly and instantly communicate with the client and send messages to this javascript connection we just made called initSignal. You would probably put the logic for this elsewhere (like a repository), but for now we are just going to place it directly in the post action method after the user have pressed Submit:
_signalHubContext comes from dependency injection (love asp.net core 🙂 ) and are added in the constructor of the controller:
Result after pressing submit:
There you go 🙂
Notice that when we redirect the page will refresh and the console message disappear since it’s only temporary until the view returns, but the paragraph message will remain unless you replace it with a message when the view returns. Because of this we can make progress bars (actual progress bars, not just a random spinning gif :D) or we could show status messages to the user while a task is running etc. You could argue that the message goes to all clients and not just the particular client which might not be what you want, but there are other methods to use here which requires a new blogpost to cover, roughly you could pass the client connectionId to your post method and then only send to that. Example: myHubContext.Clients.Client(connId).SendAsync(“initSignal”,”message to connection with Id: ” + connId);
Update 28-08-2018 sending to specific client:
So instead of sending to all clients, you can send to the calling client with a specified connectionId. The challenge is to get the connectionId – for some reason the connectionId cannot be obtained from the client to start with, you have to call the hub class and get the Id and then pass it on to the controller. This idea came from an answer from stackoverflow here. If you are using authentication on your website then it gets a little easier because you can use the signalr inbuilt User or Group methods.
So, this is how I did it:
Added method in hub class:
Client-side calling the GetConnectionId and adding the result as a value on a hidden property on the form so we can pass it on to the controller submit action:
I’m using a .then here to make sure that connectionId is called after the connection is started. Otherwise it would run async and we could get an error.
Then in the HomeController action we are just passing on the connectionId we got from client side:
Hope this was helpful to get you started with SignalR at least. There are alot of posts out there to cover more complex scenarios.
Cheers
Morten
c’est génial! Bon courage
Hi Morten,
thanks for the great post. Exactly what I was looking for. I followed your steps to set up the server and the client. I can successfully establishes the connection but when I try to send a message there are no registered Clients in my hubContext. I am injecting the hubContext into a transient service via the constructor. Everything else looks exactly like you posted. Could this be an issue?
hi Morten,
thanks for the great post. Exactly what I was needing. I set up the server and client exactly as shown here. I have esablished a connection but I have no registered clients in my hubContext. The only difference seems to be that I am sending my message from a transient service where I am injecting the hubContext via constructor.
sorry for very late response, if you still have trouble send me an email with the code. I use scoped service but shouldn’t be a problem.