Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

Using WebSocket in .NET 4.5 (Part 3)

4.81/5 (15 votes)
15 Jul 2013CPOL4 min read 95.8K   7K  
Using WebSocket in WCF service and communicating with the service in client applications or JavaScript on web pages

Introduction 

Part 1 gives an overview of the WebSocket protocol and .NET WebSocket support. Part 2 demonstrates how to use WebSocket in traditional ASP.NET and MVC 4 web applications. 

In this article, I will demonstrate how to use WCF to host a WebSocket service and write a client application. I will also show how to communicate with a WCF WebSocket service using JavaScript in a web page.

  • WcfWSChat.zip is the sample code of the WCF WebSocket service with a WCF client application.
  • WcfWSChatForWeb.zip is the sample code of the WCF WebSocket service with a web page and JavaScript.

Preparation 

To enable WebSocket on the server side, please refer to Part 1.

Host Service

In WCF 4.5, netHttpBinding and netHttpsBinding have been re-implemented to support WebSocket. And to make a WCF service serve the client through WebSocket, we should set CallbackContract for the ServiceContract attribute in the service interface declaration. For example:

C#
[ServiceContract(CallbackContract = typeof(IWSChatCallback))]
public interface IWSChatService
{  
    [OperationContract(IsOneWay = true)]
    Task SendMessageToServer(string msg);
}

The SendMessageToServer method will be called by the client and the msg argument is the message sent by the user. So the server should implement this method to handle the msg received from the client. IsOneWay should be set to true for the OperationContract attribute because the client should not wait for the return value of the service method call in duplex communication. CallbackContract is bound to IWSChatCallback which is declared as below:

C#
[ServiceContract]
interface IWSChatCallback
{   
    [OperationContract(IsOneWay = true)]
    Task SendMessageToClient(string msg);
} 

The SendMessageToClient method will be called by the server and the msg argument is the message sent by the server. So the client should implement this method to handle the msg received from the server. Again, IsOneWay should be set to true because the server should not wait for the return value of the callback method call in duplex communication.

Let’s simply implement IWSChatService as an echo server:

C#
public class WSChatService : IWSChatService
{
    public async Task SendMessageToServer(string msg)
    {
        var callback = OperationContext.Current.GetCallbackChannel<IWSChatCallback>();
        if (((IChannel)callback).State == CommunicationState.Opened)
        {
            await callback.SendMessageToClient(
                string.Format("Got message {0} at {1}", 
                msg, DateTime.Now.ToLongTimeString()));
        }
    }
}

The service just decorates the message received from the client and calls SendMessageToClient to send the decorated message to the client.

Then we need to modify web.config to add a protocol mapping schema in the <system.serviceModel> tag like:

XML
<protocolMapping> 
  <add scheme="http" binding="netHttpBinding"/>
</protocolMapping> 

The default netHttpBinding and netHttpsBinding will transfer data in Binary SOAP format. If you want to use Text SOAP format, you could create a customized configuration and set messageEncoding to “Text” like:

XML
<protocolMapping>
  <add scheme="http" binding="netHttpBinding" bindingConfiguration="textWSHttpBinding"/>
</protocolMapping>
<bindings>
  <netHttpBinding>
    <binding name="textWSHttpBinding" messageEncoding="Text"/>
  </netHttpBinding>
</bindings> 

OK. The WCF WebSocket service gets ready after I build it.

Client Application

To write a WCF client application, I create a console application and add the previous WCF service to Service References. Then I implement the IWSChatServiceCallback interface declared previously on the server:

C#
internal class CallbackClient : IWSChatServiceCallback
{
    public void SendMessageToClient(string msg)
    {
        Console.WriteLine(msg);
    }
} 

In the SendMessageToClient method, I just display the message received from the server.

The program’s main function looks like:

C#
class Program
{
    static void Main(string[] args)
    {
        var context = new InstanceContext(new CallbackClient());
        var client = new WSChatServiceClient(context);
        while (true)
        {
            Console.Write("Input (\"Exit\" to exit):");
            string input = Console.ReadLine();
            if (input.ToUpperInvariant() == "EXIT")
            {
                break;
            }
            client.SendMessageToServer(input);
            Thread.Sleep(500);
        }
        client.Close();
    }
} 

Don’t forget to modify the endpoint address in App.config to point to the correct machine name, domain name, or IP address.

Again to emphasize, the client application can only work on Windows 8, Windows Server 2012, and above.

