In topic we will focus on how to display real time updates from database with SignalR on existing ASP.NET MVC CRUD project.
The topic has two step:
- First step we will create a sample app to perform CRUD operations
- Second step we will make the app real-time with SignalR
Who are not familiar yet with SignalR Overview of SignalR from my previous post.
Step – 1
At first we need to create a database named CRUD_Sample. In sample db we have to create a table named Customers.
USE [CRUD_Sample] GO /****** Object: Table [dbo].[Customers] Script Date: 1/18/2016 11:42:45 AM ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO SET ANSI_PADDING ON GO CREATE TABLE [dbo].[Customers]( [Id] [bigint] IDENTITY(1,1) NOT NULL, [CustName] [varchar](100) NULL, [CustEmail] [varchar](150) NULL, CONSTRAINT [PK_Customers] PRIMARY KEY CLUSTERED ( [Id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO SET ANSI_PADDING OFF GO
Stored Procedures:
USE [CRUD_Sample] GO /****** Object: StoredProcedure [dbo].[Delete_Customer] Script Date: 1/18/2016 11:42:16 AM ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO -- ============================================= -- Author: <Author,,Name> -- Create date: <Create Date,,> -- Description: <Description,,> -- ============================================= CREATE PROCEDURE [dbo].[Delete_Customer] -- Add the parameters for the stored procedure here @Id Bigint AS BEGIN -- SET NOCOUNT ON added to prevent extra result sets from -- interfering with SELECT statements. SET NOCOUNT ON; -- Insert statements for procedure here DELETE FROM[dbo].[Customers] WHERE [Id] = @Id SELECT 1 END GO /****** Object: StoredProcedure [dbo].[Get_Customer] Script Date: 1/18/2016 11:42:16 AM ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO -- ============================================= -- Author: <Author,,Name> -- Create date: <Create Date,,> -- Description: <Description,,> -- ============================================= CREATE PROCEDURE [dbo].[Get_Customer] -- Add the parameters for the stored procedure here @Count BIGINT AS BEGIN -- SET NOCOUNT ON added to prevent extra result sets from -- interfering with SELECT statements. SET NOCOUNT ON; -- Insert statements for procedure here SELECT top(@Count)* FROM [dbo].[Customers] END GO /****** Object: StoredProcedure [dbo].[Get_CustomerbyID] Script Date: 1/18/2016 11:42:16 AM ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO -- ============================================= -- Author: <Author,,Name> -- Create date: <Create Date,,> -- Description: <Description,,> -- ============================================= CREATE PROCEDURE [dbo].[Get_CustomerbyID] -- Add the parameters for the stored procedure here @Id BIGINT AS BEGIN -- SET NOCOUNT ON added to prevent extra result sets from -- interfering with SELECT statements. SET NOCOUNT ON; -- Insert statements for procedure here SELECT * FROM [dbo].[Customers] WHERE Id=@Id END GO /****** Object: StoredProcedure [dbo].[Set_Customer] Script Date: 1/18/2016 11:42:16 AM ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO -- ============================================= -- Author: <Author,,Name> -- Create date: <Create Date,,> -- Description: <Description,,> -- ============================================= CREATE PROCEDURE [dbo].[Set_Customer] -- Add the parameters for the stored procedure here @CustName Nvarchar(100) ,@CustEmail Nvarchar(150) AS BEGIN -- SET NOCOUNT ON added to prevent extra result sets from -- interfering with SELECT statements. SET NOCOUNT ON; -- Insert statements for procedure here INSERT INTO [dbo].[Customers] ([CustName],[CustEmail]) VALUES(@CustName,@CustEmail) SELECT 1 END GO /****** Object: StoredProcedure [dbo].[Update_Customer] Script Date: 1/18/2016 11:42:16 AM ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO -- ============================================= -- Author: <Author,,Name> -- Create date: <Create Date,,> -- Description: <Description,,> -- ============================================= CREATE PROCEDURE [dbo].[Update_Customer] -- Add the parameters for the stored procedure here @Id Bigint ,@CustName Nvarchar(100) ,@CustEmail Nvarchar(150) AS BEGIN -- SET NOCOUNT ON added to prevent extra result sets from -- interfering with SELECT statements. SET NOCOUNT ON; -- Insert statements for procedure here UPDATE [dbo].[Customers] SET[CustName] = @CustName ,[CustEmail]= @CustEmail WHERE [Id] = @Id SELECT 1 END GO
Getting Started with MVC Project:
To create the sample applications, we need to have Visual Studio 2012 or later installed and be able to run the server on a platform that supports .NET 4.5.
Step: 1
Step: 2
Step: 3
Click ok and the visual studio will create and load a new ASP.NET application.
Use of Generic Repository:
With generic feature, we can reduce the amount of code we need for common scenarios.
namespace WebApplication1.Repository { interface IRepository<T> : IDisposable where T : class { IEnumerable<T> ExecuteQuery(string spQuery, object[] parameters); T ExecuteQuerySingle(string spQuery, object[] parameters); int ExecuteCommand(string spQuery, object[] parameters); } }
interface IRepository<T>
Show an interface of a generic repository of type T, which is a LINQ to SQL entity. It provides a basic interface with operations like Insert, Update, Delete, GetById and GetAll.
IDisposable
The IDisposable Interface, Provides a mechanism for releasing unmanaged resources.
where T : class
This is constraining the generic parameter to a class. Click for more
The type argument must be a reference type; this applies also to any class, interface, delegate, or array type.
namespace WebApplication1.Repository { public class GenericRepository<T> : IRepository<T> where T : class { Customer_Entities context = null; private DbSet<T> entities = null; public GenericRepository(Customer_Entities context) { this.context = context; entities = context.Set<T>(); } /// <summary> /// Get Data From Database /// <para>Use it when to retive data through a stored procedure</para> /// </summary> public IEnumerable<T> ExecuteQuery(string spQuery, object[] parameters) { using (context = new Customer_Entities()) { return context.Database.SqlQuery<T>(spQuery, parameters).ToList(); } } /// <summary> /// Get Single Data From Database /// <para>Use it when to retive single data through a stored procedure</para> /// </summary> public T ExecuteQuerySingle(string spQuery, object[] parameters) { using (context = new Customer_Entities()) { return context.Database.SqlQuery<T>(spQuery, parameters).FirstOrDefault(); } } /// <summary> /// Insert/Update/Delete Data To Database /// <para>Use it when to Insert/Update/Delete data through a stored procedure</para> /// </summary> public int ExecuteCommand(string spQuery, object[] parameters) { int result = 0; try { using (context = new Customer_Entities()) { result = context.Database.SqlQuery<int>(spQuery, parameters).FirstOrDefault(); } } catch { } return result; } private bool disposed = false; protected virtual void Dispose(bool disposing) { if (!this.disposed) { if (disposing) { context.Dispose(); } } this.disposed = true; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } } }
Use of Middle-tire:
namespace WebApplication1.Services { public partial class CustomerService { private GenericRepository<Customer> CustRepository; public CustomerService() { this.CustRepository = new GenericRepository<Customer>(new Customer_Entities()); } public IEnumerable<Customer> GetAll(object[] parameters) { string spQuery = "[Get_Customer] {0}"; return CustRepository.ExecuteQuery(spQuery, parameters); } public Customer GetbyID(object[] parameters) { string spQuery = "[Get_CustomerbyID] {0}"; return CustRepository.ExecuteQuerySingle(spQuery, parameters); } public int Insert(object[] parameters) { string spQuery = "[Set_Customer] {0}, {1}"; return CustRepository.ExecuteCommand(spQuery, parameters); } public int Update(object[] parameters) { string spQuery = "[Update_Customer] {0}, {1}, {2}"; return CustRepository.ExecuteCommand(spQuery, parameters); } public int Delete(object[] parameters) { string spQuery = "[Delete_Customer] {0}"; return CustRepository.ExecuteCommand(spQuery, parameters); } } }
Use of Generic Repository in MVC-Application:
public class HomeController : Controller { private CustomerService objCust; public HomeController() { this.objCust = new CustomerService(); } // GET: Home public ActionResult Index() { int Count = 10; IEnumerable<object> Result = null; try { object[] parameters = { Count }; Result = objCust.GetAll(parameters); } catch { } return View(Result); } public ActionResult Insert() { return View(); } [HttpPost] public ActionResult Insert(Customer model) { try { if (ModelState.IsValid) { object[] parameters = { model.CustName, model.CustEmail }; objCust.Insert(parameters); } } catch { } return View("Index"); } public ActionResult Delete(int id) { try { object[] parameters = { id }; objCust.Delete(parameters); } catch { } return View("Index"); } public ActionResult Update(int id) { object result = null; try { object[] parameters = { id }; result = this.objCust.GetbyID(parameters); } catch{} return View(result); } [HttpPost] public ActionResult Update(Customer model) { try { object[] parameters = { model.Id, model.CustName, model.CustEmail }; objCust.Update(parameters); } catch { } return View("Index"); } protected override void Dispose(bool disposing) { base.Dispose(disposing); } }
Use of views in MVC-Application:
Index: @model IList<WebApplication1.Models.Customer> @{ ViewBag.Title = "Index"; } <link href="~/Content/bootstrap/css/bootstrap.min.css" rel="stylesheet" /> <div class="clearfix"> </div> <div class="clearfix"> </div> <div class="container"> <div class="table-responsive"> @Html.ActionLink("New Customer", "Insert", "Home") <table class="table table-bordered table-striped"> <thead> <tr> <th>ID</th> <th>Name</th> <th>Email ID</th> <th>Delete</th> <th>Update</th> </tr> </thead> <tbody> @if (Model != null) { foreach (var item in Model) { <tr> <td>@item.Id</td> <td>@item.CustName</td> <td>@item.CustEmail</td> <td>@Html.ActionLink("Delete", "Delete", "Home", new { id = @item.Id }, null)</td> <td>@Html.ActionLink("Update", "Update", "Home", new { id = @item.Id }, null)</td> </tr> } } </tbody> </table> </div> <div class="clearfix"> </div> </div> Insert @model WebApplication1.Models.Customer @{ ViewBag.Title = "Insert"; } <link href="~/Content/bootstrap/css/bootstrap.min.css" rel="stylesheet" /> <div class="clearfix"> </div> <div class="clearfix"> </div> <div class="container"> <div class="table-responsive col-md-6 col-md-offset-3"> <table class="table table-bordered table-striped"> <tbody> @using (Html.BeginForm("Insert", "Home", FormMethod.Post)) { @*<tr> <td class="col-md-4">ID</td> <td class="col-md-8">@Html.TextBoxFor(m => m.Id)</td> </tr>*@ <tr> <td class="col-md-4">Name</td> <td class="col-md-8">@Html.TextBoxFor(m => m.CustName)</td> </tr> <tr> <td class="col-md-4">Email ID</td> <td class="col-md-8">@Html.TextBoxFor(m => m.CustEmail)</td> </tr> <tr> <td class="text-right" colspan="2"><input type="submit" value="Save" class="btn btn-primary" /></td> </tr> } </tbody> </table> </div> <div class="clearfix"> </div> @Html.ActionLink("Home", "Index", "Home") </div> Update @model WebApplication1.Models.Customer @{ ViewBag.Title = "Update"; } <link href="~/Content/bootstrap/css/bootstrap.min.css" rel="stylesheet" /> <div class="clearfix"> </div> <div class="clearfix"> </div> <div class="container"> <div class="table-responsive"> <table class="table table-bordered table-striped"> <thead> <tr> <th>Name</th> <th>Email ID</th> <th>Update</th> </tr> </thead> <tbody> <tr> @using (Html.BeginForm("Update", "Home", FormMethod.Post)) { <td>@Html.TextBoxFor(m => m.CustName)</td> <td>@Html.TextBoxFor(m => m.CustEmail)</td> <td><input type="submit" value="Update" class="btn btn-primary" /></td> } </tr> </tbody> </table> </div> </div>
Step – 2
Getting Started with SignalR:
The first thing is getting reference from NuGet.
Get it on NuGet!
Install-Package Microsoft.AspNet.SignalR
Register SignalR middleware:
Once you have it installed. Let’s create OwinStartup Class.
The following code adds a simple piece of middleware to the OWIN pipeline, implemented as a function that receives a Microsoft.Owin.IOwinContext instance.
When the server receives an HTTP request, the OWIN pipeline invokes the middleware. The middleware sets the content type for the response and writes the response body.
Startup.cs
using System; using System.Threading.Tasks; using Microsoft.Owin; using Owin; [assembly: OwinStartup(typeof(WebAppSignalR.Startup))] namespace WebAppSignalR { public class Startup { public void Configuration(IAppBuilder app) { app.MapSignalR(); } } }
Create & Use Hub classes:
After finishing previous process, let’s create a Hub. A SignalR Hub make remote procedure calls (RPCs) from a server to connected clients and from clients to the server.
CustomerHub.cs
using System; using System.Collections.Generic; using System.Linq; using System.Web; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; namespace WebApplication1.Hubs { public class CustomerHub : Hub { [HubMethodName("broadcastData")] public static void BroadcastData() { IHubContext context = GlobalHost.ConnectionManager.GetHubContext<CustomerHub>(); context.Clients.All.updatedData(); } } }
Code Explanation:
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<CustomerHub>();
It gets the CustomerHub context
context.Clients.All.updatedData();
It call the client part of SignalR and tell it to execute the JavaScript method updatedData().
Let’s Modify our Existing View:
Now we will modify part of Index view as following, and we will display data with a partial view.
Index:
@model IList<WebApplication1.Models.Customer> @{ ViewBag.Title = "Index"; } <link href="~/Content/bootstrap/css/bootstrap.min.css" rel="stylesheet" /> <div class="clearfix"> </div> <div class="clearfix"> </div> <div class="container"> <div class="table-responsive"> @Html.ActionLink("New Customer", "Insert", "Home") <hr /> <div id="dataTable"></div> </div> <div class="clearfix"> </div> </div> @section JavaScript{ <script src="~/Scripts/jquery.signalR-2.2.0.min.js"></script> <script src="/signalr/hubs"></script> <script type="text/javascript"> $(function () { // Reference the hub. var hubNotif = $.connection.customerHub; // Start the connection. $.connection.hub.start().done(function () { getAll(); }); // Notify while anyChanges. hubNotif.client.updatedData = function () { getAll(); }; }); function getAll() { var model = $('#dataTable'); $.ajax({ url: '/home/GetAllData', contentType: 'application/html ; charset:utf-8', type: 'GET', dataType: 'html' }).success(function (result) { model.empty().append(result); }).error(function (e) { alert(e); }); } </script> }
Partial View:
<table class="table table-bordered table-striped"> <thead> <tr> <th>ID</th> <th>Name</th> <th>Email ID</th> <th>Delete</th> <th>Update</th> </tr> </thead> <tbody> @if (Model != null) { foreach (var item in Model) { <tr> <td>@item.Id</td> <td>@item.CustName</td> <td>@item.CustEmail</td> <td>@Html.ActionLink("Delete", "Delete", "Home", new { id = @item.Id }, null)</td> <td>@Html.ActionLink("Update", "Update", "Home", new { id = @item.Id }, null)</td> </tr> } } </tbody> </table>
Let’s Modify our Existing Controller:
Home Controller:
In our home controller we will add a method named GetAllData(). Here the following method.
[HttpGet] public ActionResult GetAllData() { int Count = 10; IEnumerable<object> Result = null; try { object[] parameters = { Count }; Result = objCust.GetAll(parameters); } catch { } return PartialView("_DataList", Result); }
Here we are returning a partial view with returned data list, and just returning empty.
// GET: Home public ActionResult Index() { return View(); }
Finally Home Controller:
public class HomeController : Controller { private CustomerService objCust; //CustomerRepository CustRepository; public HomeController() { this.objCust = new CustomerService(); } // GET: Home public ActionResult Index() { return View(); } [HttpGet] public ActionResult GetAllData() { int Count = 10; IEnumerable<object> Result = null; try { object[] parameters = { Count }; Result = objCust.GetAll(parameters); } catch { } return PartialView("_DataList", Result); } public ActionResult Insert() { return View(); } [HttpPost] public ActionResult Insert(Customer model) { int result = 0; try { if (ModelState.IsValid) { object[] parameters = { model.CustName, model.CustEmail }; result = objCust.Insert(parameters); if (result == 1) { //Notify to all CustomerHub.BroadcastData(); } } } catch { } return View("Index"); } public ActionResult Delete(int id) { int result = 0; try { object[] parameters = { id }; result = objCust.Delete(parameters); if (result == 1) { //Notify to all CustomerHub.BroadcastData(); } } catch { } return View("Index"); } public ActionResult Update(int id) { object result = null; try { object[] parameters = { id }; result = this.objCust.GetbyID(parameters); } catch { } return View(result); } [HttpPost] public ActionResult Update(Customer model) { int result = 0; try { object[] parameters = { model.Id, model.CustName, model.CustEmail }; result = objCust.Update(parameters); if (result == 1) { //Notify to all CustomerHub.BroadcastData(); } } catch { } return View("Index"); } protected override void Dispose(bool disposing) { base.Dispose(disposing); } }
Finally Output:
Hope this will help someone 🙂
Source Code: I’ve uploaded the full source code to download/clone @github, Hope this will help 🙂
kshitij says:
Nice Artical. Keep posting..
Shashangka Shekhar says:
Thanks
durgesh says:
unable to download source code. please provide download link
Shashangka Shekhar says:
Please get it from here: https://github.com/ShashangkaShekhar/ASP.NET-MVC5-SignalR
Vern says:
Great Article. Sir can you do it with angularjs?
Shashangka Shekhar says:
Thanks, Yap sure (y)
Mateusz Diiiii says:
Dear Shashangka,
I’ve downloaded your project, opened it in VS2015 and I’ve meet a problem. There is no errors, I have a possibility to add new customer, but the list of customers don’t display on the index. Customers are added to the SQL database, but there is no possibility to see the list on the index site. Can you help me?
Tai Nguyen says:
Hi Shashangka ,
Great Article. But I can’t change index site when update from sql server. Pls help. thanks you!