ASP.NET MVC Real-Time app with SignalR

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:

  1. First step we will create a sample app to perform CRUD operations
  2. 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

1

Step: 2

2

Step: 3

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">&nbsp;</div>
<div class="clearfix">&nbsp;</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">&nbsp;</div>

</div>


Insert

@model WebApplication1.Models.Customer
@{
    ViewBag.Title = "Insert";
}
<link href="~/Content/bootstrap/css/bootstrap.min.css" rel="stylesheet" />

<div class="clearfix">&nbsp;</div>
<div class="clearfix">&nbsp;</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">&nbsp;</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">&nbsp;</div>
<div class="clearfix">&nbsp;</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. 

5

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">&nbsp;</div>
<div class="clearfix">&nbsp;</div>
<div class="container">
    <div class="table-responsive">
        @Html.ActionLink("New Customer", "Insert", "Home")
        <hr />
        <div id="dataTable"></div>
    </div>
    <div class="clearfix">&nbsp;</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:

11

Hope this will help someone 🙂

Source Code: I’ve uploaded the full source code to download/clone @github, Hope this will help 🙂

Author:

Since March 2011, have 8+ years of professional experience on software development, currently working as Senior Software Engineer at s3 Innovate Pte Ltd.

8 thoughts on “ASP.NET MVC Real-Time app with SignalR”

Leave a Reply to Tai Nguyen Cancel reply

Your email address will not be published.