Create a push notification system with SignalR



Introduction

In the previous article, we have seen Part 2 - CRUD operation on fullcalender with angularJS. Here In this article, I will show you, how to implement a push notification system with SignalR for notifying connected clients / users when any database changes happen on the server.

Today's most of the applications are multi-user application, where multiple users doing their task at the same time. But the problem is when any user(s) done any changes (ex. inserted a new order) other users doesn’t know it unless the request is initiated by the user for seeking the updates.

So, we must have a way to notify all the connected clients if there is any changes happens on the server without a refresh or update the web page. This is the area in which asp.net SignalR comes into play.

Just follow the following steps in order to implement "push notification system with SignalR in asp.net MVC".

Here In this article, I have used Visual Studio 2013

Step-1: Create New Project.

Go to File > New > Project > ASP.NET  Web Application (under web) > Entry Application Name > Click OK > Select Empty template > Checked MVC (under "Add folders and core references for" option) > OK

Step-2: Add a SQL Server Database.

I use SQL server management studio
so, I will  Open SQL Server management studio > connect to an instance of the SQL Server Database Engine > Right-click Databases, and then click New Database. > In New Database, enter a database name > OK.

Step-3: Create a table for store data.

Go to the Object Explorer (of SQL Management studio) > expand the database where you want to add the table > right-click the Tables node and then click New Table.
Create the table (see below image) and save.
Contacts Table

Step-4: Enable Service Broker on the database.

Run the following script on the database where you had created the table so that SQL server would start notifying the .NET application which subscribes to changes on the table

ALTER DATABASE [yourdatabasename] SET ENABLE_BROKER WITH ROLLBACK IMMEDIATE ;

Step-5: Add Entity Data Model.

Go to Solution Explorer (Visual studio) > Right Click on Project name form Solution Explorer > Add > New item > Select ADO.net Entity Data Model under data > Enter model name > Add.
A popup window will come (Entity Data Model Wizard) > Select Generate from database > Next >
Chose your data connection > select your database > next > Select tables > enter Model Namespace > Finish.

Step-6: Install SignalR NuGet Package.

Go to Solution Explorer > Right Click on References form Solution Explorer > Manage NuGetPackages... > Search for "SignalR"> Install > Close.

Or you can also install from package manager console.

Go to Tools (top menu) > Library Package  Manager > Open "Package Manager Console" > right below command for install SignalR Package.

PM> Install-Package Microsoft.AspNet.SignalR

Step-7: Add an Owin startup file. 

Add an Owin startup class in your application for enabling the SignalR in our application.
Go to Solution explorer > Right click on your project > Add > select OWIN Startup Class > Name the class Startup.cs > Add.

Write following code in Startup.cs file.

using System;
using System.Threading.Tasks;
using Microsoft.Owin;
using Owin;

[assembly: OwinStartup(typeof(PushNotification.Startup))]

namespace PushNotification
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.MapSignalR();
        }
    }
}

Step-8: Add a SignalR hub class. 

Now, we will create a SignalR Hub class, which makes possible to invoke the client side JavaScript method from the server side. Here in this application, we will use this for showing notification.
SignalR uses ‘Hub’ objects to communicate between the client and the server.

Go to the Solution Explorer > Right click on the project > Add > New item... > Select SignalR Hub Class > Name the class NotificationHub.cs > Add.

Enter the following contents into the file.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.AspNet.SignalR;
namespace PushNotification
{
    public class NotificationHub : Hub
    {
        //Nothing required here 
        //public void Hello()
        //{
        //    Clients.All.hello();
        //}
    }
}
you can see here, the NotificationHub.cs class is empty. Left the class empty as we will use the class later from another place.

Step-9: Add connection string into the web.config file. 

Open the application root Web.config file and find the element. Add the following connection string to the element in the Web.config file.

<add name="sqlConString" connectionString="data source=YourSqlDatasourceName;initial catalog=DatabaseName;;user id=YourSqlUserID;password=YourSqlPassword;"/>

Step-10: Add an another class file for register notification for data changes in the database.

In this class, I will create an SQL dependency which allows our application to be notified when a data has changed in the database (Microsoft SQL Server).  Write the following in this class...

  1. RegisterNotification (void method for register notification [Line 14 - Line 36])
  2. SqlDependency_OnChange (SqlDependency onchnage event, get fired when assigned SQL command produced a different result [Line 38 - Line 51]) 
  3. GetContacts (This is a method for return the changes happened on the server, here our new inserted contact data [Line 53 - Line 59])

NotificationComponent.cs file
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Web;
using System.Data.SqlClient;
using Microsoft.AspNet.SignalR;

