This answer on StackOverflow inspired me to write a full post about handling user interactions in desktop applications fashion using web sockets and ASP.NET MVC.

The main idea is to send commands to user interface from server-side code and handling the result on server side. As you know, the main problem in web is in the following, web working on request-response system, that means, when user send request to server, it generates the resulting markup for view. After it server waits for another request and so on.

I recommend to read this post, if you are interested at web development. If it would not be helpful for you, it could help you to understand some principles of architecture with web sockets.

Web sockets is an emerging technology, which allows to create a persistent connection from client side to server side and back. What means, both server and client can send some data to each other during the ligecycle of web socket.

As most of developers came to web from desktop development, so the great amount interesting at sending commands from server to client side and want to process the result.

Setuping client side

Web sockets are initializing on client side, actually it is creating and waiting for some messages from server.

I suggest to encapsulate code for web sockets initialization into the separate class, it allows you to write cleaner code.

$app = {
    uiEventsSocket : null,
    initUIEventsConnection : function(url) {
        //create a web socket connection
        if (typeof (WebSocket) !== 'undefined') {
                this.uiEventsSocket = new WebSocket(url);
            } else if (typeof (MozWebSocket) !== 'undefined') {
                this.uiEventsSocket = new MozWebSocket(url);
            } else {
                console.error('WebSockets unavailable.');
            }

            //notify if there is an web socket error
            this.uiEventsSocket.onerror = function () {
                console.error('WebSocket raised error.');
            }

            this.uiEventsSocket.onopen = function () {
                console.log("Connection to " + url + " established");
            }

            //handling message from server side
            this.uiEventsSocket.onmessage = function (msg) {
                this._handleMessage(msg.data);
            };
    },

    _handleMessage : function(data){
        //the message should be in json format
        //the next line fails if it is not
        var command = JSON.parse(data);

        //here is handling the request to show prompt
        if (command.CommandType == 'yesNo') {
            var message = command.Message;

            var result = confirm(message);
            //not sure that bool value will be successfully converted
            this.uiEventsSocket.send(result ? "true" : "false");
        }
    }
}

Here is defined a separate object, named $app, the field uiEventsSocket of it will contain the result web socket instance. The function initUIEventsConnection initializes web socket and defining a handlers for different socket’s event. The most interesting handler is onmessage. This event fires when server send some data to client. Assuming, that interaction format will be JSON of following type:

{
    CommandType : 'string', //required
    Message : 'string' //optional
}

CommandType field needs to separate the received command and realize what method should be calles from JS. The other fields can be differ depending on CommandType. In this case should be initialized javascript confirm method, so the field for CommandType == ‘yesNo’ will be the Message.

As we encapsulating the full code into the single object, it should be initialized during the start of page life cycle in the following manner:

window.onload = function() { $app.initUIEventsConnection(yourUrl); }

or, if you are using jQuery:

$(function() { $app.initUIEventsConnection(yourUrl); })

Note that due to WebSocket specs the url for connection should have ws:// or wss:// protocol instead of http:// or https://. Which means Web Sockets and Web Sockets Secure relatively.

Setuping server side

The most interesting implementation is on server side instead of client side. Through the web there are lot of tutorials for setuping web sockets in different version of IIS and different version of MVC. The following example will be in style of the most common MVC approach, using generic handlers for web sockets handling.

Firstly, it should be created a server side copy of command class, in the following manner:

public abstract class UICommand
{
    public string CommandType { get; set; }
    public Type ReturnType { get; set; }
}

public class YesNoUICommand : UICommand
{
    public string Message { get; set; }

    public YesNoUICommand()
    {
        this.CommandType = "yesNo"; //enums can be used
        this.ReturnType = typeof(bool);
    }
}

The base abstract class for all commands implementing and the concrete implementation for YesNo command. As it could be seen, there is a ReturnType field in the definition of abstract class. It needs for converting result into the required value, as interaction between server and client use string and in this example there is bool needs.

The following structure will contain a generic handler (.ashx) and a separate singleton for manipulating with send and received data from client side.

Singleton

It should contain a list of connected web sockets groupped by user. Why should we group it? If it will not be, we cannot separate to what user we should send a command, and the command will be send to all of users.

public class WebSocketsSingleton
{
    private static WebSocketsSingleton _instance = null;
    //here stored web sockets groupped by user
    //you could use user Id or another marker to exactly determine the user
    private Dictionary<string, List<WebSocket>> _connectedSockets;

    //for a thread-safety usage
    private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim();

    public static WebSocketsSingleton Instance {
        get {
            if (this._instance == null)
            {
                this._instance = new WebSocketsSingleton();
            }

            return this._instance;
        }
    }

    private WebSocketsSingleton()
    {
        this._connectedSockets = new Dictionary<string, List<WebSocket>>();
    }

