Using WebSockets with ASP.NET Web API
Asked Answered
E

6

26

What is the preferred method for using raw websockets in an ASP.NET Web API application?

We'd like to use binary WebSockets on a couple of our interfaces of our ASP.NET Web API application. I'm having a difficult time determining how this should be done as there seems to be several conflicting and/or out-dated implementations online for .NET.

There are examples which appear to be ASP.NET like this one, but I think there must be a means to use websockets within the Web API framework. As I know you can use Signalr within WebAPI.

I thought using Microsoft.AspNet.SignalR.WebSockets.WebSocketHandler would work, but I'm not sure how to link the WebSocketHandler to the Controller...

class MyServiceController : ApiController
{
    [HttpGet]
    public HttpResponseMessage SwitchProtocols (string param)
    {
        HttpContext currentContext = HttpContext.Current;
        if (currentContext.IsWebSocketRequest || 
            currentContext.IsWebSocketRequestUpgrading)
        {
            // several out-dated(?) examples would 
            // use 'new MySocketHandler' for ???
            var unknown = new ????
            currentContext.AcceptWebSocketRequest(unknown); 
            return Request.CreateResponse(HttpStatusCode.SwitchingProtocols);
        }   
    }
}

class MySocketHandler : WebSocketHandler
{
    public MySocketHandler(): base(2048){}

    ...
}

Unfortunately, AcceptWebSocketRequest no longer accepts a WebSocketHandler, instead its new signature is...

public void AcceptWebSocketRequest(Func<AspNetWebSocketContext, Task> userFunc)

Does anyone have a link or a quick sample implementing raw websockets in ASP.NET Web API application that is up-to-date?

Endoenzyme answered 4/9, 2014 at 14:42 Comment(0)
E
17

UPDATE: After a bit more research by myself and a coworker, we came to the conclusion that the WebSocketHandler class does not appear to be intended to be used outside of the internal processes of SignalR. As there is no obvious means to leverage WebSocketHandler isolated from SignalR. This is unfortunate as I find its interfaces slightly more high-level than the System.Web/System.Net interfaces. Moreover, the method described below makes use of HttpContext which I believe should be avoided.

