Click here to Skip to main content
Click here to Skip to main content
Go to top

What’s new in WCF 4.5? WebSocket support (Part 2 of 2)

, 6 Mar 2012
Rate this:
Please Sign up or sign in to vote.
WebSocket support with plain text messages that enables the interaction between web browsers and WCF.

It’s time for post No. 2 in the WCF 4.5 series. Part 1 of 2 was about WebSocket support with SOAP-based messages. Part 2 is about WebSocket support with plain text messages that enables the interaction between web browsers and WCF.

Previous posts:

  1. What’s new in WCF 4.5? let’s start with WCF configuration
  2. What’s new in WCF 4.5? a single WSDL file
  3. What’s new in WCF 4.5? Configuration tooltips and intellisense in config files
  4. What’s new in WCF 4.5? Configuration validations
  5. What’s new in WCF 4.5? Multiple authentication support on a single endpoint in IIS
  6. What’s new in WCF 4.5? Automatic HTTPS endpoint for IIS
  7. What’s new in WCF 4.5? BasicHttpsBinding
  8. What’s new in WCF 4.5? Changed default for ASP.NET compatibility mode
  9. What’s new in WCF 4.5? Improved streaming in IIS hosting
  10. What’s new in WCF 4.5? UDP transport support
  11. What’s new in WCF 4.5? WebSocket support (Part 1 of 2)

If you haven’t read part 1, please go over it first so you can get the gist about WebSockets, NetHttpBinding, and how it is used in WCF.

In part 1 I demonstrated how to create both binary encoded SOAP bindings and text encoded SOAP bindings with WebSockets. Problem is that in JavaScript it can get difficult to create and parse SOAP messages - this is why we tend to use XML/JSON based bindings (such as WebHttpBinding) instead of SOAP-based bindings (BasicHttpBinding/WsHttpBinding) when calling WCF services from JavaScript.

Creating a duplex service with WebSockets, NetHttpBinding, and plain text messages, is just like creating any other WCF service:

  1. Define the contract and callback contract
  2. Implement the service
  3. Configure the host
  4. Consume the service from a client app

First we will create our contract. Since we need to receive and send messages, we will create a duplex contract, each contract with a single method which we will mark with action=”*”:

Contracts
[ServiceContract]
public interface IWebSocketEchoCallback
{        
    [OperationContract(IsOneWay = true, Action = "*")]        
    void Send(Message message);
}

[ServiceContract(CallbackContract = typeof(IWebSocketEchoCallback))]
public interface IWebSocketEcho
{
    [OperationContract(IsOneWay = true, Action = "*")]
    void Receive(Message message);
}

The echo service itself is a simple implementation that receives the message and sends it back to the client:

EchoService
public class EchoService : IWebSocketEcho
{
    IWebSocketEchoCallback _callback = null;

    public EchoService()
    {
        _callback =
            OperationContext.Current.GetCallbackChannel<IWebSocketEchoCallback>();
    }
    public void Receive(Message message)
    {
        if (message == null)
        {
            throw new ArgumentNullException("message");
        }

        WebSocketMessageProperty property = 
                (WebSocketMessageProperty)message.Properties["WebSocketMessageProperty"];
        WebSocketContext context = property.WebSocketContext;
        var queryParameters = HttpUtility.ParseQueryString(context.RequestUri.Query);
        string content = string.Empty;

        if (!message.IsEmpty)
        {
            byte[] body = message.GetBody<byte[]>();
            content = Encoding.UTF8.GetString(body);                
        }

        // Do something with the content/queryParams
        // ...
            
        string str = null;
        if (string.IsNullOrEmpty(content)) // Connection open message
        {
            str = "Opening connection from user " + 
                    queryParameters["Name"].ToString();                
        }
        else // Message received from client
        {
            str = "Received message: " + content;                
        }

        _callback.Send(CreateMessage(str));            
    }

    private Message CreateMessage(string content)
    {
        Message message = ByteStreamMessage.CreateMessage(
                new ArraySegment<byte>(
                    Encoding.UTF8.GetBytes(content)));
        message.Properties["WebSocketMessageProperty"] =
                new WebSocketMessageProperty
                { MessageType = WebSocketMessageType.Text };
                        
        return message;
    }
}

The Receive method handles two types of calls:

  1. The first “connection upgrade” message – when the client first connects to the service and tries to upgrade the connection from HTTP to WebSocket. In this call, the request is sent using HTTP GET, and therefore there is no body, but we can access the URL’s query string.
  2. The second message and on are the messages being sent by the client over the WebSocket transport – these messages contain a message body, with no special query string.

Line 5-9 shows how to create a standard duplex service by storing the callback channel in a local variable. The callback channel will be used later on in the code in order to send messages back to the client. The service uses the default instancing mode which is PerSession, so a new instance will be created for each client, and the local variable will point to a different callback channel in each service instance.

