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".
Step-1: Create New Project.
Step-2: Add a SQL Server Database.
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 tableALTER DATABASE [yourdatabasename] SET ENABLE_BROKER WITH ROLLBACK IMMEDIATE ;
Step-5: Add Entity Data Model.
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.
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.
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(); //} } }
Step-9: Add connection string into 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.
- RegisterNotification (void method for register notification [Line 14 - Line 36])
- SqlDependency_OnChange (SqlDependency onchnage event, get fired when assigned SQL command produced a different result [Line 38 - Line 51])
- GetContacts (This is a method for return the changes happened on the server, here our new inserted contact data [Line 53 - Line 59])
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.
Here I have created a controller named "HomeController"
Step-12: Add new action into your controller.
public ActionResult Index() { return View(); }
Step-13: Add view for your Action.
@{ 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.
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"> </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>© @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(' '); }) // 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
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); } } }