namespace PushNotification
{
    public class NotificationComponent
    {
        //Here we will add a function for register notification (will add sql dependency)
        public void RegisterNotification(DateTime currentTime)
        {
            string conStr = ConfigurationManager.ConnectionStrings["sqlConString"].ConnectionString;
            string sqlCommand = @"SELECT [ContactID],[ContactName],[ContactNo] from [dbo].[Contacts] where [AddedOn] > @AddedOn";
            //you can notice here I have added table name like this [dbo].[Contacts] with [dbo], its mendatory when you use Sql Dependency
            using (SqlConnection con = new SqlConnection(conStr))
            {
                SqlCommand cmd = new SqlCommand(sqlCommand, con);
                cmd.Parameters.AddWithValue("@AddedOn", currentTime);
                if (con.State != System.Data.ConnectionState.Open)
                {
                    con.Open();
                }
                cmd.Notification = null;
                SqlDependency sqlDep = new SqlDependency(cmd);
                sqlDep.OnChange += sqlDep_OnChange;
                //we must have to execute the command here
                using (SqlDataReader reader = cmd.ExecuteReader())
                {
                    // nothing need to add here now
                }
            }
        }

        void sqlDep_OnChange(object sender, SqlNotificationEventArgs e)
        {
            //or you can also check => if (e.Info == SqlNotificationInfo.Insert) , if you want notification only for inserted record
            if (e.Type == SqlNotificationType.Change)
            {
                SqlDependency sqlDep = sender as SqlDependency;
                sqlDep.OnChange -= sqlDep_OnChange;

                //from here we will send notification message to client
                var notificationHub = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
                notificationHub.Clients.All.notify("added");
                //re-register notification
                RegisterNotification(DateTime.Now);
            }
        }

        public List<Contact> GetContacts(DateTime afterDate)
        {
            using (MyPushNotificationEntities dc = new MyPushNotificationEntities())
            {
                return dc.Contacts.Where(a => a.AddedOn > afterDate).OrderByDescending(a => a.AddedOn).ToList();
            }
        }
    }
}

Step-11: Create an MVC Controller.

Go to Solution Explorer > Right Click on Controllers folder form Solution Explorer > Add > Controller > Enter Controller name > Select Templete "empty MVC Controller"> Add.

Here I have created a controller named "HomeController"

Step-12: Add new action into your controller.

Here I have added "Index" Action into "Home" Controller. Please write this following code
public ActionResult Index()
{
    return View();
}

Step-13: Add view for your Action.

Right Click on Action Method (here right click on Index action) > Add View... > Enter View Name > Select View Engine (Razor) > Add.

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

Step-14: Add an another action in your controller (here HomeController) for fetch contact data.