Lines 17-27 demonstrates the technique of parsing the message – either by checking its query string or by reading the byte array from the message and transforming it to a string.

Lines 32-43 checks which type of message is being handled, the first connection request, or a consequent message from the client. In each case the service responds by echoing the message back to the client.

Line 46-56 demonstrates how to create a Message object with a simple string content when using the byte stream encoding.

Note: to use the ByteStreamMessage type, add a reference to the System.ServiceModel.Channels assembly.

Note: WebSocket messages can be either text or binary, so if you are planning on using binary messages you will need to change the code to work with byte arrays instead of strings.

Now that we have the contracts and the service, we need to define our host and endpoint. In this example I will use IIS as the host and I will use the routing mechanism of ASP.NET to create a service URL address that doesn’t contain the annoying “.svc” extension. The following global.asax code shows how to do that:

Global.Asax
public class Global : System.Web.HttpApplication
{
    protected void Application_Start(object sender, EventArgs e)
    {
        RouteTable.Routes.Add(new ServiceRoute("echo",
            new ServiceHostFactory(),
            typeof(EchoService)));
    }
}

And now for the endpoint configuration. Since NetHttpBinding uses SOAP messages, and there is no “WebSocketHttpBinding” for passing plain byte streams, we need to create a custom binding that will allow us to receive messages over WebSocket where the message can either be a text message or a binary message (the WebSocket API supports both types).

The standard encodings of WCF - text, binary, and MTOM, will not enable us to receive non-SOAP byte streams, that is why we need to use a new encoding which was introduced in WCF 4 – the ByteStreamMessageEncoding.

The following endpoint and binding configuration will allow us to open a WebSocket listener that receives simple byte streams:

Service Configuration
<system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" 
                               multipleSiteBindingsEnabled="true" />
    <services>
      <service name="UsingWebSockets.EchoService">
        <endpoint address="" 
                  binding="customBinding" 
                  bindingConfiguration="webSocket"
                  contract="UsingWebSockets.IWebSocketEcho" />
      </service>
    </services>
    <bindings>
      <customBinding>
        <binding name="webSocket">          
          <byteStreamMessageEncoding/>            
          <httpTransport>            
            <webSocketSettings transportUsage="Always" 
                               createNotificationOnConnection="true"/>
          </httpTransport>
        </binding>
      </customBinding>
    </bindings>
</system.serviceModel>

The important part in the configuration is lines 13-21:

  1. We set transportUsage to Always to force the usage of WebSocket rather than HTTP.
  2. We set createNotificationOnConnection to true to allow our Receive method to be invoked for the connection request message (the first GET request which is sent to the service).
  3. We use byteStreamMessageEncoding which allows the service to receive simple byte streams as input instead of complex SOAP structures.

To test our code we can add an HTML page to our project. The following code is based on the StockTicker demo from the HTML5 Labs website:

Echo Client
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Echo Demo</title>
    <script src="Scripts/jquery-1.4.1.js" type="text/javascript"></script>
    <script>

        $(document).ready(function () {
            if (!window.WebSocket && window.MozWebSocket) {
                window.WebSocket = window.MozWebSocket;
            }

            $('#echoForm').submit(function (event) {
                $('#echoForm')
                    .add('#echoForm > *')
                    .attr('disabled', 'disabled');

                var uri = 'ws://' + window.location.hostname +
                    window.location.pathname.replace('EchoDemo.html', 'echo') +
                    '?Name=' + $("#name").val();
                connect(uri);
                event.preventDefault();
            });

        });

        function connect(uri) {
            $('#messages').prepend('<div>Connecting...</div>');

            var websocket = new WebSocket(uri);
            
            websocket.onopen = function () {
                window.focus();
                $('#echoForm').hide();
                $('#outputArea').show();
                window.setInterval(function()
                {
                   websocket.send("the time is " + new Date());
                }, 1000);                
                $('#messages').html(
                    '<div>Connected. Waiting for messages...</div>');
            };

            websocket.onclose = function () {
                if (document.readyState == "complete") {
                    var warn = $('<div>').html(
                        'Connection lost. Refresh the page to start again.').
                        css('color', 'red');
                    $('#messages').append(warn);
                }
            };

            websocket.onmessage = function (event) {
                $("#messages").append(event.data + "<br>");
            };
        };

</script>
</head>
<body>
    <form id="echoForm" action="">
    <input type="text" id="name" placeholder="type your name" />
    </form>
            <div id="outputArea" style="display: none">
        <div id="messages" style="height: 80%; overflow: hidden">
        </div>
    </div>
</body>
</html>

Most of the above code is jQuery stuff to handle the incoming message, so let’s point out the important parts:

Lines 18-20 – In these lines we create the URI of the service. Note the use of the ws:// scheme – this is the scheme of WebSocket, but it works just fine even when our service base address is set to HTTP.

