ASP.NET MVC 3 Real Time Collaborative Apps with SignalR

Posted by: Sumit Maitra , on 12/22/2011, in Category ASP.NET MVC
Views: 172131
Abstract: In this article, we’ll create a simple blog app using ASP.NET MVC 3 and SignalR, that will allow multiple collaborators to review the same article in real-time
In this article, we’ll create a simple blog app using ASP.NET MVC 3 and SignalR, that will allow multiple collaborators to review the same article in real-time.

What is SignalR?

By the book – It is an asynchronous signaling framework that helps maintain persistent connections between client and server.

Not by the book – A real cool framework built on top of ASP.NET (server side) and a JavaScript library (on the client side) that helps build collaborative web apps in a jiffy. David Fowler and Damien Edwards maintain the SignalR project on GitHub

SignalR abstracts the raw technique of keeping connections open between a web client and a web server. Behind the scenes it uses an existing technique called long polling. However as the WebSockets protocol crystallizes, it could very well use WebSockets transparently, behind the scenes.

SignalR uses the task parallel library on the server side. It scales well as of now. However, work is in progress to make it better.

 

Why do I need SignalR?

Most common examples where long polling is used are stock tickers or chat clients. However as we will see today SignalR throws open a whole new set of possibilities, enabling rich collaborative applications on ASP.NET.

Solving a business problem with SignalR

Enough theory, let’s get down to the real world. I am sure most of us have at some point used Google docs and recently if you have noticed, you can share it with multiple people and now you can even have two people edit it at the same time. Each person gets a different colored caret to track who is doing what.

Wouldn’t it be cool if you could embed such ’Review’ functionality to your app? Imagine filling out a form and then sending it out for review only to find out someone down the chain rejected it after a week because of a typo? Now imagine what if you could do instant reviews and everyone could give their inputs and sign off on the document at the
same time?

In this article we’ll create a simple blog app that will allow multiple collaborators to review an article at real-time.

Step 1: Create new ASP.NET project.

In Visual Studio From File > New Project, select the MVC 3 Web Application template

asp-mvc-project

Select ‘Intranet Application’. This results in creation of a project that works by default with Windows Authentication mode.

We use the Razor View engine and keep the Use HTML5 semantic markup checked.

razor-view

If you already have Nuget Package Manager installed, skip to Step 2. Else go to Tools > Extension Manager, and from the ‘Online Gallery’ install ‘NuGet Package Manager’.
nuget-manager

It will require a Visual Studio restart. Restart once installed.

Step 2: Setup SignalR dependencies using Nuget

Bring up the Package Manager Console from the View->Other Windows->Package Manager Console menu item.
In the console type Install-package SignalR

Wait for the packages to be downloaded and setup. The jQuery dependencies will be refreshed too.

package-mgr-console
 

Step 3: Setup Data Model

We will use EF Code First to setup our model. The entity is called BlogPost and it has three properties for simplicity, Id, Title and BlogBody.
 
data-model

Note: Since the focus of the article is SignalR I am cutting some ‘pattern corners’ by putting Display attributes in my Entity Model and skipping the View model abstraction.

Create a BlogPostContext class that derives from DbContext. Build the Solution.

Step 4: Scaffold the Controllers and Views

We will use the built in scaffold tooling to generate the controller and views.

add-controller

Select the Template, Model class and Data context class in the ‘Add Controller’ dialog

controller-dialog

Step 5: Add the ‘Review’ Controller and View

By default, the scaffolding generates Index, Create Edit and Delete views each mapping to their respective actions. We will leave these as is and add a new Action called ‘Review’. To do this we’ll simply copy the Edit Action and rename it.

In the BlogPostController, add the Review action methods as follows:

review-action-method

Under Views\BlogPost add the Review.cshtml

review-cshtml

In the Index.cshtml, add a new link to the ‘Review’ view.

review-view-link

Step 6: Getting SignalR into action

On the Server Side
In your web project create a folder called SignalR (You can call it anything you want, in a real life project this as server side component and could very well reside in a dll of it’s own). In the SignalR folder add a class BlogHub.cs. Update the class to look as follows:

blog-hub-class

The class is decorated with the ‘HubName’ attribute that takes a string parameter. The string parameter is the ‘client side’ name of the ‘Hub’. The Hub as the name suggests keeps track of the ‘subscribers’ and ‘publishes’ or broadcasts the messages that are passed to it by the subscribers.

In the above snippet, the Send method is called from the web page via JavaScript. SignalR framework takes care of the appropriate hookups.

Each ‘client’ that is web page, interested in receiving events from the hub subscribes to it and then implements the addMessage function. So when the Hub fires back, all clients handle the event.

On the Client Side

Open Review.cshtml and add the following references

review-references

