How to make your own real-time Like Button using ASP.NET MVC, jQuery, and SignalR

SignalR is one of my favorite libraries because of it's ability to use the web in real-time. In this post, we see how to create a real-time like button using SignalR.

Written by Jonathan "JD" Danylko • Last Updated: • MVC •
Two Way Communication

SignalR is one of those libraries that I first experienced back in 2012 at my second Codemash. Scott Hanselmann gave a brief overview of the library in one of his sessions and I looked over to one of my friends and said, "Man, SignalR sounds like a game-changer...I wish there was a session on THAT."

I checked out the schedule and BAM...there was a session about SignalR the next day.

Needless to say, I was extremely excited to start digging into the library and learn how to apply SignalR to existing projects.

Since then, SignalR has definitely made it's way into the Microsoft world. It's even used in the Web Essentials extension for updating a web page through Visual Studio when you make a change to CSS or HTML. You immediately see it update in the browser.

In this post, we'll walk-through a simple example of building a "Did you like this post?" widget using SignalR. 

What is SignalR?

SignalR is a server-side library to add real-time functionality to your existing web applications. This also includes the ability to push notifications out to the client as they happen.

SignalR takes advantage of multiple protocols when determining how to send and receive data. First, it determines if it can use WebSockets, an HTML5 protocol. If that doesn't work then it gracefully falls back to older technology to make sure it works.

For more detailed information about how SignalR determines which technology to use, head to the Introduction to SignalR page at Microsoft.

Where did you use SignalR?

Notice at the top and bottom of this post? I added a "widget" this weekend.

Real time Like Button

If you click on the thumbs up, you see the number increment. Click it again and it "unlikes" the post.

The awesome part of this is that if someone was on another machine and reading the post at the same time and pushed the like button, you would see the number increment/decrement in real-time...without refreshing the page.

Let's go through how I did it.

Setup

First, we need the package. In your NuGet Package Manager Console, type:

install-package Microsoft.AspNet.SignalR

This should give you all you need for a functional ASP.NET MVC SignalR website.

Now the fun...

Hubs

For us to get our real-time on, we need to implement a server-side class called a Hub. There are two types of Hubs: general Hubs and Persistent Hubs. The General Hubs overlay the Persistent Hub lower layers providing a better API.

This Hub class is what receives the requests and sends the broadcasts out to the clients.

Through these hubs, we can determine if a client is connected, disconnected, or waiting to be reconnected.

Here is a sample template of a Hub.

Hubs\MyHub1.cs

using System.Threading.Tasks;
using Microsoft.AspNet.SignalR;
namespace DanylkoWeb.Hubs
{
    public class MyHub1 : Hub
    {
        public void Hello()
        {
            Clients.All.hello();
        }
        public override Task OnConnected()
        {
            return base.OnConnected();
        }
        public override Task OnDisconnected(bool stopCalled)
        {
            return base.OnDisconnected(stopCalled);
        }
        public override Task OnReconnected()
        {
            return base.OnReconnected();
        }
    }
}

I added the three methods at the end as a way to show how easy it is to check the status of whether they are online or offline.

Let's focus on our component. Here is what we have for our Hub.

Hubs\PostHub.cs

using System;
using System.Linq;
using System.Threading.Tasks;
using BlogPlugin.EntityExtensions;
using BlogPlugin.GeneratedClasses;
using CaramelCMS.Core.Extensions;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
namespace DanylkoWeb.Hubs
{
    [HubName("postHub")]
    public class PostHub : Hub
    {
        public Task Like(string postHashId)
        {
            var likePost = SaveLike(postHashId);
            return Clients.All.updateLikeCount(likePost);
        }
        private LikePost SaveLike(string hashId)
        {
            var baseContext = this.Context.Request.GetHttpContext();
            var unitOfWork = baseContext.GetUnitOfWork<BlogUnitOfWork>();
            var item = unitOfWork.PostRepository.GetByHashId(hashId);
            var liked = new PostLike
            {
                Id = Guid.NewGuid().ToString(),
                IpAddress = baseContext.Request.UserHostAddress,
                PostId = item.Id,
                UserAgent = baseContext.Request.UserAgent,
                UserLike = true,
                DateLiked = DateTime.UtcNow
            };
            var dupe = item.PostLikes.FirstOrDefault(e => e.IpAddress == liked.IpAddress);
            if (dupe == null)
            {
                item.PostLikes.Add(liked);
            }
            else
            {
                dupe.UserLike = !dupe.UserLike;
            }
            unitOfWork.PostLikeRepository.SaveChanges();
            var post = unitOfWork.PostRepository.GetByHashId(hashId);
            return post.ToLikePost();
        }
 
    }
}

A couple of notes about this PostHub class:

  • I know it's optional, but I always add the HubName Attribute to the Hub Class. It leaves no doubt of what the Hub name is called in the JavaScript.
  • Of course, I'm using Entity Framework, so I have a Unit of Work for my blog with a table called PostLikes.
  • I check to see if there is an existing "like" based on an IPAddress. If it doesn't exist, I add one. If there is a record, I flip the flag called UserLike and save it.
  • I reload the post object and convert it to a simple POCO. Simple objects work a whole lot better than Entity Framework entities. The simpler the object, the easier it is to turn it into JSON, so you'll need a simple object to return back to your JavaScript.

Also, the primary reason I reload the post again is because we may have an additional "like" since this process was executed.

