May 21st, 2013

DataTable using SignalR+AngularJS+EntityFramework

SignalR brought developers an easier way to build real-time, very responsive web applications. But, how does it play with other available technologies? I took a couple of days to implement a very common scenario needed in every Enterprise Application: A DataTable to do CRUD operations persisting changes on a database.

My initial thought was this is going to be a trivial task, I have done DataTables a few times in the past. Then, I realized the real-time model with many concurrent users introduces a few challenges.

Before going through details, here is a screenshot of what I accomplished: A DataTable where multiple users are collaborating on the add/edit/delete of its contents! When user 1 edits David Fowler, the Delete and Edit buttons disappear for David in the browsers of users 2, 3, and 4. When user 2 edits Gustavo Armenta, the Delete and Edit buttons disappear for Gustavo in the browsers of users 1, 3, and 4. When user 1 finishes editing David Fowler, Delete and Edit buttons reappear on all browsers.

multiple_edits

 

HTML Content needs to update every time I receive a SignalR message

My SignalR client receives a JSON message indicating there is a new friend. Now I need to update my html table with the new content. AngularJS plays beautiful in this scenario as I only have to add a row in my existing array (array.push(item)) and refresh content (scope.apply()).

// SignalR Events
hubProxy.client.add = function (friend) {
  console.log("client.add(" + JSON.stringify(friend) + ")");
  $scope.friends.push(friend);
  $scope.$apply();
}

 

Broadcast SignalR messages when user modifies data

The previous code works well when the server pushes data to client. Now, let’s handle the case when the user is pushing changes.

// AngularJS Events
$scope.add = function () {
  hubProxy.server.add({ Name: $scope.add_friend.Name });
  $scope.add_friend.Name = "";
}

    Concurrent users competing to edit the same row

    What to do when two or more users want to edit the same row? You could allow them to edit simultaneously and the last one to save changes win. You could track versions and let the first user save changes and fail for other users as they need to refresh to the latest version before submitting changes. You could lock the row and allow only a single user to make changes, once he finishes other users can take the lock.

    In this sample, I have followed the lock approach with a few more considerations.

    1. User can only lock one row at a time
    2. Release lock in row when user completes the update or user disconnects temporarily or permanently
    3. Handle race condition when multiple users try to edit same row

    Most of the magic happen on the SignalR Hub. OnConnected() returns both a list of items and a list of locks. OnReconnected() simply refreshes the state as if connecting for the first time. OnDisconnected releases the lock I had taken so other active users can edit the row.

    private static ConcurrentDictionary<string, int> _locks = 
    new ConcurrentDictionary<string, int>(); public override async Task OnConnected() { var query = from f in _db.Friends orderby f.Name select f; await Clients.Caller.all(query); await Clients.Caller.allLocks(_locks.Values); } public override async Task OnReconnected() { // Refresh as other users could update data while we were offline await OnConnected(); } public override async Task OnDisconnected() { int removed; _locks.TryRemove(Context.ConnectionId, out removed); await Clients.All.allLocks(_locks.Values); } public void TakeLock(Friend value) { _locks.AddOrUpdate(Context.ConnectionId, value.Id, (key, oldValue) => value.Id); Clients.All.allLocks(_locks.Values); } public void Update(Friend value) { var updated = _db.Friends.First<Friend>(f => f.Id == value.Id); updated.Name = value.Name; _db.SaveChanges(); Clients.All.update(updated); int removed; _locks.TryRemove(Context.ConnectionId, out removed); Clients.All.allLocks(_locks.Values); }

      Then, AngularJS provides a very clear mapping between HTML content and conditional logic using ”ng-repeat” and “ng-show”. Look how easy is to make UI decisions based on the current state of JSON objects bound to $scope.

      <tr ng-repeat="friend in friends | orderBy:predicate">
          <td><button name="deleteButton" ng-click="delete(friend)" 
      ng-show="!friend.IsLocked">Delete</button></td> <td> <button name="editButton" ng-click="edit(friend)"
      ng-show="!friend.IsLocked">Edit</button> <button name="updateButton" ng-click="update(friend)"
      ng-show="isEdit(friend)">Update</button> </td> <td>{{friend.Id}}</td> <td ng-show="!isEdit(friend)">{{friend.Name}}</td> <td ng-show="isEdit(friend)"><input type="text" ng-model="edit_friend.Name" /></td> </tr>

       

      Things to improve in this sample

      1. Improve UI Look & Feel
      2. Play well with other datatypes like number, currency, date
      3. Display validation and error messages
      4. Support SignalR scaleout

      Source Code

      https://github.com/gustavo-armenta/SignalR-JS-HTML

      Feedback

      Have you seen other HTML/JS/SignalR controls out there? Please share the links!
      I have only seen this one:

      http://mvc.syncfusion.com/demos/ui/grid/productshowcase/signalr#

      Category
      ASP.NET
      Topics
      SignalR

      0 comments

      Discussion are closed.