The jQuery and signalR dependencies will be available for you from the SignalR nuget package installation, however json2.min.js is not. You have to download it separately from https://github.com/douglascrockford/JSON-js. This library is written by Douglas Crockford and is a public domain license. The minified version is available at http://code.google.com/p/webscan/source/browse/trunk/ui/js/third-party/json2.min.js?r=80 (or in the code package of this article).

The ‘/signalr/hubs’ reference is showing the green squiggly for a reason. It doesn’t exist as design time. It will be generated at run-time.

Bringing Diff-Match-Patch into the party

We will use Neil Fraiser’s excellent implementation from http://code.google.com/p/google-diff-match-patch/ There are multiple implementations available and you could use it on the server side (C# library) or client side (javascript library). I decided to let the client do all the hard work, so I used the javascript library. Add the script reference to Review.cshtml

mvc-review-reference

Add a placeholder to Review.cshtml that will hold the session id. This session Id is used to identify which client initiated the request.

session-id

Hooking up the hub and the publish handlers

Add the following script block to the Review.cshtml
publish-handlers

The code comments are pretty self-explanatory and the sequence of events is as follows:
  1. Two clients come to review the same BlogPost
  2. One makes changes to the BlogPost’s Article (aka BlogBody) that is ‘Send’ to the hub with an identifier as to who initiated the change (Session Id).
  3. The hub simply lobs it back to all the clients by calling the ‘addMessage’ method.
  4. Now both the clients receive the addMessage ‘signal’. Only the one that has a session Id that doesn’t match goes ahead and does a diff-match-patch.

Other Miscellaneous changes

Update _Layout.cshtml ‘s jquery script reference to the version you have.
Make things pretty by changing the default title of the app from ‘My MVC Application’ in the _Layout.cshtml to something nicer.
Change the Index.cshtml and update the header from ‘BlogBody’ to ‘Article’
Change the Global.asax ‘s RegisterRoutes method to navigate to the BlogPost’s Index directly

register-routes

Step 7: Let it Roll

Run the application and create a new Post. Save it. Your Index page should look something like the following

mvc-signalr-demo

Click on Review to go the review page. Press Ctrl+N to fire up a new browser instance with same page. Hit F5 to update session Id in the new Browser instance.

Line up the browsers side by side. Type in one and watch the other one change.

mvc-signalr-sync

Conclusion

SignalR is a powerful and easy to use framework that enables permanent connections and Asynchronous signaling in ASP.NET. Its potential uses include but not limited to
  • Collaborative editing tools like the one we saw here.
  • Highly dynamic apps where certain changes can be pushed in near real time for example, a Cart can signal a purchase completion enabling dynamic updates of ‘stock numbers’ for end users
  • News tickers with real-time feeds and many many more.
Long-polling is not a new thing, however now we have a fantastic abstraction to use in ASP.NET.

So what will You do with SignalR?

Full Disclosure: I have reproduced parts of this article from an article on the same topic in my personal blog.

The entire source code of this article can be downloaded over here
Give a +1 to this article if you think it was well written. Thanks!
Recommended Articles
Sumit is a .NET consultant and has been working on Microsoft Technologies since his college days. He edits, he codes and he manages content when at work. C# is his first love, but he is often seen flirting with Java and Objective C. You can follow him on twitter at @sumitkm or email him at sumitkm [at] gmail


Page copy protected against web site content infringement by Copyscape


User Feedback
Comment posted by Jags on Tuesday, January 3, 2012 12:08 AM
A very nicely explained article! I liked the demo app you choose for explaining the power of SignalR. Before reading this article I was hoping the example not to be based on a chat app :)
SignalR is certainly going to be a nice addition to our web development library and in coming days, a must have.
Comment posted by Sumit on Wednesday, January 4, 2012 12:51 AM
Thanks Jags, glad you liked the use case. I try to keep use cases as close to real life as possible :).

Do share your SignalR adventures with us if you get a chance!
Comment posted by Jeff on Wednesday, January 4, 2012 3:20 PM
Very good. I noticed one bug that I had to change to get this to work. In the call to blog.send (Review.cshtml) I had to remove the last, boolean parameter. The Send method didn't have this.
Comment posted by Sumit on Wednesday, January 4, 2012 5:24 PM
@Jeff: Thanks for pointing out the discrepancy. My bad! We'll update the screenshot.
Comment posted by Sumit on Wednesday, January 4, 2012 10:49 PM
UPDATE: Thanks to @Jeff we have updated the screenshot to remove the last boolean parameter.
Comment posted by Jon Davis on Thursday, January 5, 2012 4:19 PM
Followed this article to detail. Ending up with 500 error on keyup, "'Send' could not be resolved." @ SignalR.Hubs.HubDispatcher.OnReceivedAsync(String clientId, String data) +693
Comment posted by Jon Davis on Thursday, January 5, 2012 4:19 PM
.. hrm, I had this web page up for 24 hours, I see new comments, the problem may be addressed. Thanks.
Comment posted by Sumit on Saturday, January 7, 2012 3:05 AM
Hello Jon,
Apologies for the delay in response. Can you send your source-code to my email address. I'll take a look. Sounds like an issue hooking up the Hub to the client.
Thanks and Regards,
Sumit.
Comment posted by jose pardo on Friday, January 13, 2012 4:08 AM
Hello
Thank you for this clear article. Since i've been evaluating this library , I have a question. Does SignalR works in a web farm environment?
Comment posted by victorjhon on Friday, January 13, 2012 4:55 AM
Nice post, Author has a very sharp mind.