As such we plan to take an approach similar to the one shown by Mrchief, but with a bit more Web API flavor. Like this...(NOTE: our socket is write-only, but I discovered you MUST perform read operations of you want WebSocket.State to get updated properly.

class MyServiceController : ApiController
{
    public HttpResponseMessage Get (string param)
    {
        HttpContext currentContext = HttpContext.Current;
        if (currentContext.IsWebSocketRequest || 
            currentContext.IsWebSocketRequestUpgrading)
        {
            currentContext.AcceptWebSocketRequest(ProcessWebsocketSession); 
            return Request.CreateResponse(HttpStatusCode.SwitchingProtocols);
        }   
    }

    private async Task ProcessWebsocketSession(AspNetWebSocketContext context)
    {
        var ws = context.WebSocket;

        new Task(() =>
        {
            var inputSegment = new ArraySegment<byte>(new byte[1024]);

            while (true)
            {
                // MUST read if we want the state to get updated...
                var result = await ws.ReceiveAsync(inputSegment, CancellationToken.None);

                if (ws.State != WebSocketState.Open)
                {
                    break;
                }
            }
        }).Start();

        while (true)
        {
            if (ws.State != WebSocketState.Open)
            {
                break;
            }
            else
            {
                byte[] binaryData = { 0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe };
                var segment = new ArraySegment<byte>(binaryData);
                await ws.SendAsync(segment, WebSocketMessageType.Binary, 
                    true, CancellationToken.None);
            }
        }
    }
}

NOTE: Obviously error checking and proper usage of a CancellationToken is left as an exercise for the reader.

Endoenzyme answered 4/9, 2014 at 18:24 Comment(3)
This works, but I learned a few things from testing that I'll update in my answer. Namely, you must call ReadAsync if you want the ws.State to get updated!Endoenzyme
Thanks for this code. There are some compile errors though -- the Get method needs a return after the if block, and the await inside the lambda requires the async modifier on the lambda.Thiosinamine
Thanks for the code, this gave me a great boost. However, using Task.Run with async lambda may not be the greatest idea (e.g. you get a compiler warning). I ended up creating ReadTest(WebSocket) and WriteTask(WebSocket) and running them in parallel using await Task.WhenAll(ReadTask(ws), WriteTask(ws))Disdain
M
15

This is an older question, but I would like to add another answer to this.
It turns out, you CAN use it, and I have no clue why they made it so "hidden". Would be nice if someone could explain to me what's wrong with this class, or if what I'm doing here is somehow "forbidden" or "bad design".

If we look in the Microsoft.Web.WebSockets.WebSocketHandler, we find this public method:

[EditorBrowsable(EditorBrowsableState.Never)]
public Task ProcessWebSocketRequestAsync(AspNetWebSocketContext webSocketContext);

This method is hidden from intellisense, but it's there, and can be called without compilation errors.
We can use this method to get the task that we need to return in the AcceptWebSocketRequest method. Check this out:

public class MyWebSocketHandler : WebSocketHandler
{
    private static WebSocketCollection clients = new WebSocketCollection();

    public override void OnOpen()
    {
        clients.Add(this);
    }

    public override void OnMessage(string message)
    {
        Send("Echo: " + message);
    }
}

And then in my API controller:

public class MessagingController : ApiController
{
    public HttpResponseMessage Get()
    {
        var currentContext = HttpContext.Current;
        if (currentContext.IsWebSocketRequest ||
            currentContext.IsWebSocketRequestUpgrading)
        {
            currentContext.AcceptWebSocketRequest(ProcessWebsocketSession);
        }

        return Request.CreateResponse(HttpStatusCode.SwitchingProtocols);
    }

    private Task ProcessWebsocketSession(AspNetWebSocketContext context)
    {
        var handler = new MyWebSocketHandler();
        var processTask = handler.ProcessWebSocketRequestAsync(context);
        return processTask;
    }
}

This works completely fine. OnMessage gets triggered, and echoes back to my JavaScript instantiated WebSocket...

Movable answered 17/1, 2016 at 11:42 Comment(10)
I see nothing bad with this design. And that EditorBrowsable attribute to hide a public interface is just directly crazy. Why it even exists, one can only wonder.Spar
+1 - René, would you happen to have a POC of this technique lying around somewhere? I want to use it, but I'm unsure on how to initialize. I imagine you specify a RoutePrefix for your ApiController, am I right?Importune
@lbotinelly, I suggest you try some google searches along the lines of "ASP.NET 4.5/5 WebSockets", you'll find some interesting articles like this one, which has a very detailed video, including the use of the WebSocketHandler: weblogs.asp.net/dwahlin/… :)Herthahertz
How would the request flow be from the client be for sending a message in this case?Cheeks
@ÞorvaldurRúnarsson I don't believe I fully understand your question. I simply created a web socket client in JavaScript as demonstrated by numerous examples online ;)Herthahertz
So it's just enough to change the https to ws and you should be able to connect?Cheeks
and use that url to feed the WebSocket client APIsCheeks
@ÞorvaldurRúnarsson Yup. Don't forget, if you're working with HTTPS, it needs to be WSS://, not WS://. Just try, there's loads of examples online ;)Herthahertz
This example is using the Microsoft.Web.WebSockets, which if you look at the nuget package Microsoft.WebSockets (I know it's confusing) it is no longer being developed and they recommend using SignalR. Which leads back to the original question of how to use SignalR.WebSocketHandler in the example above. Which based on my research can't be done easily.Lessee
The best lightweight implementation. Always choose this over signalrNereen
C
4

I found this example:

Sample code (reproduced from the post):

public class WSHandler : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        if (context.IsWebSocketRequest)
        {
            context.AcceptWebSocketRequest(ProcessWSChat);
        }
    }

    public bool IsReusable { get { return false; } }

    private async Task ProcessWSChat(AspNetWebSocketContext context)
    {
        WebSocket socket = context.WebSocket;
        while (true)
        {
            ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[1024]);
            WebSocketReceiveResult result = await socket.ReceiveAsync(
                buffer, CancellationToken.None);
            if (socket.State == WebSocketState.Open)
            {
                string userMessage = Encoding.UTF8.GetString(
                    buffer.Array, 0, result.Count);
                userMessage = "You sent: " + userMessage + " at " + 
                    DateTime.Now.ToLongTimeString();
                buffer = new ArraySegment<byte>(
                    Encoding.UTF8.GetBytes(userMessage));
                await socket.SendAsync(
                    buffer, WebSocketMessageType.Text, true, CancellationToken.None);
            }
            else
            {
                break;
            }
        }
    }
}
Countersignature answered 4/9, 2014 at 14:45 Comment(4)
I saw that but it doesn't leverage the Web API interfaces? Is that compatible with WebAPI?Endoenzyme
Which interfaces are you looking for exactly? Web API is built on top of ASP.Net and this should work (you may have to tweak things a bit though).Countersignature
The second example doesn't work as AcceptWebSocketRequest no longer accepts a WebSocketHandler as I mentioned above. I added a note or two to my original question to help clarify my confusion.Endoenzyme
Right, my bad. Updated my answer to delete that section.Countersignature
D
2

