WebSockets with ASP.NET Core and Xamarin Forms
WebSockets provide near-instant, two-way communication between servers and client apps. Messaging apps, multiplayer games and other fun things all leverage websockets to deliver and receive data quickly over a persisted, two-way connection with the server.
Essentially, any use case involving communication between users or real-time data can likely make use of websockets.
In this post we're going to build a crude chat app using websockets in a Xamarin Forms app with a backend powered by ASP.NET Core. Our implementation won't exactly be production worthy but it will provide you with the basic building blocks to get websockets working with Xamarin Forms and ASP.NET Core.
My setup
I'm using Visual Studio Professional 2015, Update 3 on Windows 10.
Starting with the backend
Before we build our mobile app, we need a server to manage things. Fire up Visual Studio and let's get started.
Create a new Web project and select the ASP.NET Core Web Application template.
Next, we'll choose Web Application - this will allow us to quickly add the websockets middleware and a simple page to act as one of our chat clients. If we were strictly building a backend api we could just as easily pick Web API at this point to provide websocket support for client apps living elsewhere.
Get notified on new posts
Straight from me, no spam, no bullshit. Frequent, helpful, email-only content.
The first step in enabling websockets in our server application is adding the applicable package from nuget. To do so, open the package manager console and install it.
PM> install-package Microsoft.AspNetCore.WebSockets.Server
Handling connections
Now we're ready to add some code that will actually handle new websocket connections and route data to clients. Because of ASP.NET Core's super-configurable middleware and the fact we're keeping things small and simple this code could very well be inlined in Startup.cs along with the rest of our bootstrapping stuff. But, in the name of good practice let's abstract into its own file and class we'll call WebSocketMiddleware.cs
The only interesting bit of code in this class lies in the Invoke() method. This is the point where we accept new websocket connections and receive data. Our server is so simple that currently it just echos back the received message to all connected sockets. You can see that this could be extended to route messages to specific users by storing a reference to each connection with an identifier and adding some additional code to only send messages to ids of specific clients.
public async Task Invoke(HttpContext httpContext) {
if (httpContext.WebSockets.IsWebSocketRequest) {
var socket = await httpContext.WebSockets.AcceptWebSocketAsync();
WebSockets.Add(socket);
while (socket.State == WebSocketState.Open) {
var token = CancellationToken.None;
var buffer = new ArraySegment<byte>(new byte[4096]);
var received = await socket.ReceiveAsync(buffer, token);
switch (received.MessageType) {
case WebSocketMessageType.Close:
// nothing to do for now...
break;
case WebSocketMessageType.Text:
var incoming = Encoding.UTF8.GetString(buffer.Array, buffer.Offset, buffer.Count);
// get rid of trailing crap from buffer
incoming = incoming.Replace("\0", "");
var data = Encoding.UTF8.GetBytes("data from server :" + DateTime.Now.ToLocalTime() + " " + incoming);
buffer = new ArraySegment<byte>(data);
// send to all open sockets
await Task.WhenAll(WebSockets.Where(s => s.State == WebSocketState.Open)
.Select(s => s.SendAsync(buffer, WebSocketMessageType.Text, true, token)));
break;
}
}
} else {
await _next.Invoke(httpContext);
}
}
}
Now that our websocket middleware class is created we need to register it as part of our request pipeline in Startup.cs. This couldn't be easier, in the Configure() method we add 2 lines and we're done - our websocket enabled server is ready to go!
... app.UseWebSockets(); app.UseWebSocketHandler(); ...
A simple web client
To make our project somewhat interesting we'd like to be able to send data from a web client to our mobile app and vice versa. The implementation of our web client is super simple, in fact - because we chose to create a web application we're 75% of the way there already.
On the Index\Home view I've added a few elements and some javascript to create a new websocket connection on page load by specifying my server url prefixed with the ws protocol. After that, there's some handlers for the events we need to respond to when something interesting happens with our connection.
- OnOpen
- OnMessage
- OnError
- OnClose
$().ready(function() {
webSocket = new WebSocket("ws://localhost:53273/");
webSocket.onopen = function() {
$("#status").text("connected");
};
webSocket.onmessage = function(evt) {
$("#data").append("" + evt.data + "
");
};
webSocket.onerror = function(evt) {
alert(evt.message);
};
webSocket.onclose = function() {
$("#status").text("disconnected");
};
$("#sendMessage").click(function() {
if (webSocket.readyState == WebSocket.OPEN) {
webSocket.send($("#message").val());
} else {
$("status").text("Connection is closed");
}
});
});
At this point if we run our project we can send messages to the server and see them echoed back on our page - cool stuff.
The xamarin forms app
With our server and web client online, let's create a new xamarin forms portable project. Note that I partitioned my solution into separate web and mobile folders so my XF project lives in the mobile folder.
Websocket implementation
I'm going to use Nicholas Ventimiglia's Websockets.PCL library to add websockets to my project. This saves us a lot of time as we avoid having to muck with the native implementation. For the purpose of this article I'm focused on Android so I only installed the package in my PCL and Droid projects.
Install-Package Websockets.Pcl
Next, I added some code to MainPage.cs that can open a connection and send a message - too easy.
public partial class MainPage
{
private bool _echo,_failed;
private readonly IWebSocketConnection _connection;
public MainPage()
{
InitializeComponent();
// Get a websocket from your PCL library via the factory
_connection = WebSocketFactory.Create();
_connection.OnOpened += Connection_OnOpened;
_connection.OnMessage += Connection_OnMessage;
_connection.OnClosed += Connection_OnClosed;
_connection.OnError += Connection_OnError;
}
protected override async void OnAppearing()
{
base.OnAppearing();
_echo = _failed = false;
_connection.Open("ws://169.254.80.80:53273/");
while (!_connection.IsOpen && !_failed)
{
await Task.Delay(10);
}
Message.Focus();
}
private static void Connection_OnClosed()
{
Debug.WriteLine("Closed !");
}
private void Connection_OnError(string obj)
{
_failed = true;
Debug.WriteLine("ERROR " + obj);
}
private void Connection_OnOpened()
{
Debug.WriteLine("Opened !");
}
private void Connection_OnMessage(string obj)
{
_echo = true;
Device.BeginInvokeOnMainThread(() =>
{
ReceivedData.Children.Add(new Label { Text = obj });
});
}
private async void BtnSend_OnClicked(object sender, EventArgs e)
{
_echo = false;
_connection.Send(Message.Text);
Message.Text = "";
while (!_echo && !_failed)
{
await Task.Delay(10);
}
Message.Focus();
}
}
Getting the android emulator talking to our web server
With our mobile client complete we're almost ready to test things out from end to end.
I'm using the VS Android emulator running in a Hyper-v VM. To get the emulator connected to the web server I first had to poke a hole in my firewall (in my case port: 53273). Secondly, in the mobile client code you'll notice we're using 169.254.80.80 as the ip of our server. This is the localhost loopback so I had to add a binding for that ip to .vs/applicationhost.config in the XFWebSocketServer project.
Finishing up
With my network changes in place I fired up both projects and was able to send and receive messages from both clients - success!
Our final product isn't much to look at on the surface but hopefully the steps we took to get here will give you a head start in building out something way cooler with websockets in xamarin forms and ASP.NET Core.
If you have any feedback I'd love to hear it in the comments below.
References
Get notified on new posts
Straight from me, no spam, no bullshit. Frequent, helpful, email-only content.
Get notified on new posts
Straight from me, no spam, no bullshit. Frequent, helpful, email-only content.