<a href="http://www.testkiller.me/"><b>TestKiller<
Comment posted by victorjhon on Friday, January 13, 2012 5:01 AM
Nice post, Author has a very sharp mind.

<a href="http://www.testkiller.me/"><b>TestKiller<
Comment posted by Nyron on Friday, January 13, 2012 5:25 PM
Excellent article, very well explain. Is there any security concerns with placing the users session ID in a hidden field?
Comment posted by Sumit on Saturday, January 14, 2012 2:45 PM
@Jose Pardo : Jose I am not sure about the status of the SignalR.ScaleOut branch. But that was the namespace being developed by the authors of SIgnalR for the web farm configuration.

@Nyron: Instead of a hidden field you can use the ViewBag or use the SignalR provided clientId propoerty as defined here:  http://stackoverflow.com/questions/7872589/call-specific-client-from-signalr
Comment posted by johnny on Monday, January 16, 2012 8:58 AM
Definitely pretty cool! But I have problems with SignalR messing up my conventions, my Stylecop doesn't allow lowercase methods in C#, while uppercase doesn't fit js conventions. Also, I don't like the fact we're dealing with dynamic objects on members like 'Clients' where there's absolutely no need for it. I'd rather have my own interfaces so my server is still typesafe.
Comment posted by Rob Lynch on Tuesday, January 17, 2012 11:32 AM
Very intesting.. I did notice when playing with this that the connection seemed to be open for all of the windows i could open. However. When i added a "new" post, and edited it, all the open windows that were pointing @ post 1 were getting post 2's new text. How would you suggest making this specific to a particular post and not do a broardcast to all the open browsers no matter which post they had opened.


Comment posted by shakti singh on Wednesday, January 18, 2012 1:12 AM
very nice..
Comment posted by shakti singh on Wednesday, January 18, 2012 1:15 AM
very nice..
Comment posted by Sumit on Thursday, January 19, 2012 3:43 AM
@Rob Lynch,

Hi Rob, you can simply pass the Blog Id along with the session id in the Send method, then lob it back in the addMEssage method and compare it in Javascript like I am doing for session id.

It's a nice little bug you found there. I'll try and update the code and screenshots for the same.

Thanks and Regards,
Sumit.
Comment posted by Zaia Yokhanna on Thursday, February 2, 2012 1:06 PM
I noticed that you are using intranet template,is it applicable to internet?
Comment posted by Zaia Yokhanna on Thursday, February 2, 2012 2:22 PM
I noticed that you are using intranet template,is it applicable to internet?
Comment posted by Zaia Yokhanna on Friday, February 3, 2012 12:59 PM
I tried it in my project MVC4 using internet with plan textarea, it works fine.  However, if you collaborating via rtf textarea such as simple tinymce it stops working.
Comment posted by Sumit on Sunday, February 5, 2012 5:41 AM
@Zaia,
Any RTF editor has markup information embedded in them. So you may have to use the appropriate property to retrieve the entire text including the markup and then do the diff. Same with the merge.
If you can send me your code sample I will try to take a look.
Thanks and Regards,
Sumit.
Comment posted by Zaia Yokhanna on Thursday, March 8, 2012 7:10 PM
Sumit, how would you prefer to receive the code sample?
Comment posted by Sumit on Sunday, March 11, 2012 10:51 PM
@Zaia, my email address is sumitkm [at] gmail [dot] com. You can send non-binaries there, or you could use a skydrive/dropbox public location and mail me the URL.
Comment posted by Salinas on Monday, June 11, 2012 8:11 PM
Hi Zaia, I'm building an MVC4 application, does your example work with MVC 4?
Comment posted by vali on Thursday, July 5, 2012 4:55 AM
Very good presentation. Built it and ran it just from article without using the attached source code. Well done!
Comment posted by mike wiegand on Friday, July 20, 2012 8:11 AM
Downloaded the source, F5, and it fires up, but ya, 2 side by side browsers don't reflect changes. Nothing is referencing the BlogHub class, so how would this ever work?
Comment posted by Sumit on Sunday, July 22, 2012 3:53 PM
@mike wiegand
Mike, I am assuming you are looking at the 'Review' page. Do both browsers display different session variables? If not, hit, CTRL+F5 on one browser to get it a different session id.
BlogHub co-ordinates calls between the client and the server. The Send method in BlogHub are called from JavaScript in the Review page.
Comment posted by Mark on Tuesday, July 31, 2012 10:35 AM
Hi Sumit, thanks very much for this detailed tutorial. It was exactly what I needed to get into SignalR.
Best wishes, Mark
Comment posted by mike on Tuesday, October 23, 2012 2:36 PM
most bullshit of examples i have ever seen, download it make it work .. i dont know how others say good things about it..
Fuck you Sumit
Comment posted by Sumit on Saturday, October 27, 2012 1:56 PM
@mike
I don't see how your suggestion makes the article and better or the discussion any constructive.
Comment posted by Anton on Wednesday, October 31, 2012 9:56 AM
Hi Sumit,
Thanks for this wonderful example, however..when I try to run the project which i have created following your tutorial exactly as it is...i get an error on the Index action saying ..."Invalid value for key 'attachdbfilename' "
Please help, as I would love to get it working.
Whe I try to run your example in VS 2010, it says error and your project is incompatible with VS 2010.
Thanks
Comment posted by Sumit on Monday, November 5, 2012 7:27 AM
Hello Anton,
Apologies for the delay in response.