Lines 27-56 – the connect function basically does all the rest. The WebSocket functions are based on the WebSocket API.

  • Line 30 – create the WebSocket object
  • Lines 32-42 – open the connection
  • Lines 36-49 – run a function every 1 second that sends the current time to the service
  • Lines 44-51 – handle the WebSocket channel closing
  • Lines 53-55 – handle a received message (a message sent from the service to the client)

Running the client will show the following output:

image

To conclude, in order to create a service that can receive and send message to browsers using WebSockets we need to do the following:

  1. Create the duplex contract which contains a simple Receive and Send methods (or any other names you like).
  2. Implement the contract in a service like you’ll implement any other duplex service. The only thing you need to take care of is how to read and write the message.
  3. Create an endpoint which uses a custom binding which supports WebSockets and simple byte-stream encoding.

Although the above works quite well, there is another way to create this type of service – by creating a service class that inherits from the Microsoft.WebSockets.WebSocketService type. The Microsoft.WebSockets package, available from NuGet, enables the creation of WebSocket-based services. Once inheriting your service from WebSocketService, you can override methods such as OnMessage, OnOpen, OnClose, and OnError. Working with these methods is quite easy, as demonstrated in the following code:

EchoService2
public class EchoService2 : 
     Microsoft.ServiceModel.WebSockets.WebSocketService
 {
     public override void OnMessage(string message)
     {
         string str = "Received message: " + message;
         Send(str);
     }
     
     public override void OnOpen()
     {
         var queryParameters = this.QueryParameters;
         string str = "Opening connection from user " +
             queryParameters["Name"].ToString();

          Send(str);                        
     }

     protected override void OnClose()
     {
         base.OnClose();            
     }

     protected override void OnError()
     {
         base.OnError();            
     }
}

As you can see, in this case you don’t have to work directly with byte arrays. In order to host this service you also don’t need to define a special endpoint configuration, as this package includes a WebSocketHost class that automatically creates and configures a WebSocket endpoint. To create a WebSocketHost and provide it to IIS, we need to create a class that inherits from ServiceHostFactory, as demonstrated in the following code:

WebSocketServiceHostFactory
public class WebSocketServiceHostFactory : ServiceHostFactory
{
    protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
    {
        var host = new WebSocketHost(serviceType, baseAddresses);
        host.AddWebSocketEndpoint();
        return host;
    }
}

Note: the ServiceHostFactory is declared in the System.ServiceModel.Activation assembly, so don’t forget to add a reference to it.

Once we have the new factory, we can register it with the routing mechanism (lines 5-7):

Global.Asax
public class Global : System.Web.HttpApplication
{
      protected void Application_Start(object sender, EventArgs e)
      {
          RouteTable.Routes.Add(new ServiceRoute("echo2",
              new WebSocketServiceHostFactory(),
              typeof(EchoService2)));

          RouteTable.Routes.Add(new ServiceRoute("echo",
              new ServiceHostFactory(),
              typeof(EchoService)));
      }
}

All is left is to change the client HTML code in line 19 to call the ‘echo2’ service instead of ‘echo’.

You can see more examples on how to use this package in Paul Batum’s blog post, and in his //BUILD session.

So as you can see, it is quite easy to create a WCF service that can receive messages from a browser and push messages to a browser by using WebSockets. Farewell long polling, I hope we never meet again Smile

You can download the above code (both versions) from my SkyDrive. The source code also includes a sample self-hosted WebSocket service and an HTML page that uses it instead of the IIS-hosted service.

License

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

Share

About the Author

Ido Flatow
Architect Sela Group
Israel Israel
Web developer since 1997. I'm a senior architect at Sela Group in Israel. I'm a consultant, trainer (Microsoft MCT), and a speaker in conferences worldwide.
My main fields are WCF, ASP.NET, Windows Azure, IIS, Entity Framework, and Silverlight.
Follow on   Twitter

Comments and Discussions

 
QuestionNo code to download.... But a great article ;) PinmemberMorgoZ16-Jun-14 20:44 
AnswerRe: No code to download.... But a great article ;) PinmemberIdo Flatow16-Jun-14 22:52 
QuestionCode not reachable Pinmemberlightstalker8917-May-14 6:21 
QuestionSky Source code is apsent PinmemberMember 390648320-Oct-13 10:34 
GeneralMy vote of 5 Pinmemberian__lindsay2-Jul-13 0:00 
BugGreat article but I cant get IIS hosting to work PinmemberPhlaz20-Jan-13 21:44 
QuestionSSL support PinmemberToivo A7-Dec-12 3:34 
AnswerRe: SSL support PinmemberToivo A9-Dec-12 21:48 
GeneralRe: SSL support Pinmemberian__lindsay1-Jul-13 23:58 
GeneralRe: SSL support PinmemberIdo Flatow2-Jul-13 4:34 
GeneralMy vote of 5 Pinmemberjfriedman14-Mar-12 1:33 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140916.1 | Last Updated 6 Mar 2012
Article Copyright 2012 by Ido Flatow
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid