Build React-RealTime .NET application using SignalR and MongoDB


How to build application using Reactive Programming paradigm (effect like in MeteorJS framework), when data is immediately refreshing when something changes in storage, based on MongoDB.

It can be done in .NET application by using:
  • MonogoDB with oplog collection and tailable corsor
  • WebSocket's based frameworks like SignalR (or Socket.io)
So what is oplog (or operation log). Basically,  oplog is a system collection which stored in local system database and contains info about all operation that changed all collection. For example insert item or update item or even create new collection, all this operation info will be in oplog.

Example how oplog's items look like below. Update operation of UI collection (in Test database).

/* 15 */
{
    "ts" : Timestamp(1438956908, 1),
    "h" : NumberLong(1100723948735166960),
    "v" : 2,
    "op" : "u",
    "ns" : "Test.UI",
    "o2" : {
        "_id" : ObjectId("55c33ea48d9fe379c192e91e")
    },
    "o" : {
        "_id" : ObjectId("55c33ea48d9fe379c192e91e"),
        "xc" : "o3242"
    }
}

How to Enable oplog

By default this collection not exists in local database.
Because oplog this is root point of replication mechanism in MongoDB. Intended for creating replication and synchronizing data between replicated servers. You can find detail info about oplog in replication here .

Next step is creating Replica Set, but without providing additional info like URL to secondary server, etc.

You can add sitting to mongo config like:
replSet=rs0
opLogSize=50

 Or when starts mongo service (mongod.exe) in command line:

c:\Program Files\MongoDB 2.6 Standard\bin\mongod.exe --dbpath=c:\data --logpath=C:\data\log\log.txt --replSet rs0 --opLogSize 50
  • it creates replica set with name: rs0
  • set oplog collection 50 mb. Notice: oplog - this is capped collection. Capped collections in mongo this is collections with fixed size. 
  • Then execute command rs.initiate()(don't forget to select db local before) in mongo shell (or use, for example, RoboMongo tool)   
Notice: It also can be used as windows service, using command  --install (or --remove for removing previous windows service)
 As a result you should see  this message:
{
 "info2" : "no configuration explicitly specified -- making one",
 "me" : "MONGOSERVER:27017",
 "info" : "Config now saved locally. Should come online in about a minute.",
 "ok" : 1
}

That's all oplog collection will be appeared in local db.


Creating tailable cursor.

Creating a tailable cursor for check how oplog works. This cursor will updates immediately when new data will be appear in oplog. All cursor's options here.  Code below:

c = db['oplog.rs'].find({fromMigrate: { $exists : false }})
.addOption(DBQuery.Option.tailable).addOption(DBQuery.Option.awaitData)

You can use c.next() to get only new item from oplog and check new data using c.hasNex() commands.

Notice: cursor will be close automatically. To prevent it you can add this command DBQuery.Option.noTimeout

Creating tailable cursor, C# version:

public void CursorDemo()
{
     var client = new MongoClient("mongodb://localhost:27017");
     var server = client.GetServer();
     var database = server.GetDatabase("local");
     
     MongoCollection opLog = database.GetCollection("oplog.rs");
     BsonValue lastId = BsonMinKey.Value;

     var query2 = Query.GT("_id", lastId);
     var cursor = opLog.FindAs<BsonDocument>(query2)
                       .SetFlags(QueryFlags.AwaitData |
                                 QueryFlags.TailableCursor |
                                 QueryFlags.NoCursorTimeout)
                       .SetSortOrder(SortBy.Ascending("$natural"));
                    

            using (var enumerator = cursor.GetEnumerator())
            {
                while (enumerator.MoveNext())
                {
                    var document = enumerator.Current;
                    lastId = document["ts"] as BsonTimestamp;
                    //new data items will apear here
                }
            }
 }


Example application based on ASP.NET MVC and SignalR 

First of all should be created ASP.NET mvc appliction and added SignalR library(using nudget).
Here you can find info how to get started.

Then you can see example of SignalR Hub with MongoDB tailable cursor. In this example I've retrieved data from oplog and data from my collection with name UI, (so you can change name to your own collection etc)

namespace MongoOplogTest
{
    public class MongoHub : Hub
    {
        public void StartMongoCursor()
        {
            //Connection to mongo server and system collection 'local'
            var client = new MongoClient("mongodb://localhost:27017");
            var server = client.GetServer();
            var database = server.GetDatabase("local");
            
            MongoCollection opLog = database.GetCollection("oplog.rs");
            BsonValue lastId = BsonMinKey.Value;

            //Cursor's query and cursor settings 
            var query2 = Query.GT("_id", lastId);
            var cursor = opLog.FindAs<BsonDocument>(query2)
                              .SetFlags(QueryFlags.AwaitData |
                                        QueryFlags.TailableCursor |
                                        QueryFlags.NoCursorTimeout).SetSortOrder(SortBy.Ascending("$natural"));
            
            using (var enumerator = cursor.GetEnumerator())
            {
                while (enumerator.MoveNext())
                {
                    var document = enumerator.Current;

                    //Sent data to client method 'updateData'
                    Clients.All.updateData(document.ToJson());

                    /* Read data from collection with name 'UI' */
                    var databaseTest = server.GetDatabase("Test");
                    var uiCollectionData = databaseTest.GetCollection("UI").FindAll();
                    //Send data to client method 'updateUiData'
                    Clients.All.updateUiData(uiCollectionData);
                }
            }
        }
    }
}

Client side example very simple.  JavaScript module with SignalR functions for connect to server, send data from client to signalr hub and few callbacks functions for getting data from hub(server-side) and update html.

<div id="operationLogInfo"></div>
<br/>
<div id="uiCollectionData"></div>

<script src="~/Scripts/jquery-1.7.1.js"></script>
<script src="/Scripts/jquery.signalR-2.2.0.min.js"></script>
<script src="signalr/hubs"></script>

<script type="text/javascript">
    var mongoCursorTestModule = (function() {
        return {
            
            mongoHub: $.connection.mongoHub,

            //starts connection with Hub and call Hub functions 
            startServer: function() {
                var self = this;

                self.mongoHub.connection.start()
                    //Register server calls
                    .done(function () {

                        //Call Hub(Server) function 
                        self.mongoHub.server.startMongoCursor();

                    });
            },

            //Register callback listeners to get data from Hub(Server-Side)
            clientEventsListerners: function () {
                var self = this;

                this.mongoHub.client.updateData = function (data) {
                    console.log(data);
                    self.updateHtmlFromOpLog(data);
                };

                this.mongoHub.client.updateUiData = function(data) {
                    console.log(data);
                    self.updateHtmlFromUICollection(data);
                };
            },
            
            //Simple read Json array from 'UI' collection and updates div  
            updateHtmlFromUICollection: function(data) {
                var result = "";
                for (var item in data) {
                    for (var innerTtem in data[item]) {
                        result += data[item][innerTtem].Value + "  ,  ";
                    }
                }
                $("#uiCollectionData").html(result);
            },

            //And updates div from 'oplog' callection
            updateHtmlFromOpLog: function (data) {
                $("#operationLogInfo").html(data);
            },

            init: function() {
                this.startServer();
                this.clientEventsListerners();
            }
        }
    })();

    $(function () {
        mongoCursorTestModule.init();
    });

</script>

That's it. I've used jQuery for quick demo purposes. Of course, the better way, is using this approach with  MVVM frameworks  like KnockoutJs, AngularJS, Backbone etc.
I will show this in my next articles.

Comments