Introduction
If you ever have to deal with asynchronous data streams, you probably have used or at least heard of ReactiveX, a library for reactive programming which offers powerful APIs to transform data streams into observable sequences that can be subscribed to and acted upon. But what sets it apart from the regular event-driven approach is the capability to compose new data streams out of multiple other observable sequences, which you can combine, filter, or transform with great flexibility.
I will demonstrate how you can leverage this and my own library dotNetify-React to make an asynchronous real-time web application fairly trivial to implement. Here is the output of what we will be building:
It is a live chart on a web browser, fed by an asynchronous data stream that is itself composed of two different data streams; one for the sine wave signal and the other its amplitude.
The following steps use the create-react-app boilerplate from Facebook to generate the web app, and .NET Core SDK to run the back-end. You will need to install them first.
If you just want to pull the source code, go to Github dotnetify-react-demo. There is also a version that runs on Visual Studio 2017 at dotnetify-react-demo-vs2017.
Front-End
We will start by creating the app shell and installing the required libraries:
create-react-app livechart
cd livechart
npm install dotnetify --save
npm install chart.js@1.1.1 --save
npm install react-chartjs@0.8.0 --save
npm install concurrently --save-dev
(I keep to an older version of the chart library because the APIs have changed since and I‘m not familiar with them yet.)
Add the component /src/LiveChart.jsx that will render the chart:
import React from 'react';
import dotnetify from 'dotnetify';
import { Bar } from 'react-chartjs';
export default class LiveChart extends React.Component {
constructor(props) {
super(props);
dotnetify.react.connect("LiveChart", this);
this.state = {};
this.chartData = {
labels: Array(10).fill(""),
datasets: [{
data: Array(10),
fillColor: 'rgba(75, 192, 192, 0.2)',
strokeColor: 'rgba(75, 192, 192, 1)'
}]
};
this.chartOptions = {
responsive: true,
scaleOverride: true,
scaleSteps: 5,
scaleStepWidth: 10
};
this.updateChart = value => {
this.chartData.datasets[0].data.shift();
this.chartData.datasets[0].data.push(value);
};
}
render() {
return (
<Bar data={this.chartData} options={this.chartOptions}>
{this.updateChart(this.state.NextValue)}
</Bar>
);
}
}
This component will initially render a bar chart component from react-chartjs with an empty data set. As soon as the connection to the back-end through dotNetify occurs, the component will be receiving real-time update to this.state.NextValue
, which in turn causes the chart to re-render with the new data set value.
Next, replace the default /src/App.js to render our component:
import React, { Component } from 'react';
import LiveChart from './LiveChart';
export default class App extends Component {
render() {
return <LiveChart />
}
}
Back-End
With the front-end in place, we now add the .NET Core back-end piece. Start by creating a default ASP.NET Core web project and installing the required packages:
dotnet new web
dotnet add package DotNetify.SignalR --version 2.1.0-pre
dotnet add package System.Reactive
dotnet restore
Open package.json and add the following line to redirect requests that are unhandled by the Node dev server to the .NET Core server:
"proxy": "http://localhost:5000/",
Still in package.json, modify the line that calls the react-scripts
to use the concurrently
library to start both Node and .NET Core server:
"start": "concurrently \"react-scripts start\"
\"dotnet run\" --kill-others",
Next, add the class LiveChart.cs that will provide the real-time update to the front-end component:
using System;
using System.Reactive.Linq;
namespace livechart
{
public class LiveChart : DotNetify.BaseVM
{
private IDisposable _subscription;
public int NextValue { get; set; }
public LiveChart()
{
var sine = Observable
.Interval(TimeSpan.FromMilliseconds(100))
.Select(t => Math.Sin(2 * Math.PI * .06 * t));
var amp = Observable
.Interval(TimeSpan.FromMilliseconds(100))
.Select(a => a % 50 + 1);
_subscription = Observable
.Zip(sine, amp, (s, a) => (int) Math.Abs( s * a))
.Subscribe(value =>
{
NextValue = value;
Changed(nameof(NextValue));
PushUpdates();
});
}
public override void Dispose() => _subscription.Dispose();
}
}
The idea behind this class is to produce a data stream that’s composed of two other streams: one for the sine wave signal, the other an iteration of numbers to make a fluctuating amplitude. To create the streams, we use the Rx API Observable.Interval
to emit a sequence of integers in a time interval, which is then further projected into the desired sequence. The two streams are then combined with Observable.Zip
into a single stream, which is subscribed to by our class instance.
When new data becomes available, we use the dotNetify API Changed
and PushUpdates
to send the data to the front-end component to update its local state. The actual communication is done through SignalR, which will use WebSocket
when available. But we don’t have to worry about it, since it’s already abstracted away.
Next, configure dotNetify and SignalR in the Startup.cs:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using DotNetify;
namespace livechart
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMemoryCache();
services.AddSignalR();
services.AddDotNetify();
}
public void Configure(IApplicationBuilder app)
{
app.UseWebSockets();
app.UseSignalR();
app.UseDotNetify();
app.Run(async (context) =>
{
await context.Response.WriteAsync("LiveChart server");
});
}
}
}
Finally, build and run the application:
dotnet build
npm start
Summary
And, that’s it. An asynchronous real-time web app that you can quickly build in minutes. Although somewhat a contrived example, I hope it still serves to illustrate how powerful this technique can be.
In a real-world scenario, the web client could be waiting for multiple back-end microservices whose asynchronous outputs need to be chained together to produce the final result. The usage of ReactiveX and dotNetify combo will significantly reduce the code complexity and save you time and effort.