JavaScript in Web Page

By using netHttpBinding and netHttpsBinding, all data transferred in a WCF WebSocket connection is in SOAP format (Binary or Text). To communicate with the WCF WebSocket service with JavaScript code in the web page, I have to parse the SOAP by myself.

To avoid SOAP, I need to create and use a custom binding as below:

XML
<bindings>
  <customBinding>
    <binding name="textWSHttpBinding">
      <byteStreamMessageEncoding/>     
      <httpTransport>       
        <webSocketSettings transportUsage="Always"          
            createNotificationOnConnection="true"/>     
      </httpTransport>
    </binding> 
  </customBinding>
</bindings>
<protocolMapping>
  <add scheme="http" binding="customBinding" 
      bindingConfiguration="textWSHttpBinding"/>
</protocolMapping>

I use byteStreamMessageEncoding to specify the data should be transferred as byte stream instead of SOAP. In webSocketSettings, transportUsage is set to Always to enable the WebSocket protocol regardless of the contract; createNotificationOnConnection must be set to “true” to notify the client when connection is established.

The service interface and implementation need to change as well. I modified IWSChatService and IWSChatCallback as below:

C#
[ServiceContract(CallbackContract = typeof(IWSChatCallback))]
public interface IWSChatService
{
    [OperationContract(IsOneWay = true, Action = "*")]
    Task SendMessageToServer(Message msg);
}
[ServiceContract]
interface IWSChatCallback
{
    [OperationContract(IsOneWay = true, Action="*")]
    Task SendMessageToClient(Message msg);
} 

I change the type of the msg argument from string to Message (System.ServiceModel.Channels) which will wrap my UTF-8 encoded text. Then I re-implement IWSChatService as below:

C#
public class WSChatService : IWSChatService
{
    public async Task SendMessageToServer(Message msg)
    {
        var callback = OperationContext.Current.GetCallbackChannel<IWSChatCallback>();
        if (msg.IsEmpty || ((IChannel)callback).State != CommunicationState.Opened)
        {
            return;
        }
        byte[] body = msg.GetBody<byte[]>();
        string msgTextFromClient = Encoding.UTF8.GetString(body);
        string msgTextToClient = string.Format(
            "Got message {0} at {1}",
            msgTextFromClient,
            DateTime.Now.ToLongTimeString());
        await callback.SendMessageToClient(
            CreateMessage(msgTextToClient));
    }
    private Message CreateMessage(string msgText)
    {
        Message msg = ByteStreamMessage.CreateMessage(
            new ArraySegment<byte>(Encoding.UTF8.GetBytes(msgText)));
        msg.Properties["WebSocketMessageProperty"] =
            new WebSocketMessageProperty 
            { MessageType = WebSocketMessageType.Text };
        return msg;
    }
} 

The client side need not implement IWSChatCallback. My JavaScript code is very similar to the example in Part 2:

HTML
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>WebSocket Chat</title>
    <script type="text/javascript" src="Scripts/jquery-2.0.2.js"></script>
    <script type="text/javascript">
        var ws;
        $().ready(function () {
            $("#btnConnect").click(function () {
                $("#spanStatus").text("connecting");
                ws = new WebSocket("ws://" + window.location.hostname
                    + "/WcfWSChatForWeb/WSChatService.svc");
                ws.onopen = function () {
                    $("#spanStatus").text("connected");
                };
                ws.onmessage = function (evt) {
                    $("#spanStatus").text(evt.data);
                };
                ws.onerror = function (evt) {
                    $("#spanStatus").text(evt.message);
                };
                ws.onclose = function () {
                    $("#spanStatus").text("disconnected");
                };
            });
            $("#btnSend").click(function () {
                if (ws.readyState == WebSocket.OPEN) {
                    ws.send($("#textInput").val());
                }
                else {
                    $("#spanStatus").text("Connection is closed");
                }
            });
            $("#btnDisconnect").click(function () {
                ws.close();
            });
        });
    </script>
</head>
<body>
    <input type="button" value="Connect" id="btnConnect" />
    <input type="button" value="Disconnect" id="btnDisconnect" /><br />
    <input type="text" id="textInput" />
    <input type="button" value="Send" id="btnSend" /><br />
    <span id="spanStatus">(display)</span>
</body>
</html> 

Summary

Next in Part 4, I will demonstrate how to use Microsoft.WebSockets.dll.

Related Links 

Using WebSocket in .NET 4.5: 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)