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 the SignalR
package in your BLL project as well.
In the BLL project, we do not need SignalR
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 your Startup
class, add the following code in the Configuration()
method.
app.MapSignalR();
Setup SignalR Hubs
- Create a new class in your BLL project, name it "MyHub.cs" and write two methods,
SendMessage()
and SendDoneMessage()
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 the SignalR_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
. This Id
is generated by SignalR
to uniquely identify each client which is connected to the Server. We can use this ConnectionId
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 to true
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()
and SetHubConnectionId()
. In our DoSomething()
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 two private
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()
{
Task.Factory.StartNew(() =>
{
MyHub myHub = new MyHub(_HubConnectionId);
int prevWonderIndex = -1;
for (int i = 0; i < (new Random()).Next(7, 15); i++)
{
int selectedWonderIndex = (new Random()).Next(0, _WondersList.Count);
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));
}
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.");
}
myHub.SendDoneMessage();
});
}
The DoSomething()
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 inform SignalR
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 click Index()
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>
<!---->
<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>
<!---->
<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 src="~/Scripts/jquery-1.10.2.min.js"></script>
<script src="~/Scripts/jquery.signalR-2.2.0.min.js"></script>
<!---->
<script src="~/signalr/hubs"></script>
<script type="text/javascript">
$(function () {
var proxyHub = $.connection.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...");
$.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 of broadcastMessage
or broadcastDoneMessage
to send a message to this Hub
. 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 the broadcastDoneMessage
, we enable the Start button and display, "Work Completed" message.
The code, $.connection.hub.start()
establishes a connection with the server and generates a hubConId
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 the Hub
Connection Id in a hidden field.
Finally, our function, Start()
makes an AJAX call to the StartWorking
action in our CrazyServer
controller. When we click the Start Working button, the CrazyServer
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