Display Live Server Feedback to the Client






4.71/5 (9 votes)
Broadcast information to the client browser and display the progress while the server is processing a user request using MVC and SignalR.
Introduction
In this tip, we will create a simple MVC application using the SignalR
library in which we will display a live stream of messages to inform the user what's going on in the background. These messages are generated within the Business Logic Layer while processing a user's request. In our example, the client will "try" to make our CrazyServer
visit all the Seven Wonders of the World by clicking a button, "Start Working".
Background
There are times when a little bit of time is required to process a user's request. In such a case, you may schedule it to be processed later in batches or let the user wait until the process is over. In addition, you can also provide a live feedback and display a stream of progress notification to the user to keep them updated with what's going on in the background. SignalR
makes this "incredibly simple" by automatically doing a lot of dirty work for us with just a few lines of code.
Using the Code
Setup Presentation and BLL Projects
- Start Visual Studio (this tip makes use of VS2015 Community Edition), create a New C# based "ASP.NET Web Application" and name it "
SignalR_LiveFeedback
". - From the "Select a Template" dialog box, select "MVC" and change the authentication to "No Authentication". Click on Ok to create and open your ASP.NET MVC project.
- In the solution Explorer, right click your solution name and click on Add a new project. In "Add New Project: dialog box, select "Class Library" and name it your new project, "
BLL
". - We will now add the
SignalR
library to both our projects, "SignalR_LiveFeedback
" and "BLL
". To do this, select "SignalR_LiveFeedback
" from the Solution Explorer. Open "Package Manager Console" from the "Tools" menu. - In the "Package Manager Console" make sure that, "
SignalR_LiveFeedback
" is selected as your Default Project. Now, type the following command to install the Library in the selected Project.Install-Package Microsoft.AspNet.SignalR
- After the
SignalR
package has been installed, change the Default Project to "BLL
" and execute the same command again in the Package Manager Console. This will install theSignalR
package in your BLL project as well.
In the BLL project, we do not needSignalR
script files which were installed by the Package Manager Console. You can delete these and other extra files. - In the
SignalR_LiveFeedback
project, open the "Add New Item" dialog box and select, "OWIN Startup class". Name this class as "Startup.cs". - To configure your application to make use of
SignalR
, in yourStartup
class, add the following code in theConfiguration()
method.app.MapSignalR();
Setup SignalR Hubs
- Create a new class in your BLL project, name it "MyHub.cs" and write two methods,
SendMessage()
andSendDoneMessage()
as shown in the code snippet which follows:using System; using Microsoft.AspNet.SignalR; namespace BLL { public class MyHub:Hub { private string _ConnectionId; public MyHub(string connectionId) { _ConnectionId = connectionId; } public void SendMessage(string message) { var context = GlobalHost.ConnectionManager.GetHubContext("MyProxyHub"); context.Clients.Client(_ConnectionId).broadcastMessage(message); } public void SendDoneMessage() { var context = GlobalHost.ConnectionManager.GetHubContext("MyProxyHub"); context.Clients.Client(_ConnectionId).broadcastDoneMessage("DONE"); } } }
We need to broadcast the messages from our Business Layer. To do this, we will create a Proxy Hub in our Presentation layer and use it to broadcast messages out of our BLL project.
In the code above,
GlobalHost.ConnectionManager.GetHubContext()
enables us to refer to "MyProxyHub
" which is present in theSignalR_LiveFeedback
project. We will shortly create this class.The
broadcasMessage()
is a dynamic method. You can create and name your own method and use this name to link or refer to it in the JavaScript code which we will write in the view, Index.cshtml later in this tip.Note that we are making use of a property,
_ConnectionId
. ThisId
is generated bySignalR
to uniquely identify each client which is connected to the Server. We can use thisConnectionId
to send messages to a particular client. - Create an empty class in your
SingalR_LiveFeedback
project, name it "MyProxyHub.cs" and change the code as shown below:using System; using Microsoft.AspNet.SignalR; namespace SignalR_LiveFeedback { public class MyProxyHub : Hub { } }
Develop the Business Logic
- In the BLL project, create a class and name it, "Wonder.cs". Include the following properties and a constructor as shown below:
using System; namespace BLL { public class Wonder { public string Name { get; set; } public bool IsVisited { get; set; } public Wonder(string name) { Name = name; } } }
The property,
IsVisited
is set totrue
as soon as the CrazyServer visits it. - In the "
BLL
" project, create another class and name it "CrazyService
".
In this service class, we will create two methods,DoSomething()
andSetHubConnectionId()
. In ourDoSomething()
method, we will perform a random task which takes a bit of time to execute. While this method is executing, we will send messages to the user. - In the
CrazyServer
class, create twoprivate
properties,_HubConnectionId
and_WondersList
. Include a parameterless constructor to initialise_WondersList
with names of Seven Wonders of the World.private string _HubConnectionId; private List<Wonder> _WondersList; public CrazyService() { _WondersList = new List<Wonder>(); _WondersList.Add(new Wonder("Great Wall of China")); _WondersList.Add(new Wonder("Petra")); _WondersList.Add(new Wonder("Colosseum")); _WondersList.Add(new Wonder("Chichen Itza")); _WondersList.Add(new Wonder("Machu Picchu")); _WondersList.Add(new Wonder("Taj Mahal")); _WondersList.Add(new Wonder("Christ the Redeemer")); }
- Include the following code in the
DoSomething()
method.public void DoSomething() { //Perform a task or an activity in the background Task.Factory.StartNew(() => { MyHub myHub = new MyHub(_HubConnectionId); int prevWonderIndex = -1; //Do some random task and keep the server busy for (int i = 0; i < (new Random()).Next(7, 15); i++) { int selectedWonderIndex = (new Random()).Next(0, _WondersList.Count); //Try not to visit the same place consecutively for (int x = 0; x < 3 && prevWonderIndex == selectedWonderIndex;x++) { selectedWonderIndex = (new Random()).Next(0, _WondersList.Count); } Wonder wonder = _WondersList[selectedWonderIndex]; prevWonderIndex = selectedWonderIndex; string message = "I visited " + wonder.Name + (wonder.IsVisited ? " once again." : "."); wonder.IsVisited = true; myHub.SendMessage(message); Thread.Sleep((new Random()).Next(1000, 5000)); } //Display end result of the task int wondersVisitedCount = _WondersList.Count(x => x.IsVisited); if (wondersVisitedCount == _WondersList.Count) { myHub.SendMessage("Yay, I was able to visit all the Seven Wonders of the World!"); } else { myHub.SendMessage("Argh, I could not visit all the Seven Wonders of the World."); } //Notify the client that the task is over myHub.SendDoneMessage(); }); }
TheDoSomething()
method uses the class library, "Random
" to bring a level of uncertainty in program execution time and end result. The code which we have written picks a random place to visit from the_WondersList
list. It then checks whether it had visited the same place in its previous visit (consecutively). If it has, then the logic will try to pick a different place to visit within a maximum of three attempts . - Next, write the following code in the method,
SetHubConnectionId()
. This method helps us informSignalR
to send messages to a particular client and not everyone connected to the server.public void SetHubConnectionId(string hubConnectionId) { _HubConnectionId = hubConnectionId; }
Develop the Controller
- In the
SignalR_LiveFeedback
Project, create a new controller, "CrazyServerController.cs". - Create an empty
Index()
action if it hasn't already been created for you by Visual Studio.public ActionResult Index() { return View(); }
- Create another
Action
,StartWorking()
and include the code shown below. We will use this action to make the server do something.public ActionResult StartWorking(string hubConnectionId) { CrazyService crazyService = new CrazyService(); crazyService.SetHubConnectionId(hubConnectionId); crazyService.DoSomething(); return Json("SUCCESS",JsonRequestBehavior.AllowGet); }
Develop the User Interface
- We will now edit the
Index
view and create our User Interface for the client to use. If your Index.cshtml view is not already created, right clickIndex()
in the source code and click on Add View. - Replace the code written in Index.cshtml with the following:
@{ Layout = null; } <!DOCTYPE html> <html> <head> <title>Crazy Server</title> <style type="text/css"> .container { background-color: #b5b5ff; border: thin solid black; margin:5px; padding: 15px; } </style> </head> <body> <!-- Start a task (logic) from executing on the Server --> <div class="container"> <input type="hidden" id="hubConnectionId" /><h3>Visit Seven Wonders of the World</h3> <button id="btnStart" onclick="btnStart_Click()">Start Working</button> <button onclick="btnClear_Click()">Clear</button> </div> <!-- Display messages broadcasted from the server --> <div class="container"> <table> <thead> <tr> <td><h3>Crazy Server's Activity</h3></td> </tr> </thead> <tbody id="tbodyContainer"> <tr><td></td></tr> </tbody> </table> </div> <!-- Script Libraries --> <script src="~/Scripts/jquery-1.10.2.min.js"></script> <script src="~/Scripts/jquery.signalR-2.2.0.min.js"></script> <!-- Auto generated SignalR hub script --> <script src="~/signalr/hubs"></script> <script type="text/javascript"> $(function () { //Reference to SignalR Hub var proxyHub = $.connection.myProxyHub; //Function which links to BLL.MyHub.SendMessage (Business Layer) //through a proxy Hub, SignalR_LiveFeedback (UI Layer), MyProxyHub proxyHub.client.broadcastMessage = function (message) { DisplayMessage(message); }; proxyHub.client.broadcastDoneMessage = function (message) { if (message == "DONE") { $("#btnStart").prop("disabled", false); DisplayMessage("Work Completed"); } }; DisplayMessage("Connecting to Crazy Server..."); //Establish a connection with the server $.connection.hub.start().done(function () { var hubConId = $.connection.hub.id; $("#hubConnectionId").val(hubConId); DisplayMessage("Connected to Crazy Server"); Start(hubConId); }); }); function Start(hubConnectionId) { DisplayMessage("Starting Work..."); $.ajax({ type: "POST", url: "@Url.Action("StartWorking", "CrazyServer")", data: { hubConnectionId: hubConnectionId }, success: function () { $("#btnStart").prop("disabled", true); DisplayMessage("Started Working"); }, error: function () { $("#btnStart").prop("disabled", false); alert("Oops, something went wrong.") } }); } function btnStart_Click() { var hubConId = $("#hubConnectionId").val(); Start(hubConId); } function btnClear_Click() { $("#tbodyContainer").empty(); } function DisplayMessage(message) { $("#tbodyContainer").append("<tr><td>" + message + "</td></tr>"); } </script> </body> </html>
Using
$.connection.myProxyHub
, we establish a reference to our hub,MyProxyHub
.Using
proxyHub
, we define what happens when the server makes use ofbroadcastMessage
orbroadcastDoneMessage
to send a message to thisHub
. We create an anonymous function which captures the message within its parameter. We can use this parameter variable to display or perform an action.In our
broadcastMessage
function, we are displaying the message received using our UDF,DisplayMessage()
. Whereas, in thebroadcastDoneMessage
, we enable the Start button and display, "Work Completed" message.The code,
$.connection.hub.start()
establishes a connection with the server and generates ahubConId
which we would pass to our action,StartWorking()
as a parameter. The function,done()
is executed once a connection is established with the server. In our case, we store theHub
Connection Id in a hidden field.Finally, our function,
Start()
makes an AJAX call to theStartWorking
action in ourCrazyServer
controller. When we click the Start Working button, theCrazyServer
starts visiting the Seven Wonders of the World.
Points of Interest
It took me a cup of Cappuccino while trying to make the CrazyServer
visit all the Seven Wonders of the world.
This happens to be my first try application written using the new Visual Studio 2015 on the all new Windows 10... And I must say, the experience was really good!
History
- 15th August, 2015: Initial version