Even simpler and faster real-time web for ASP.NET Core.
Even simpler and faster real-time web for ASP.NET Core.
SignalW is a simplified version of SignalR, with only WebSockets as a transport
and MemoryStream
as a message type.
RecyclableMemoryStream
that pools internal buffers.Instead of multiple methods inside Hubs that clients invoked by name, in SignalW we have a single method
async Task OnReceiveAsync(MemoryStream payload)
. If one uses Angular2 with @ngrx/store
then
a deserialized-to-JSON message will always have a type
field, and one could use a custom
JsonCreationConverter<IMessage>
to deserialize a message to its correct .NET type. Then one could write multiple methods with the same
name that differ only by its parameter type and use dynamic
keyword to dispatch a message to
a correct handler.
public class DispatchHub : Hub {
public override async Task OnReceiveAsync(MemoryStream payload) {
// Extension method ReadJsonMessage returns IMessage object instance based on the `type` field in JSON
object message = payload.ReadJsonMessage();
// dispose as soon as it is no longer used becasue it uses pooled buffers inside
payload.Dispose();
// dynamic will dispatch to the correct method
dynamic dynMessage = message;
await OnReceiveAsync(dynMessage);
}
public async void OnReceiveAsync(MessageFoo message) {
var stream = message.WriteJson();
await Clients.Group("foo").InvokeAsync(stream);
}
public async void OnReceiveAsync(MessageBar message) {
var stream = message.WriteJson();
await Clients.Group("bar").InvokeAsync(stream);
}
}
On the Angular side, one could simply use a WebSocketSubject
and forward messages to @ngrx/store
directly
if they have a type
field. No dependencies, no custom deserialization, no hassle!
From a C# client
var client = new ClientWebSocket();
var header = new AuthenticationHeaderValue("Bearer", _accessToken);
client.Options.RequestHeaders.Add("Authorization", header.ToString());
From JavaScript:
It is impossible to add headers to WebSocket constructor in JavaScript, but we could use protocol parameters for this. Here we are using RxJS WebSocketSubject:
import { WebSocketSubjectConfig, WebSocketSubject } from 'rxjs/observable/dom/WebSocketSubject';
...
let wsConfig: WebSocketSubjectConfig = {
url: 'wss://example.com/api/signalw/chat',
protocol: [
'access_token',
token
]
};
let ws = new WebSocketSubject<any>(wsConfig);
Then in the very beginning of OWIN pipeline (before any identity middleware) use this trick to populate the correct header:
app.Use((context, next) => {
if (!context.Request.Headers.ContainsKey("Authorization")
&& context.Request.Headers.ContainsKey("Upgrade")) {
if (context.WebSockets.WebSocketRequestedProtocols.Count >= 2) {
var first = context.WebSockets.WebSocketRequestedProtocols[0];
var second = context.WebSockets.WebSocketRequestedProtocols[1];
if (first == "access_token") {
context.Request.Headers.Add("Authorization", "Bearer " + second);
context.Response.Headers.Add("Sec-WebSocket-Protocol", "access_token");
}
}
}
return next();
});
To use SignalW, create a custom Hub:
[Authorize]
public class Chat : Hub {
public override Task OnConnectedAsync() {
if (!Context.User.Identity.IsAuthenticated) {
Context.Connection.Channel.TryComplete();
}
return Task.FromResult(0);
}
public override Task OnDisconnectedAsync() {
return Task.FromResult(0);
}
public override async Task OnReceiveAsync(MemoryStream payload) {
await Clients.All.InvokeAsync(payload);
}
}
Then add SignalW to the OWIN pipeline and map hubs to a path. Here we use SignalR together with MVC on the "/api" path:
public void ConfigureServices(IServiceCollection services) {
...
services.AddSignalW();
...
}
public void Configure(IApplicationBuilder app, ...){
...
app.Map("/api/signalw", signalw => {
signalw.UseSignalW((config) => {
config.MapHub<Chat>("chat", Format.Text);
});
});
app.Map("/api", apiApp => {
apiApp.UseMvc();
});
...
}
Open several pages of https://www.websocket.org/echo.html
and connect to https://[host]/api/signalw/chat?connectionId=[any value]
. Each page should broadcast
messages to every other page and this is a simple chat.