Transform An Existing MVC App To A Real-Time App Using SignalR

In this post, we will transform an existing MVC application to a real-time app using SignalR. We will use the latest SignalR library (2.4.1) along with Owin middleware for this conversion. We will create a SignalR hub to push a server-side message to all connected clients.

Introduction

SignalR is a library for ASP.NET developers to simplify the process of adding real-time web functionality to applications. Real-time web functionality is the ability to have server code push content to connected clients instantly as it becomes available, rather than having the server wait for a client to request new data.

Chat is often used as a SignalR example, we can do a whole lot more. Any time a user refreshes a web page to see new data, or the page implements long polling to retrieve new data, it is a candidate for using SignalR. Examples include dashboards and monitoring applications, collaborative applications (such as simultaneous editing of documents), job progress updates, and real-time forms. Stock market applications are also an example of SignalR.

SignalR includes API for connection management (for instance, connect and disconnect events), and grouping connections. SignalR also provides a simple API for creating server-to-client remote procedure calls (RPC) that call JavaScript functions in the client browsers (and other client platforms) from server-side .NET code.

You can get more details about SignalR from this Microsoft document.
 
We will create an MVC application using Entity Framework and SQL Server. Then, we will add SignalR NuGet packages to the application and create a SignalR hub to push the messages to connected clients. We will modify the Razor View files with some jQuery codes.
 

Create Database and Table in SQL Server

We will create an Employee application with all CRUD actions. We can create a new “Employees” table in the SQL database. If you don’t have an existing database, please create that also.

  1. CREATE TABLE [dbo].[Employees](  
  2.     [Id] [int] IDENTITY(1,1) NOT NULL,  
  3.     [Name] [nvarchar](50) NULL,  
  4.     [Company] [nvarchar](50) NULL,  
  5.     [Designation] [nvarchar](50) NULL,  
  6.  CONSTRAINT [PK_dbo.Employees] PRIMARY KEY CLUSTERED   
  7.     (  
  8.         [Id] ASC  
  9.     )  
  10. )  

Create MVC application in Visual Studio 2017

 
We can create a web application using MVC template.
 
Transform An Existing MVC App To A Real-Time App Using SignalR 

Since we are creating an Employee app, we can add an “Employee” class inside the “Models” folder.

Employee.cs
  1. namespace MVCRealtimeSignalR.Models  
  2. {  
  3.     public class Employee  
  4.     {  
  5.         public int Id { get; set; }  
  6.         public string Name { get; set; }  
  7.         public string Company { get; set; }  
  8.         public string Designation { get; set; }  
  9.     }  
  10. }  

We can build the application and add a new Employees controller class using scaffolding template. Right-click on Controllers folder and add new scaffolded item.

Transform An Existing MVC App To A Real-Time App Using SignalR
 
Choose "MVC 5 controller with views, using Entity Framework" option.
 
Transform An Existing MVC App To A Real-Time App Using SignalR
 
We can choose the Employee class as a model class and click the “+” button to add a new data context class. This data context class will be used by the Entity Framework to connect with SQL Server.
 
Transform An Existing MVC App To A Real-Time App Using SignalR

Click the “Add” button to create a new Employees controller.

You can notice that a new connection string is created in the Web.Config file. We can modify that connection string with our SQL Server name and database name.
 
Transform An Existing MVC App To A Real-Time App Using SignalR
 