Looks like there is an issue with the connection to the default database. To get this going you need to do the following:

1. Determine the correct SQL Server (SQL Express/of LocalDB) name in your environment.
2. Add a connection string to the web.config
3. Use the connection name in BlogPostContext.

Example: Let's assume your SQL Server is at YOURMACHINE\SQLEXPRESS

In such a case your connection string in the web.config will be as follows

  <add name="FunWithSignalRDB"
         connectionString="data source=YOURMACHINE\SQLEXPRESS;Integrated Security=true;Initial Catalog=FunWithSignalRDB"
         providerName="System.Data.SqlClient" />

Now in the BlogContext.cs add a constructor as follows

public BlogPostContext()
            : base("FunWithSignalRDB")
        {

        }

Where FunWithSignalRDB is the name of the connection string in web.config.

Apologies to everyone who couldn't get it going, I assumed a successful default installation of SQL Express. If the you have a default SQL EXpress, this code should work straight off the bat.

Anton, can you confirm your version of Visual Studio? I'll check specific errors for the same.

Comment posted by Jennifer on Wednesday, December 12, 2012 9:36 AM
This command:
Install-package SignalR

Returns this error:
Install-Package : Unable to find package 'SignalR'.
At line:1 char:16
+ install-package <<<<  SignalR
    + CategoryInfo          : NotSpecified: (:) [Install-Package], InvalidOperationException
    + FullyQualifiedErrorId : NuGetCmdletUnhandledException,NuGet.PowerShell.Commands.InstallPackageCommand

I have also tried
Install-Package Microsoft.AspNet.SignalR

What is the correct name for the Nuget package?
Comment posted by Jennifer on Wednesday, December 12, 2012 9:47 AM
This command:
Install-package SignalR

Returns this error:
Install-Package : Unable to find package 'SignalR'.
At line:1 char:16
+ install-package <<<<  SignalR
    + CategoryInfo          : NotSpecified: (:) [Install-Package], InvalidOperationException
    + FullyQualifiedErrorId : NuGetCmdletUnhandledException,NuGet.PowerShell.Commands.InstallPackageCommand

I have also tried
Install-Package Microsoft.AspNet.SignalR

What is the correct name for the Nuget package?
Comment posted by MikeW on Tuesday, December 18, 2012 8:19 AM
I found this in another article and I have tested it. It appears to be the correct package name.

install-package Microsoft.AspNet.signalr -pre
Comment posted by Stefan Plattner on Thursday, January 17, 2013 6:03 AM
In the server-side code: Clients.addMessage should be Clients.All.addMessage in the latest SignalR release (V1 RC2)
Comment posted by Sumit on Thursday, February 21, 2013 11:59 AM
SignalR is now Microsoft.AspNet.SignalR . You can install it using

install-package Microsoft.AspNet.SignalR

No more -pre required.

install-package SignalR will no longer work.
Comment posted by Bhupendra on Wednesday, April 3, 2013 1:43 AM
Would you help please this is the best article i have seen on SignalR, concern is its not working in IE (I am using IE 8)
Comment posted by Unfortun8one on Sunday, October 13, 2013 10:15 AM
Thanks for this example!  I've made it work with MVC4 and it will be a great model for future SignalR projects.

Post your comment
Name:  
E-mail: (Will not be displayed)
Comment:
Insert Cancel