User Specific Notifications Using ASP.NET MVC & SignalR

In this post we are going to explore how to implement user based notification using ASP.Net MVC & SignalR.

In case if you are new to SignalR the please get some basics here: https://docs.microsoft.com/en-us/aspnet/signalr/overview/getting-started/introduction-to-signalr

Why SignalR?

SignalR provides “real-time” web functionality in our application using Javascript function call in client browser from server (Server Sent Events). It has several connection management like

  • connect/disconnect/reconnect events,
  • grouping connections,
  • authorization etc

Go to http://signalr.net for more.

We will focus on:

  1. Creating a new ASP.Net MVC Web Application
  2. Add SignalR
  3. Creating Hub
  4. Enable SignalR
  5. Database Modification
  6. Send specific user Notification using SignalR

Create ASP.Net MVC Web Application:

Open Visual Studio goto > File >New Project Choose ASP.Net MVC application

Choose a template in my case I have used MVC. Check Web API reference then hit ok button, that’s it. Build & run the application for first time.

Add SignalR:

Get it on NuGet! Right click the project > Manage NuGet package > Browse to install

Browse package then install to application, it’ll automatically done the rest.

OR Install using package manager console

Install-Package Microsoft.AspNet.SignalR

Creating Hub:

Add New Hub Class, Name it NotificationHub.cs

public class NotificationHub : Hub
{
    private static readonly ConcurrentDictionary<string, UserHubModels> Users =
        new ConcurrentDictionary<string, UserHubModels>(StringComparer.InvariantCultureIgnoreCase);


    public override Task OnConnected()
    {
        string userName = Context.User.Identity.Name;
        string connectionId = Context.ConnectionId;

        var user = Users.GetOrAdd(userName, _ => new UserHubModels
        {
            UserName = userName,
            ConnectionIds = new HashSet<string>()
        });

        lock (user.ConnectionIds)
        {
            user.ConnectionIds.Add(connectionId);
            if (user.ConnectionIds.Count == 1)
            {
                Clients.Others.userConnected(userName);
            }
        }

        return base.OnConnected();
    }

    public override Task OnDisconnected(bool stopCalled)
    {
        string userName = Context.User.Identity.Name;
        string connectionId = Context.ConnectionId;

        UserHubModels user;
        Users.TryGetValue(userName, out user);

        if (user != null)
        {
            lock (user.ConnectionIds)
            {
                user.ConnectionIds.RemoveWhere(cid => cid.Equals(connectionId));
                if (!user.ConnectionIds.Any())
                {
                    UserHubModels removedUser;
                    Users.TryRemove(userName, out removedUser);
                    Clients.Others.userDisconnected(userName);
                }
            }
        }

        return base.OnDisconnected(stopCalled);
    }
}

Enable SignalR:

Startup.cs

using Microsoft.Owin;
using Owin;

[assembly: OwinStartupAttribute(typeof(NotifSystem.Web.Startup))]
namespace NotifSystem.Web
{
    public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.MapSignalR();
        }
    }
}

Layout page

<script src="~/Scripts/jquery.signalR-2.2.2.min.js"></script>
<script src="~/signalr/hubs"></script>
<script type="text/javascript">
    $(document).ready(function () {
        var hub = $.connection.notificationHub;
        $.connection.hub.start()
            .done(function () {
                console.log("Hub Connected!");

            })
            .fail(function () {
                console.log("Could not Connect!");
            });
    });
</script>

Test SignalR

Run the application go to url modify by /signalr/hubs. This will show the magic SignalR JavaScript Library with current version like below screenshot.

In Browser Console it’ll show message about Hub Connection.

 

Database Modification:

Create new database then modify connection string in web.config file. Restore database from attached script file in App_Folder. We need to create a table to store Notifications.