public JsonResult GetNotificationContacts()
{
    var notificationRegisterTime = Session["LastUpdated"] != null ? Convert.ToDateTime(Session["LastUpdated"]) : DateTime.Now;
    NotificationComponent NC = new NotificationComponent();
    var list = NC.GetContacts(notificationRegisterTime);
    //update session here for get only new added contacts (notification)
    Session["LastUpdate"] = DateTime.Now;
    return new JsonResult { Data = list, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
}

Step-15: update _Layout.cshtml for showing notification.

Here I have added some HTML, CSS and JS code for showing notification. You can see yellow mark code here.
Line 16 - line 20 : added HTML code for showing notification icon top-right corner of the page.
Line 44 - line 50 : added jquery, SignalR and CSS library.
Line 52 - line 98 : added CSS code make looks perfect of the notification icon.
Line 100 - line 159 : added JS code for show/hide notifications, update notification count etc. and start notification hub.


<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title - My ASP.NET Application</title>
    <link href="~/Content/Site.css" rel="stylesheet" type="text/css" />
    <link href="~/Content/bootstrap.min.css" rel="stylesheet" />
    <script src="~/Scripts/modernizr-2.6.2.js"></script>
</head>
<body>
    <div class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">  
                
                    <span class="noti glyphicon glyphicon-bell"><span class="count">&nbsp;</span></span>
                    <div class="noti-content">
                        <div class="noti-top-arrow"></div>
                        <ul id="notiContent"></ul>
                    </div>
                
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                @Html.ActionLink("Application name", "Index", "Home", null, new { @class = "navbar-brand" })
            </div>
            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    
                </ul>
            </div>
        </div>
    </div>
    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>&copy; @DateTime.Now.Year - My ASP.NET Application</p>
        </footer>
    </div>
    
        @* Add Jquery Library *@
        <script src="~/Scripts/jquery-2.2.3.min.js"></script>
        <script src="~/Scripts/jquery.signalR-2.2.0.min.js"></script>
        <script src="/signalr/hubs"></script>
        <script src="~/Scripts/bootstrap.min.js"></script>
        @* Add css  *@
        <link href="~/Content/bootstrap.min.css" rel="stylesheet" />    
    
    <style type="text/css">
        /*Added css for design notification area, you can design by your self*/
        /* COPY css content from youtube video description*/
        .noti-content{
            position:fixed;
            right:100px;
            background:#e5e5e5;
            border-radius:4px;
            top:47px;
            width:250px;
            display:none;
            border: 1px solid #9E988B;
        }
        ul#notiContent{
            max-height:200px;
            overflow:auto;
            padding:0px;
            margin:0px;
            padding-left:20px;
        }
            ul#notiContent li {
                margin:3px;
                padding:6px;
                background:#fff;
            }
            .noti-top-arrow{
                border-color:transparent;
                border-bottom-color:#F5DEB3;
                border-style:dashed dashed solid;
                border-width: 0 8.5px 8.5px;
                position:absolute;
                right:32px;
                top:-8px;
            }
            span.noti{
                color:#FF2323;
                margin:15px;
                position:fixed;
                right:100px;
                font-size:18px;
                cursor:pointer;
            }
            span.count{
                position:relative;
                top:-3px;
            }
    </style>   
    
        @* Add jquery code for Get Notification & setup signalr *@
        <script type="text/javascript">
        $(function () {
            // Click on notification icon for show notification
            $('span.noti').click(function (e) {
                e.stopPropagation();
                $('.noti-content').show();
                var count = 0;
                count = parseInt($('span.count').html()) || 0;
                //only load notification if not already loaded
                if (count > 0) {
                    updateNotification();
                }
                $('span.count', this).html('&nbsp;');
            })
            // hide notifications
            $('html').click(function () {
                $('.noti-content').hide();
            })
            // update notification
            function updateNotification() {
                $('#notiContent').empty();
                $('#notiContent').append($('<li>Loading...</li>'));
                $.ajax({
                    type: 'GET',
                    url: '/home/GetNotificationContacts',
                    success: function (response) {
                        $('#notiContent').empty();
                        if (response.length  == 0) {
                            $('#notiContent').append($('<li>No data available</li>'));
                        }
                        $.each(response, function (index, value) {
                            $('#notiContent').append($('<li>New contact : ' + value.ContactName + ' (' + value.ContactNo + ') added</li>'));
                        });
                    },
                    error: function (error) {
                        console.log(error);
                    }
                })
            }
            // update notification count
            function updateNotificationCount() {
                var count = 0;
                count = parseInt($('span.count').html()) || 0;
                count++;
                $('span.count').html(count);
            }
            // signalr js code for start hub and send receive notification
            var notificationHub = $.connection.notificationHub;
            $.connection.hub.start().done(function () {
                console.log('Notification hub started');
            });
            //signalr method for push server message to client
            notificationHub.client.notify = function (message) {
                if (message && message.toLowerCase() == "added") {
                    updateNotificationCount();
                }
            }
        })
        </script>
    
</body>
</html>

Step-16: Update global.asax.cs for start, stop SQL dependency 

Here I have added code in global.asax.cs file for start and stop SQL dependency. You can see yellow mark code here.

Line  12 : Get connection string from web.config file.
Line 18 : Started SQL dependency inside Application_Start event.
Line 21 - line 27 : Register notification.
Line 30 - line 34 : Stoped SQL dependency inside Application_End event.

using System;
using System.Configuration;
using System.Data.SqlClient;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

namespace PushNotification
{
    public class MvcApplication : System.Web.HttpApplication
    {
        string con = ConfigurationManager.ConnectionStrings["sqlConString"].ConnectionString;
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            //here in Application Start we will start Sql Dependency
            SqlDependency.Start(con);
        }

        protected void Session_Start(object sender, EventArgs e)
        {
            NotificationComponent NC = new NotificationComponent();
            var currentTime = DateTime.Now;
            HttpContext.Current.Session["LastUpdated"] = currentTime;
            NC.RegisterNotification(currentTime);
        }


        protected void Application_End()
        {
            //here we will stop Sql Dependency
            SqlDependency.Stop(con);
        }
    }
}

Step-17: Run Application.

Hello ! My name is Sourav Mondal. I am a software developer working in Microsoft .NET technologies since 2010.

I like to share my working experience, research and knowledge through my site.

I love developing applications in Microsoft Technologies including Asp.Net webforms, mvc, winforms, c#.net, sql server, entity framework, Ajax, Jquery, web api, web service and more.