Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Display Live Server Feedback to the Client

0.00/5 (No votes)
15 Aug 2015 1  
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

  1. 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".
  2. 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.
  3. 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".
  4. 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.
  5. 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
  6. 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.
  7. In the SignalR_LiveFeedback project, open the "Add New Item" dialog box and select, "OWIN Startup class". Name this class as "Startup.cs".
  8. 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

  1. 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.

  2. 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

  1. 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.

  2. 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.
  3. 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"));
    }
  4. 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();
      });
    }
    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 .
  5. 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

  1. In the SignalR_LiveFeedback Project, create a new controller, "CrazyServerController.cs".
  2. Create an empty Index() action if it hasn't already been created for you by Visual Studio.
    public ActionResult Index()
    {
      return View();
    }
  3. 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

  1. 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.
  2. 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 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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here