Tuesday, April 12, 2011

MVC 3 Long Polling / "Comet" Chat Example

I recently read a great article on WebSocket alternatives in MVC by Clay Lenhart and decided to write up a quick proof of concept Chat Server Example for .Net MVC.  Here is a quick run-down of what I did.

First, read up on Comet; a basic way to have real-time notifications sent to a browser (like push notifications).  One common Comet technique involves "Long Polling" an http connection.  What this means is that we call out to a URL that expects to wait around for a long time before something happens.


Pubsub_poll


Pubsub_longpoll




The Long Poll Chat Async Controller

The first step for the chat server was to create a new Async Controller (Async what? Check out this great introduction to Async Controllers) to handle our Long Poll requests.  Here is the ChatController I created:

You can see that we've created our Index action as an Async that calls our to our ChatServer and checks for a message to arrive.  By default, I've specified a 60 second time limit before returning an empty list of messages, but you could theoretically let this go on for hours waiting for a response.  Once a response comes back from the ChatServer, we tell the AsyncManager we are done and return our messages in JSON form.


The ChatServer

I chose to use Observables and the Reactive (Rx) Framework to have a synchronized way of notifying all interested parties when a new message comes in.  To do this, I took advantage of the Subject<T> class that wraps a lot of the Observable and Observer implementation details.  To handle persisting my chat history, I use a simple Queue to keep the last 100 or so by default.  Whenever a new message comes in, I check the history length and pop off anything over the limit, then push in my new messages.  Yes, eventually I'd have to figure out a strategy for pushing this back to a database or Redis cache server thingy but this is just a demo, so relax.



The Client Side (HTML and JS)

On the client side, we need to take advantage of some of jQuery's helper methods for POSTing to an MVC Action (the Index Action on the Chat Controller in our case).  We are going to make a POST call to /Chat that will sit around until a new message arrives, or our 60 second timeout is reached.  If a new message comes in, our POST call will return immediately with the new message(s).  From there, we just insert them into our current chat history.



See It In Action



Example


I've uploaded the MVC Chat Example to AppHarbor for people to play around with.  You can also download the sample project from my dropbox account, or view the source at the MVCChatSite Project page on BitBucket.

9 comments:

  1. Hello!

    Great article! Can you please explain why in the CheckForMessagesAsync function you call onMessages like
    ((Action>)parm)(msgs);

    instead of just write "onMessages(msgs);"? Is it because it will be called from another thread? But I don't know how it can be a problem here...

    Thanks.

    ReplyDelete
  2. That's a good question. Whether I should have just used a closure, instead of passing the action as a parameter to the thread?

    In my experience, using closures in this type of scenario ends up giving you unexpected behavior. I think this is because C# Lambdas are "Lexical Closures" meaning they are not passed by value, but by reference. Therefore, when the reference changes (as in another call is made to that static method) the behavior will be different.

    When we pass the action as a parameter its value is then passed to use in the scope of the threadwait callback.

    Plus, in general, I'm a fan of this method because it keeps the waitcallback independent of outside "global" variables (like the closure). You can look at the code in the lambda and know exactly what it does because of the passed in parameter.

    ReplyDelete
  3. I am trying to implement a userlist for the chat using your asynchronous sample as a template. However I think maybe having a problem because i am now trying to handle two long polling asynchronous requests from one and the same page. Also I am not used to threading at all as I am a web developer so I am wondering if my approach is wrong, if I should send the userlist back in the same request as the messages?

    I am getting results on the client but I am pretty sure that the one long polling request is blocking the other as I tried to change the timeout to 5 seconds and the one side then pops up with my users every 5 seconds..

    Please help and give me a few hint on how to handle this... I am pretty stuck her :(

    ReplyDelete
  4. Hi Christian,

    I would probably send the user list along with the messages and just handle them in the client side.

    Try modifying the ChatResponse class to also pass back a list of new users (or the whole user list). If you go with just new users, you will probably want to send the current user list down when the page first loads.

    Once you've got the users coming back in the ChatResponse, update the getMessages javascript function in the Home/Index view to handle data.users as well as data.messages.

    Hope that helps. Otherwise, try to send me some code to my email address at jacob[dot]gable at gmail.

    ReplyDelete
  5. Hi mate

    Awesome stuff I am having a problem where the messages don't always get back to the client they are getting to the service as I have them saved but some how they randomly dont get back to the client any ideas?

    ReplyDelete
  6. What exactly is this :

    "private static Subject _messages = new Subject();"

    ??

    My MVC app won't build and it finds an error with the Subject in that line of code.
    Can you please tell me what "Subject" is and help me a little, to solve my problem ?

    ReplyDelete
  7. I meant this :

    private static Subject < MessageInfo > _messages = new Subject < MessageInfo >();

    ReplyDelete
  8. That is an observable, from the reactive extensions framework. You may need to download the framework from here http://msdn.microsoft.com/en-us/data/gg577610.aspx.

    A Subject is just a source of events that we can subscribe to, in this case, it's a source of chat message events that fire every time a new message comes in.

    More info on Subject here: http://msdn.microsoft.com/en-us/library/hh229173(v=vs.103).aspx

    ReplyDelete