UPDATE: I forgot to mention that the GetUnitOfWork extension method wasn't added. Here is the code that assists with getting a BlogUnitOfWork (this can be your own Unit of Work). All this does is create an instance of a BlogUnitOfWork so we can work with it.

Along with this extension method, you need a dependency injection library. Here, we are using Ninject.

To install Ninject, use NuGet to grab the latest version.

install-package Ninject

install-package Ninject.Extensions.Conventions

Once these are installed, you should be ready to go.

public static T GetUnitOfWork<T>(this HttpContextBase contextBase) where T : AbstractUnitOfWork
{
    var objectContextKey = String.Format("{0}-{1}"typeof(T),
        contextBase.GetHashCode().ToString("x"));
    if (contextBase.Items.Contains(objectContextKey))         return contextBase.Items[objectContextKey] as T;
    T type;     using (IKernel kernel = new StandardKernel())     {         type = kernel.Get<T>();     }     contextBase.Items.Add(objectContextKey, type);
    return contextBase.Items[objectContextKey] as T; }

Now let's move on to the client-side of the website.

Prepping the HTML

Next, we need to create our HTML control. This is a simple step. I made a simple HTML button with some fancy FontAwesome graphics.

<button data-id="@Model.Post.Hash" type="button" class="like-button" title="Click to like this post!">
    <i class="fa fa-thumbs-up"></i> Like
    |
    <span class="like-count">@Model.PostLikes</span>
</button>

A couple notes with the HTML:

  • The line after the "Like" label is just a pipe.
  • Pay particular attention to the button data-id. This was a way to identify the blog post we want to increase with a like.
  • Also notice the "like-count" class for the number of total likes. We'll be using that later in our jQuery.

Now we need to add our scripts to our HTML file. You need three files to make this work. Actually four with your JavaScript file.

  1. You need jQuery first (Currently using jQuery 2.1.1 as of this writing).
  2. Then, the SignalR JavaScript file (Currently using jquery.signalR-2.1.2.js)
  3. Next, and this is important, add a script called
    /signalr/hubs
  4. Finally, add YOUR JavaScript file to the project (which will be discussed in the next section below).

Your Scripts will look like below and they HAVE to be in this order.

<script src="/Scripts/jquery-2.1.1.min.js"></script>
<script src="/Scripts/jquery.signalR-2.1.2.min.js"></script>
<script src="/signalr/hubs"></script>
<script src="/Scripts/yourJavaScriptFile.js"></script>

The reason you need the /signalr/hubs file is because it creates a dynamic JavaScript proxy for your C# Hub class.

Don't believe me? If everything is installed properly, you should be able to type in localhost:xxxx/signalr/hubs and you should see a dynamic JavaScript file that is generated based on your Hub.

As you know, you absolutely need this to work to get real-time updates sent to the server and client.

So always test to see if everything is working using the /signalr/hubs in your address bar.

Onward to the jQuery.

SignalR with jQuery

For the last part, you need to hook everything up with your JavaScript/jQuery file.

Now, there is a certain way to layout your jQuery.

Scripts\rater.js

$(function () {
    var postClient = $.connection.postHub;
    postClient.client.updateLikeCount = function (post) {
        var counter = $(".like-count");
        $(counter).fadeOut(function () {
            $(this).text(post.LikeCount);
            $(this).fadeIn();
        });
    };
    $(".like-button").on("click"function () {
        var code = $(this).attr("data-id");
        postClient.server.like(code);
    });
 
    $.connection.hub.start();
    
});

A couple of notes regarding the jQuery file:

  • If you noticed the JavaScript proxy file (from /signalr/hubs), you'll notice your method names are all camelCase.
  • The connection hub name ($.connection.postHub) should be the same name as the HubName in your C# Hub class (again, having that HubNameAttribute on your Hub class helps).
  • Anytime you need a call from the server to the client, prefix everything with <hubVarName>.client.<serverToClientMethodName>. In this case, I'm using postClient.client.updateLikeCount and passing in our JSON POCO from the server (remember the post.ToLikePost()? )
  • Anytime you need an event to send something to the server, prefix everything with <hubVarName>.server.<clientToServerMethodName>. In this case, I have one public method in my hub class called Like. Also notice that it's lowercase in the JavaScript!
  • Finally, at the end of your script, you need a way to kick off your hub from your script. Add $.connection.hub.start() to the end.

Conclusion

In this post, I described how I took a simple concept and widget and made it a real-time component using SignalR. If it means anything, it took longer to write this post than it did to implement this widget on my site.

As I mentioned above, when I first saw this implemented, I was blown away. We now have the ability to make web applications real-time. In 2002, it was AJAX. Ten years later, .NET developers have SignalR. What a time, what a time!

I wanted to make something simple with SignalR to show that ANYONE using .NET has the ability to update their web applications in real-time...really easy.

If you want to experience more SignalR, search Twitter with #signalr and you'll find people posting projects using SignalR.

Do you have any questions regarding the project? Did everything make sense? Post your comments below.

Did you like this content? Show your support by buying me a coffee.

Buy me a coffee  Buy me a coffee
Picture of Jonathan "JD" Danylko

Jonathan Danylko is a web architect and entrepreneur who's been programming for over 25 years. He's developed websites for small, medium, and Fortune 500 companies since 1996.

He currently works at Insight Enterprises as an Principal Software Engineer Architect.

When asked what he likes to do in his spare time, he replies, "I like to write and I like to code. I also like to write about code."

comments powered by Disqus