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 inte
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...
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.
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;
}
}
}
}
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
}
}
}
What about using SignalR 2?