    // Adds a socket into the required collection
    public void AddSocket(string userName, WebSocket ws)
    {
        if (!this._connectedSockets.ContainsKey(userName))
        {
            Locker.EnterWriteLock();
            try
            {
                this._connectedSockets.Add(userName, new List<WebSocket>());
            }
            finally
            {
                Locker.ExitWriteLock();
            }
        }

        Locker.EnterWriteLock();
        try
        {
            this._connectedSockets[userName].Add(ws);
        }
        finally
        {
            Locker.ExitWriteLock();
        }
    }

    // Sends a UI command to required user
    public async Task SendAsync(string userName, UICommand command)
    {
        if (this._connectedSockets.ContainsKey(userName))
        {
            var sendData = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(command));

        foreach(var item in this._connectedSockets[userName])
        {
            try
            {
                await item.SendAsync(new ArraySegment(sendData), WebSocketMessageType.Text, true, CancellationToken.None);
            }
            catch (ObjectDisposedException)
            {
                //socket removed from front end side
            }
        }

            var buffer = new ArraySegment(new byte[1024]);
            var token = CancellationToken.None;         
            foreach(var item in this._connectedSockets[userName])
            {
                await Task.Run(async () => {
                    var tempResult = await item.ReceiveAsync(buffer, token);
                    //result received
                    token = new CancellationToken(true);
                });
            }

            var resultStr = Encoding.Utf8.GetString(buffer.Array);

            if (command.ReturnType == typeof(bool))
            {
                return resultStr.ToLower() == "true";
            }

            //other methods to convert result into required type

            return resultStr;
        }

        return null;
    }
}

Public static property, private default constructor… the most common singleton implementation. Static field Locker allows us to add instances of web socket and group it by user. In this example, I am groupping users by user name. If you are using an Form authentication, you could group by user Id, it will be more efficient, and for Windows authentication users could be groupped by Sid.

AddSocket method checks the presence of passed user name in the resulting dictionary and add the new pair of user name and list with single socket into it, if passed user name is present at this dictionary, socket will be simply added to already created list.

SendAsync method gets the user name and command, serializing command, send it to all of web socket connection, related to this user and waiting for respond. Note, that waiting for respond from sockets occuring in different Threads, as we cannot wait it from single thread, it will block the execution. When we get the result from one thread, each other will be stopped and we will process the result.

When we receiving response from client side, we can work with it, we converting it to required return type and sending back to context.

Handler

It is implementation of generic handler, which can be simple added with Add->Existing Item->Generic Handler

public class UIEventsHandler : IHttpHandler, IRequiresSessionState
{
    public void ProcessRequest(HttpContext context)
    {
        if (context.IsWebSocketRequest)
        {
            context.AcceptWebSocketRequest(UIEventRequest);
        }
        else 
        {
            throw new ApplicationException("There was not web socket request");
        }
    }

    public bool IsReusable
    {
        get
        {
            return false;
        }
    }

    private async Task UIEventRequest(AspNetWebSocketContext context)
    {
        var userName = HttpContext.Current.User.Identity.Name;
        var socket = context.WebSocket;
        
        WebSocketsSingleton.Instance.AddSocket(userName, socket);

        while (socket.State == WebSocketState.Open))
        {
            //simply holding the connection to this socket
            Thread.Sleep(5000);
        }
        
        //here the socket should be removed from collection
    }

}

IRequiresSessionState needs for accessing current user Identity, which is stored at session. The handler fails, if this could be removed.

The logic of this handler is to simply accept web socket request and add it to singleton collection. Another work will be done through code.

Usage

This implementation should allow developer to use it in the following manner:

public async Task ShowYesNoQuestionBox(string userName, string text)
{
    var command = new YesNoUICommand
    {
        Message = text
    };

    return await WebSocketsSingleton.Instance.SendAsync(string userName, command);
}

Improvements

I have not tested this code, but there is still some problems:

  1. When waiting for a respond from client side, there could be incorrect usage of await and loop, it should be improved due to creating different threads and waiting the respond in a single thread per socket.
  2. After notification sent, all handlers receive this, but user works only in one window, so when he respond at single window – prompt still be exist at others. This code should be improved for creating prompts when one of socket sends respond from client side.
  3. When user closing the tab of browser, web socket dies. There should exists a techniques for removing died sockets from the resulting list.

Conclusion

This article is some kind of direction for web sockets usage as handler for code requests to user. It could be simple and efficient and allows the developer work witth web, like with the desktop application.

Advertisements

One thought on “User interactive abstractions handling at asp.net MVC

  1. public static WebSocketsSingleton Instance {
    get {
    if (this._instance == null)
    {
    this._instance = new WebSocketsSingleton();
    }

    return this._instance;
    }
    }

    Singleton thread-safety problem… Should we add some lock? Or maybe couple of them?
    No, no! There is a better way without locks!

    LET THE HOLY WAR BEGINS !!! 😉

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s