CREATE TABLE [dbo].[Notification](
	[Id] [int] IDENTITY(1,1) NOT NULL,
	[Type] [int] NULL,
	[Details] [nvarchar](500) NULL,
	[Title] [nvarchar](50) NULL,
	[DetailsURL] [nvarchar](500) NULL,
	[SentTo] [nvarchar](50) NULL,
	[Date] [date] NULL,
	[IsRead] [bit] NULL,
	[IsDeleted] [bit] NULL,
	[IsReminder] [bit] NULL,
	[Code] [nvarchar](100) NULL,
	[NotificationType] [nvarchar](100) NULL,
 CONSTRAINT [PK_Notification] 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

Send specific user Notification using SignalR:

In this section we will work with view to send notification to specific user.

Index.cshtml:

<div class="col-md-12">
    <h2>Say Hello</h2>
    <div id="messages"></div>
    <hr />

    <div class="form-group">
        <label for="email">User Email:</label>
        <input type="email" class="form-control" id="toUser" value="" placeholder="shashangka@gmail.com"/>
    </div>
    <div class="form-group">
        <label for="pwd">Message:</label>
        <input type="password" class="form-control" id="myMessage" value="" placeholder="message"/>
    </div>

    <button type="submit" class="btn btn-default" id="submit">Submit</button>

</div>

Javascript:

<script type="text/javascript">
$("#submit").click(function (e) {
    e.preventDefault();

    var message = $("#myMessage").val();
    var sendtouser = $("#toUser").val();

    var Notification = { UserID: sendtouser, Message: message };

    $.ajax({
        type: "POST",
        url: "/api/Values/SendNotification",
        data: JSON.stringify(Notification),
        contentType: 'application/json; charset=utf-8',
        success: function (data) {
            //reset field
            $("#myMessage").val("");
        },
        error: function () {
            alert("Error occured!!")
        }
    });

});
</script>

API:

public class ValuesController : ApiController
{
    private NotifEntities context = new NotifEntities();

    [HttpPost]
    public HttpResponseMessage SendNotification(NotifModels obj)
    {
        NotificationHub objNotifHub = new NotificationHub();
        Notification objNotif = new Notification();
        objNotif.SentTo = obj.UserID;

        context.Configuration.ProxyCreationEnabled = false;
        context.Notifications.Add(objNotif);
        context.SaveChanges();

        objNotifHub.SendNotification(objNotif.SentTo);

        return Request.CreateResponse(HttpStatusCode.OK);
    }
}

Modify Hub: We need to add additional methods in our Hub class.

private NotifEntities context = new NotifEntities();

//Logged Use Call
public void GetNotification()
{
    try
    {
        string loggedUser = Context.User.Identity.Name;

        //Get TotalNotification
        string totalNotif = LoadNotifData(loggedUser);

        //Send To
        UserHubModels receiver;
        if (Users.TryGetValue(loggedUser, out receiver))
        {
            var cid = receiver.ConnectionIds.FirstOrDefault();
            var context = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
            context.Clients.Client(cid).broadcaastNotif(totalNotif);
        }
    }
    catch (Exception ex)
    {
        ex.ToString();
    }
}

//Specific User Call
public void SendNotification(string SentTo)
{
    try
    {
        //Get TotalNotification
        string totalNotif = LoadNotifData(SentTo);

        //Send To
        UserHubModels receiver;
        if (Users.TryGetValue(SentTo, out receiver))
        {
            var cid = receiver.ConnectionIds.FirstOrDefault();
            var context = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
            context.Clients.Client(cid).broadcaastNotif(totalNotif);
        }
    }
    catch (Exception ex)
    {
        ex.ToString();
    }
}

private string LoadNotifData(string userId)
{
    int total = 0;
    var query = (from t in context.Notifications
                    where t.SentTo == userId
                    select t)
                .ToList();
    total = query.Count;
    return total.ToString();
}

Finally the Hub:

public class NotificationHub : Hub
{
    private static readonly ConcurrentDictionary<string, UserHubModels> Users =
        new ConcurrentDictionary<string, UserHubModels>(StringComparer.InvariantCultureIgnoreCase);

    private NotifEntities context = new NotifEntities();

    //Logged Use Call
    public void GetNotification()
    {
        try
        {
            string loggedUser = Context.User.Identity.Name;

            //Get TotalNotification
            string totalNotif = LoadNotifData(loggedUser);

            //Send To
            UserHubModels receiver;
            if (Users.TryGetValue(loggedUser, out receiver))
            {
                var cid = receiver.ConnectionIds.FirstOrDefault();
                var context = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
                context.Clients.Client(cid).broadcaastNotif(totalNotif);
            }
        }
        catch (Exception ex)
        {
            ex.ToString();
        }
    }

    //Specific User Call
    public void SendNotification(string SentTo)
    {
        try
        {
            //Get TotalNotification
            string totalNotif = LoadNotifData(SentTo);

            //Send To
            UserHubModels receiver;
            if (Users.TryGetValue(SentTo, out receiver))
            {
                var cid = receiver.ConnectionIds.FirstOrDefault();
                var context = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
                context.Clients.Client(cid).broadcaastNotif(totalNotif);
            }
        }
        catch (Exception ex)
        {
            ex.ToString();
        }
    }

    private string LoadNotifData(string userId)
    {
        int total = 0;
        var query = (from t in context.Notifications
                        where t.SentTo == userId
                        select t)
                    .ToList();
        total = query.Count;
        return total.ToString();
    }

    public override Task OnConnected()
    {
        string userName = Context.User.Identity.Name;
        string connectionId = Context.ConnectionId;

        var user = Users.GetOrAdd(userName, _ => new UserHubModels
        {
            UserName = userName,
            ConnectionIds = new HashSet<string>()
        });

        lock (user.ConnectionIds)
        {
            user.ConnectionIds.Add(connectionId);
            if (user.ConnectionIds.Count == 1)
            {
                Clients.Others.userConnected(userName);
            }
        }

        return base.OnConnected();
    }

    public override Task OnDisconnected(bool stopCalled)
    {
        string userName = Context.User.Identity.Name;
        string connectionId = Context.ConnectionId;

        UserHubModels user;
        Users.TryGetValue(userName, out user);

        if (user != null)
        {
            lock (user.ConnectionIds)
            {
                user.ConnectionIds.RemoveWhere(cid => cid.Equals(connectionId));
                if (!user.ConnectionIds.Any())
                {
                    UserHubModels removedUser;
                    Users.TryRemove(userName, out removedUser);
                    Clients.Others.userDisconnected(userName);
                }
            }
        }

        return base.OnDisconnected(stopCalled);
    }
}

Broadcast to Client

//Hub Method to Broadcast Notification to Specific User
context.Clients.Client(cid).broadcaastNotif(totalNotif);

//Server Sent Event
hub.client.broadcaastNotif = function (totalNotif) {
    setTotalNotification(totalNotif)
};

Call to Server

//Client Call to Server
hub.server.getNotification();

//Hub Method
public void GetNotification()
{

}

Finally Client Script:

<script type="text/javascript">
    $(document).ready(function () {

        function setTotalNotification(totalNotif) {
            console.log(totalNotif);
            $("#messages").html("<p> Total Notif:" + totalNotif + "</p>");
        }

        var hub = $.connection.notificationHub;

        //Server Sent Event
        hub.client.broadcaastNotif = function (totalNotif) {
            setTotalNotification(totalNotif)
        };

            
        //$.connection.hub.start().done(function () { });

        $.connection.hub.start()
            .done(function () {
                console.log("Hub Connected!");

                //Client Call to Server
                hub.server.getNotification();

            })
            .fail(function () {
                console.log("Could not Connect!");
            });
    });
</script>

Output:

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.

15 thoughts on “User Specific Notifications Using ASP.NET MVC & SignalR”

Leave a Reply

Your email address will not be published.