Web.Config
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <!--  
  3.   For more information on how to configure your ASP.NET application, please visit  
  4.   https://go.microsoft.com/fwlink/?LinkId=301880  
  5.   -->  
  6. <configuration>  
  7.   <configSections>  
  8.     <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->  
  9.     <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />  
  10.   </configSections>  
  11.   <connectionStrings>  
  12.     <add name="SignalRDbContext" connectionString="Data Source=MURUGAN\SQL2017ML; Initial Catalog=SignalRDb; Integrated Security=True;           MultipleActiveResultSets=True;" providerName="System.Data.SqlClient" />  
  13.   </connectionStrings>  
  14.   <appSettings>  
  15.     <add key="webpages:Version" value="3.0.0.0" />  
  16.     <add key="webpages:Enabled" value="false" />  
  17.     <add key="ClientValidationEnabled" value="true" />  
  18.     <add key="UnobtrusiveJavaScriptEnabled" value="true" />  
  19.   </appSettings>  
  20.   <system.web>  
  21.     <compilation debug="true" targetFramework="4.5" />  
  22.     <httpRuntime targetFramework="4.5" />  
  23.   </system.web>  
  24.   <runtime>  
  25.     <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">  
  26.       <dependentAssembly>  
  27.         <assemblyIdentity name="Antlr3.Runtime" publicKeyToken="eb42632606e9261f" />  
  28.         <bindingRedirect oldVersion="0.0.0.0-3.5.0.2" newVersion="3.5.0.2" />  
  29.       </dependentAssembly>  
  30.       <dependentAssembly>  
  31.         <assemblyIdentity name="System.Diagnostics.DiagnosticSource" publicKeyToken="cc7b13ffcd2ddd51" />  
  32.         <bindingRedirect oldVersion="0.0.0.0-4.0.2.1" newVersion="4.0.2.1" />  
  33.       </dependentAssembly>  
  34.       <dependentAssembly>  
  35.         <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" />  
  36.         <bindingRedirect oldVersion="0.0.0.0-11.0.0.0" newVersion="11.0.0.0" />  
  37.       </dependentAssembly>  
  38.       <dependentAssembly>  
  39.         <assemblyIdentity name="System.Web.Optimization" publicKeyToken="31bf3856ad364e35" />  
  40.         <bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="1.1.0.0" />  
  41.       </dependentAssembly>  
  42.       <dependentAssembly>  
  43.         <assemblyIdentity name="WebGrease" publicKeyToken="31bf3856ad364e35" />  
  44.         <bindingRedirect oldVersion="0.0.0.0-1.6.5135.21930" newVersion="1.6.5135.21930" />  
  45.       </dependentAssembly>  
  46.       <dependentAssembly>  
  47.         <assemblyIdentity name="System.Web.Helpers" publicKeyToken="31bf3856ad364e35" />  
  48.         <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />  
  49.       </dependentAssembly>  
  50.       <dependentAssembly>  
  51.         <assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35" />  
  52.         <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />  
  53.       </dependentAssembly>  
  54.       <dependentAssembly>  
  55.         <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />  
  56.         <bindingRedirect oldVersion="1.0.0.0-5.2.4.0" newVersion="5.2.4.0" />  
  57.       </dependentAssembly>  
  58.     </assemblyBinding>  
  59.   </runtime>  
  60.   <system.webServer>  
  61.     <modules>  
  62.       <remove name="TelemetryCorrelationHttpModule" />  
  63.       <add name="TelemetryCorrelationHttpModule" type="Microsoft.AspNet.TelemetryCorrelation.TelemetryCorrelationHttpModule, Microsoft.AspNet.TelemetryCorrelation" preCondition="integratedMode,managedHandler" />  
  64.     </modules>  
  65.   </system.webServer>  
  66.   <system.codedom>  
  67.     <compilers>  
  68.       <compiler language="c#;cs;csharp" extension=".cs" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:6 /nowarn:1659;1699;1701" />  
  69.       <compiler language="vb;vbs;visualbasic;vbscript" extension=".vb" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.VBCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:14 /nowarn:41008 /define:_MYTYPE=\"Web\" /optionInfer+" />  
  70.     </compilers>  
  71.   </system.codedom>  
  72.   <entityFramework>  
  73.     <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">  
  74.       <parameters>  
  75.         <parameter value="mssqllocaldb" />  
  76.       </parameters>  
  77.     </defaultConnectionFactory>  
  78.     <providers>  
  79.       <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />  
  80.     </providers>  
  81.   </entityFramework>  
  82. </configuration>  

Now, modify the “_Layout.cshtml” partial view under “Shared” folder with the below changes.

Transform An Existing MVC App To A Real-Time App Using SignalR
 
