This time I want to share with you a short example of how to implement WebSockets in your ASP.NET 4.6 Web API / AngularJS Application by using WebSocketHandler from Microsoft.WebSockets nuget Package.
Installing a Microsoft.WebSockets Nuget package
First of all you need to install the Microsoft.WebSockets from Nuget using Package Manager Console by typing:
PM> Install-Package Microsoft.WebSockets
Note that this Package is almost one year old now and not actively developed (says official Nuget package description, current version 0.2.3.1) but it is the best implementation I found so far if you are using it with ASP.NET 4.6.
You can also find a .NET Framework implementation of WebSockets in System.Web.WebSockets Namespace but I find it little bit impractical and if you want to implement it to be stable and multiclient, you will actually end up implementing the WebSocketHandler from Microsoft.WebSockets package.
Defining your Message Object
Here is a simple model of a Message Object that we will be sending to Client:
public
class
SimpleMessage
{
[JsonProperty("id")]
public
long Id { get; set; }
[JsonProperty("body")]
public
dynamic Body { get; set; }
[JsonProperty("sessionId")]
public
Guid SessionId { get; set; }
}
Implementing your WebSocketHandler
Now we need to implement our own WebSocketHandler. I called it for purpose of this Post "SimpleWebSocketHandler" and it looks like this:
public
class
SimpleWebSocketHandler : WebSocketHandler
{
private
static
WebSocketCollection _wsClients = new
WebSocketCollection();
public
override
void OnOpen()
{
_wsClients.Add(this);
base.OnOpen();
}
public
override
void OnClose()
{
wsClients.Remove(this);
base.OnClose();
}
public
void SendMessage(SimpleMessage message)
{
if (string.IsNullOrEmpty(message.SessionId))
{
SendBroadcastMessage(message);
}
else
{
SendMessage(message, message.SessionId);
}
}
public
void SendMessage(SimpleMessage message, string sessionId)
{
var webSockets = _wsClients.Where(s =>
{
var httpCookie = s.WebSocketContext.Cookies["SessionId"];
return httpCookie != null && httpCookie.Value == sessionId;
});
foreach (var socket in webSockets)
{
socket.Send(JsonConvert.SerializeObject(message));
}
}
public
void SendBroadcastMessage(SimpleMessage message)
{
_wsClients.Broadcast(JsonConvert.SerializeObject(message));
}
}
As you can see, I am also using a WebSocketCollection object for storing my opened WebSocket connections. That way I can simply send a Broadcast message to all WebSocket connections or filter the one that has my Session ID as a cookie (that will be place upon first web request in Startup.cs below) and send the message only to that client.
Changes in Application Startup
Now we have to make sure that we initialize our "SimpleWebSocketHandler" upon application start. If you are using an OWIN Middleware and Unity Container (and you should), your Startup.cs should look something like this:
Startup.cs
public
partial
class
Startup
{
public
void Configuration(IAppBuilder app)
{
var container = new
UnityContainer();
container
.RegisterType<SimpleWebSocketHandler>();
DependencyResolver.SetResolver(new
UnityDependencyResolver(container));
// register dependency resolver for WebAPI
configuration.DependencyResolver = new Unity.WebApi.UnityDependencyResolver(container);
UnityConfig.Initialise(GlobalConfiguration.Configuration);
ConfigureAuth(app);
ConfigureWebSocketListeners();
app.Use(new Func<AppFunc, AppFunc>(next => (async env =>
{
if (context.Request.Cookies["SessionId"] == null)
context.Response.Cookies.Append("SessionId", Guid.NewGuid().ToString(), new
CookieOptions() { HttpOnly = false });
await next.Invoke(env);
})));
}
}
Startup.WebSockets.cs
public
partial
class
Startup
{
public
void ConfigureWebSocketListeners(SimpleWebSocketHandler _webSocket)
{
ListenForSystemMessages(_webSocket);
}
private
void ListenForSystemMessages(SimpleWebSocketHandler webSocket)
{
// Here you can register any event that should send the messages to users.
// It can be for example ServiceBus.OnMessage or anything else.
// queueClient.OnMessage(receivedMessage =>
// {
// var msg = JsonConvert.DeserializeObject<SimpleMessage>(receivedMessage.GetBody<string>());
// webSocket.SendMessage(msg);
//
// queueClient.Complete(receivedMessage.LockToken);
// });
}
}
Note that I am injecting my SimpleWebSocketHandler over Unity Container that we configured in step above.
Implementing your Web API Controller
So now what we need to do is the register one WebAPI route/method ("GetMessages") that will be used as our WebSocket connection endpoint and will be open for whole user session.
[RoutePrefix("api/messages")]
public
class
MessageController : ApiController
{
private
readonly
SimpleWebSocketHandler _webSocketHandler;
public FrontendSystemMessageController(SimpleWebSocketHandler webSocketHandler)
{
_webSocketHandler = webSocketHandler;
}
[HttpGet]
[Route("GetMessages")]
public
async
Task<HttpResponseMessage> Messages()
{
var currentContext = HttpContext.Current;
return
await
Task.Run(() =>
{
if (currentContext.IsWebSocketRequest || currentContext.IsWebSocketRequestUpgrading)
{
currentContext.AcceptWebSocketRequest(ProcessWebSocketRequest);
return Request.CreateResponse(HttpStatusCode.SwitchingProtocols);
}
return Request.CreateResponse(HttpStatusCode.BadRequest);
});
}
private
async
Task ProcessWebSocketRequest(AspNetWebSocketContext context)
{
var sessionCookie = context.Cookies["SessionId"];
if (sessionCookie != null)
{
await _webSocketHandler.ProcessWebSocketRequestAsync(context);
}
}
}
Note that I am injecting my SimpleWebSocketHandler over Unity Container that we configured in step above.
AngularJS "WebSocketPoller" Factory
For this purpose I wrote a simple AngularJS factory that creates a WebSocket connection upon the app start and raises the event on every message that has been received.
'use strict';
app.factory('webSocketPoller', function() {
var listener;
var errorHandler;
var address;
return
function(options) {
var Service = {};
if (options && options.address) {
address = options.address;
}
if (options && options.listener) {
listener = options.listener;
}
if (options && options.errorHandler) {
errorHandler = options.errorHandler;
}
Service.init = function()
{
var ws = new WebSocket(address);
ws.onmessage = function(message) {
//Do whatever you need with
//SimpleMessage Object:
//JSON.parse(message.data);
};
ws.onerror = errorHandler;
}
return Service
}
});
Now you can find a right AngularJS Controller or use app.run() to add following code that initializes the Puller and attaches to OnMessage and OnError so you can handle your messages on UI:
var wsPoller = new webSocketPoller({
address: 'ws://localhost/api/messages/GetMessages',
listener: onNewSystemMessageFunction,
errorHandler: function (error) { console.error(error); }
})
wsPoller.init();
Posted
Dec 06 2016, 12:17 PM
by
Armin Kalajdzija