Sharing my code based on Tony's answer with cleaner task handling. This code sends out current UTC time approximately every second:

public class WsTimeController : ApiController
{
    [HttpGet]
    public HttpResponseMessage GetMessage()
    {
        var status = HttpStatusCode.BadRequest;
        var context = HttpContext.Current;
        if (context.IsWebSocketRequest)
        {
            context.AcceptWebSocketRequest(ProcessRequest);
            status = HttpStatusCode.SwitchingProtocols;

        }

        return new HttpResponseMessage(status);
    }

    private async Task ProcessRequest(AspNetWebSocketContext context)
    {
        var ws = context.WebSocket;
        await Task.WhenAll(WriteTask(ws), ReadTask(ws));
    }

    // MUST read if we want the socket state to be updated
    private async Task ReadTask(WebSocket ws)
    {
        var buffer = new ArraySegment<byte>(new byte[1024]);
        while (true)
        {
            await ws.ReceiveAsync(buffer, CancellationToken.None).ConfigureAwait(false);
            if (ws.State != WebSocketState.Open) break;
        }
    }

    private async Task WriteTask(WebSocket ws)
    {
        while (true)
        {
            var timeStr = DateTime.UtcNow.ToString("MMM dd yyyy HH:mm:ss.fff UTC", CultureInfo.InvariantCulture);
            var buffer = Encoding.UTF8.GetBytes(timeStr);
            if (ws.State != WebSocketState.Open) break;
            var sendTask = ws.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, CancellationToken.None);
            await sendTask.ConfigureAwait(false);
            if (ws.State != WebSocketState.Open) break;
            await Task.Delay(1000).ConfigureAwait(false); // this is NOT ideal
        }
    }
}
Disdain answered 27/8, 2017 at 2:30 Comment(2)
How can we modify the WriteTask method to send out the updated data every time there is a new transaction in DB.Spectrohelioscope
@Osama: there are several ways to do it. You can generate an enumerable of Tasks, each based on a TaskCompletionSource. Mark TaskCompletionSoruce as complete when an event of interest happens. I used this approach in nuget.org/packages/TaskTimer. Alternatively, you can use SemaphoreSlim.WaitAsync method to signal that something happened. Yet another approach is to use github.com/StephenCleary/AsyncEx/wiki/AsyncAutoResetEvent from the AsyncEx package.Disdain
Z
0

What about using SignalR 2?

  • Can be installed via NuGet
  • For .NET 4.5+
  • No permanent loop required
  • Broadcasting possible -Tutorial here
Zoogloea answered 13/8, 2016 at 12:29 Comment(2)
If SignalR2 supports binary websockets that might work. v1 did not, moreover, it wanted to Serialize/Deserialize packets into local objects which tends to require additional bandwidth. I needed to make my transport as tight as possible (I was streaming live audio data), so raw binary websockets was the best approach.Endoenzyme
And there is still a dependency on jquery which is the only reason I'm not using it.Selfassurance
E
0

It’s an old question, but it’s really unfortunate that to this day there is no official GOOD support for writing pure WebSocket servers. If you can’t use SignalR the official fallback is System.Net.WebSockets.

Quite a lot of hard work has gone into making SignalR as performant and reliable as it is today, and that makes the gap that much bigger.

For that reason, I created a library that is a fork of SignalR, but stripped from all the custom protocols. You can find it here. It’s now available as a nuget package(SimpleR.Server), hope it can help and in case of any questions and suggestions reach out to me or open an issue in github.

Eponymous answered 2/4, 2024 at 20:1 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.