_Layout.cshtml
  1. <!DOCTYPE html>  
  2. <html>  
  3. <head>  
  4.     <meta charset="utf-8" />  
  5.     <meta name="viewport" content="width=device-width, initial-scale=1.0">  
  6.     <title>@ViewBag.Title - MVC SignalR Application</title>  
  7.     @Styles.Render("~/Content/css")  
  8.     @Scripts.Render("~/bundles/modernizr")  
  9. </head>  
  10. <body>  
  11.      
  12.     <div class="navbar navbar-inverse navbar-fixed-top">  
  13.         <div class="container">  
  14.             <div class="navbar-header">  
  15.                 <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">  
  16.                     <span class="icon-bar"></span>  
  17.                     <span class="icon-bar"></span>  
  18.                     <span class="icon-bar"></span>  
  19.                 </button>  
  20.                 @Html.ActionLink("MVC SignalR App""Index""Home"new { area = "" }, new { @class = "navbar-brand" })  
  21.             </div>  
  22.             <div class="navbar-collapse collapse">  
  23.                 <ul class="nav navbar-nav">  
  24.                     <li>@Html.ActionLink("Home""Index""Home")</li>  
  25.                     <li>@Html.ActionLink("Employees""Index""Employees")</li>  
  26.                     <li>@Html.ActionLink("Contact""Contact""Home")</li>  
  27.                 </ul>  
  28.             </div>  
  29.         </div>  
  30.     </div>  
  31.     <div class="container body-content">  
  32.         @RenderBody()  
  33.         <hr />  
  34.         <footer>  
  35.             @{  
  36.                 var WebBrowserName = HttpContext.Current.Request.Browser.Browser;  
  37.                 <p>© @DateTime.Now.Year - MVC SignalR Application</p>  
  38.                 <p style="color:blue; background-color:yellow; font-size:18px;">(Browser :@WebBrowserName)</p>  
  39.             }  
  40.         </footer>  
  41.     </div>  
  42.   
  43.     @Scripts.Render("~/bundles/jquery")  
  44.     @Scripts.Render("~/bundles/bootstrap")  
  45.     @RenderSection("scripts", required: false)  
  46.     @RenderSection("JavaScript", required: false)  
  47. </body>  
  48. </html>  

We have added an action link to the Index action in Employees controller. We have also added a RenderSection for JavaScript with required as false.

We can run the application and check if our MVC app is working properly or not.
 
Transform An Existing MVC App To A Real-Time App Using SignalR 

You can click the “Employees” menu and add/edit/delete employee records.

Adding SignalR and Owin NuGet packages to the project.
 
We can add SignalR NuGet packages to the project.
 
Transform An Existing MVC App To A Real-Time App Using SignalR
 
Please note that other 6 packages will also be added with SignalR package.
 
Transform An Existing MVC App To A Real-Time App Using SignalR

Owin middleware is used for establishing a persistent connection between server and client.

Currently, there is a compatibility issue in jQuery version 3 with SignalR client. Hence, we can downgrade the jQuery version to 2.2.4. Otherwise, SignalR hub broadcasting will fail.
 
Transform An Existing MVC App To A Real-Time App Using SignalR

We can create a “Hubs” folder and create an “EmployeesHub” class inside this folder.

EmployeesHub.cs
  1. using Microsoft.AspNet.SignalR;  
  2. using Microsoft.AspNet.SignalR.Hubs;  
  3.   
  4. namespace MVCRealtimeSignalR.Hubs  
  5. {  
  6.     [HubName("employeesHub")]  
  7.     public class EmployeesHub : Hub  
  8.     {  
  9.         public static void BroadcastData()  
  10.         {  
  11.             IHubContext context = GlobalHost.ConnectionManager.GetHubContext<EmployeesHub>();  
  12.             context.Clients.All.refreshEmployeeData();  
  13.         }  
  14.     }  
  15. }  

We have created a Hub context and invoked client method “refreshEmployeeData” from it. Whenever the Hub broadcast is called, this client method in all connected clients is automatically invoked.

We can add a new action method “GetEmployeeData” in Employees controller. We will call “_EmployeeData” partial view from this method.
 
Transform An Existing MVC App To A Real-Time App Using SignalR
 
We can call “EmployeesHub.BroadcastData” method after db.SaveChanges in Create, Edit and Delete action methods. After saving the data, this broadcast method will be called, and the client-side method will be automatically invoked. Every data update on server-side will cause connected client's data to be refreshed accordingly.
 
Transform An Existing MVC App To A Real-Time App Using SignalR
 
EmployeesController.cs
  1. using MVCRealtimeSignalR.Hubs;  
  2. using MVCRealtimeSignalR.Models;  
  3. using System.Data.Entity;  
  4. using System.Linq;  
  5. using System.Net;  
  6. using System.Web.Mvc;  
  7.   
  8. namespace MVCRealtimeSignalR.Controllers  
  9. {  
  10.     public class EmployeesController : Controller  
  11.     {  
  12.         private SignalRDbContext db = new SignalRDbContext();  
  13.   
  14.         // GET: Employees  
  15.         public ActionResult Index()  
  16.         {  
  17.             return View();  
  18.         }  
  19.   
  20.         public ActionResult GetEmployeeData()  
  21.         {  
  22.             return PartialView("_EmployeeData", db.Employees.ToList());  
  23.         }  
  24.   
  25.         // GET: Employees/Details/5  
  26.         public ActionResult Details(int? id)  
  27.         {  
  28.             if (id == null)  
  29.             {  
  30.                 return new HttpStatusCodeResult(HttpStatusCode.BadRequest);  
  31.             }  
  32.             Employee employee = db.Employees.Find(id);  
  33.             if (employee == null)  
  34.             {  
  35.                 return HttpNotFound();  
  36.             }  
  37.             return View(employee);  
  38.         }  
  39.   
  40.         // GET: Employees/Create  
  41.         public ActionResult Create()  
  42.         {  
  43.             return View();  
  44.         }  
  45.   
  46.         // POST: Employees/Create  
  47.         // To protect from overposting attacks, please enable the specific properties you want to bind to, for   
  48.         // more details see https://go.microsoft.com/fwlink/?LinkId=317598.  
  49.         [HttpPost]  
  50.         [ValidateAntiForgeryToken]  
  51.         public ActionResult Create([Bind(Include = "Id,Name,Company,Designation")] Employee employee)  
  52.         {  
  53.             if (ModelState.IsValid)  
  54.             {  
  55.                 db.Employees.Add(employee);  
  56.                 db.SaveChanges();  
  57.                 EmployeesHub.BroadcastData();  
  58.                 return RedirectToAction("Index");  
  59.             }  
  60.   
  61.             return View(employee);  
  62.         }  
  63.   
  64.         // GET: Employees/Edit/5  
  65.         public ActionResult Edit(int? id)  
  66.         {  
  67.             if (id == null)  
  68.             {  
  69.                 return new HttpStatusCodeResult(HttpStatusCode.BadRequest);  
  70.             }  
  71.             Employee employee = db.Employees.Find(id);  
  72.             if (employee == null)  
  73.             {  
  74.                 return HttpNotFound();  
  75.             }  
  76.             return View(employee);  
  77.         }  
  78.   
  79.         // POST: Employees/Edit/5  
  80.         // To protect from overposting attacks, please enable the specific properties you want to bind to, for   
  81.         // more details see https://go.microsoft.com/fwlink/?LinkId=317598.  
  82.         [HttpPost]  
  83.         [ValidateAntiForgeryToken]  
  84.         public ActionResult Edit([Bind(Include = "Id,Name,Company,Designation")] Employee employee)  
  85.         {  
  86.             if (ModelState.IsValid)  
  87.             {  
  88.                 db.Entry(employee).State = EntityState.Modified;  
  89.                 db.SaveChanges();  
  90.                 EmployeesHub.BroadcastData();  
  91.                 return RedirectToAction("Index");  
  92.             }  
  93.             return View(employee);  
  94.         }  
  95.   
  96.         // GET: Employees/Delete/5  
  97.         public ActionResult Delete(int? id)  
  98.         {  
  99.             if (id == null)  
  100.             {  
  101.                 return new HttpStatusCodeResult(HttpStatusCode.BadRequest);  
  102.             }  
  103.             Employee employee = db.Employees.Find(id);  
  104.             if (employee == null)  
  105.             {  
  106.                 return HttpNotFound();  
  107.             }  
  108.             return View(employee);  
  109.         }  
  110.   
  111.         // POST: Employees/Delete/5  
  112.         [HttpPost, ActionName("Delete")]  
  113.         [ValidateAntiForgeryToken]  
  114.         public ActionResult DeleteConfirmed(int id)  
  115.         {  
  116.             Employee employee = db.Employees.Find(id);  
  117.             db.Employees.Remove(employee);  
  118.             db.SaveChanges();  
  119.             EmployeesHub.BroadcastData();  
  120.             return RedirectToAction("Index");  
  121.         }  
  122.   
  123.         protected override void Dispose(bool disposing)  
  124.         {  
  125.             if (disposing)  
  126.             {  
  127.                 db.Dispose();  
  128.             }  
  129.             base.Dispose(disposing);  
  130.         }  
  131.     }  
  132. }  

We can create “EmployeeData” partial view now.

_EmployeeData.cshtml (Partial View)
  1. @model IEnumerable<MVCRealtimeSignalR.Models.Employee>  
  2.   
  3. <h2>Index</h2>  
  4.   
  5. <p>  
  6.     @Html.ActionLink("Create New""Create")  
  7. </p>  
  8. <table class="table">  
  9.     <tr>  
  10.         <th>  
  11.             @Html.DisplayNameFor(model => model.Name)  
  12.         </th>  
  13.         <th>  
  14.             @Html.DisplayNameFor(model => model.Company)  
  15.         </th>  
  16.         <th>  
  17.             @Html.DisplayNameFor(model => model.Designation)  
  18.         </th>  
  19.         <th></th>  
  20.     </tr>  
  21.   
  22.     @foreach (var item in Model)  
  23.     {  
  24.         <tr>  
  25.             <td>  
  26.                 @Html.DisplayFor(modelItem => item.Name)  
  27.             </td>  
  28.             <td>  
  29.                 @Html.DisplayFor(modelItem => item.Company)  
  30.             </td>  
  31.             <td>  
  32.                 @Html.DisplayFor(modelItem => item.Designation)  
  33.             </td>  
  34.             <td>  
  35.                 @Html.ActionLink("Edit""Edit"new { id = item.Id }) |  
  36.                 @Html.ActionLink("Details""Details"new { id = item.Id }) |  
  37.                 @Html.ActionLink("Delete""Delete"new { id = item.Id })  
  38.             </td>  
  39.         </tr>  
  40.     }  
  41.   
  42. </table>  

Please note that we have just moved the entire code from the “Index” view to this partial view.

We can modify the “Index” view with the below code.
 
Index.cshtml
  1. @{  
  2.     ViewBag.Title = "Employee Details";  
  3. }  
  4.   
  5. <div id="dataModel"></div>  
  6.   
  7. @section JavaScript{  
  8.     <script src="~/Scripts/jquery.signalR-2.4.1.min.js"></script>  
  9.     <script src="/signalr/hubs"></script>  
  10.     <script type="text/javascript">  
  11.         $(function () {  
  12.             var hubNotify = $.connection.employeesHub;  
  13.   
  14.             $.connection.hub.start().done(function () {  
  15.                 getAll();  
  16.             });  
  17.   
  18.             hubNotify.client.refreshEmployeeData = function () {  
  19.                 getAll();  
  20.             };  
  21.         });  
  22.   
  23.         function getAll() {  
  24.             var model = $('#dataModel');  
  25.             $.ajax({  
  26.                 url: '/Employees/GetEmployeeData',  
  27.                 contentType: 'application/html ; charset:utf-8',  
  28.                 type: 'GET',  
  29.                 dataType: 'html',  
  30.                 success: function(result) { model.empty().append(result); }  
  31.             });  
  32.         }  
  33.     </script>  
  34. }  

Using SignalR jQuery library, we have created client-side hub connection in this file. Using this hub connection, we have invoked a function for getting data from Server. This client function will call the “GetEmployeeData” action method in Employees controller and will get the data as html. This html will be added to a div element. Whenever the broadcast event is invoked from server side, this client-side function is called, and new data is updated in the “Index” view.

We can add an Owin startup file and invoke “MapSignalR” method on project startup. This will establish a persistent connection between the server and connected clients.

Startup.cs
  1. using Microsoft.Owin;  
  2. using Owin;  
  3.   
  4. [assembly: OwinStartup(typeof(MVCRealtimeSignalR.Startup))]  
  5.   
  6. namespace MVCRealtimeSignalR  
  7. {  
  8.     public class Startup  
  9.     {  
  10.         public void Configuration(IAppBuilder app)  
  11.         {  
  12.             app.MapSignalR();  
  13.         }  
  14.     }  
  15. }  

We have completed all the coding part. We can run the application on Google Chrome and Firefox at same time. So, that we can see the data is automatically refreshed real-time in both browsers.

Transform An Existing MVC App To A Real-Time App Using SignalR

Conclusion

 
In this post, we have created an MVC application and then we have added SignalR packages to the project. Using Hub class, we have invoked client-side methods from server-side. We have run the application on Google Chrome and Firefox at the same time and have seen data refreshed real-time on both browsers.