Dart/Javascript Interop - Creating a SignalR client wrapper in Dart
I've recently been exploring the idea of using Dart for the client side of my next web project as I think the language lends itself to a more maintainable code base at larger sizes copared to JavaScript. However, due to the large number of frameworks JavaScript already has at it's disposal, it would be silly to not try to leverage those frameworks. One in particular, is the SignalR JS client. I really like SignalR (opens new window) as a technology, it's easy to use and provides great real-time support to your .NET web application. However, the idea of writing my own Dart client (opens new window) from scratch to support two way communication with a SignalR server seems like it would be quite a large effort. So I decided to see how hard would it be to just create a Dart wrapper around the existing JS library.
# Example server code
In this example, we should first go over what is on the server. I've built my client separate from the server so I needed to enable CORS support. Strangely, this support in SignalR is in an entirely separate NuGet package, which in my mind, seems a little odd. Make sure you have the installed the SignalR server and CORS NuGet package onto your server project.
Install-Package Microsoft.AspNet.SignalR
Install-Package Microsoft.Owin.Cors
**
**
To setup SignalR, you need a Startup class with a [assembly:..] reference to it.
using Microsoft.Owin;
using Microsoft.Owin.Cors;
using Owin;
[assembly: OwinStartup(typeof(SignalDart.Server.Startup))]
namespace SignalDart.Server
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseCors(CorsOptions.AllowAll);
app.MapSignalR();
}
}
}
You will also need to register a Hub.
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
namespace SignalDart.Server
{
[HubName("testHub")]
public class TestHub : Hub
{
public void Send(string name, string message)
{
Clients.All.broadcastMessage(name, message);
}
}
}
# Example in JavaScript
Now, the JS code to access this hub (without JS code gen) is pretty simple and looks like the following snippet.
var connection = $.hubConnection();
var testHubProxy = connection.createHubProxy('testHub');
testHubProxy.on('broadcastMessage', function (name, message) {
console.log(name + " - " + message);
});
connection.start().done(function() {
$('#sendMessage').click(function() {
testHubProxy.invoke('Send', 'Javascript', $('#chatInput').val());
});
});
This is assuming your page is hosted on the same server, SignalR defaults urls, something we are not going to do for our Dart client.
# Example in Dart
I wrote the wrapper to try to keep the implementation code as similar to the JavaScript equivalent as possible.
void main() {
HubConnection connection = new HubConnection(url:'http://localhost:55780/signalr');
HubProxy testHubProxy = hc.createHubProxy('testHub');
testHubProxy.on('broadcastMessage', (String name,String message) {
print(name + ' - ' + message);
});
connection.start((JsObject response) {
document.querySelector('#sendMessage').onClick.listen((mouseEvent) {
testHubProxy.invoke('Send',['Dart',(document.querySelector('#chatInput') as InputElement).value]);
});
});
}
# JavaScript Interop
The enable the code above, I wrote a small Dart package (opens new window) to wraps the connection and proxy objects exposed in the JavaScript library that SignalR provides. So this code still requires that the SignalR JavaScript client is included in the page as well as jQuery.
Wrapping an existing JS library is pretty straight forward, though debugging can be difficult at times, I think the Dart team has done a pretty darn good job with it. Here is all the code that enables the HubConnection and HubProxy classes.
part of signal_dart;
class HubConnection {
JsObject hubConnection;
HubConnection({String url: null}) {
hubConnection = context['\$'].callMethod('hubConnection');
if(url != null) {
hubConnection['url'] = url;
hubConnection['useDefaultPath'] = false;
}
}
createHubProxy(String hubName) {
return new HubProxy(this,hubName);
}
start(Function callback) {
JsObject startConnectionResult = hubConnection.callMethod('start');
startConnectionResult.callMethod('done',[callback]);
}
}
part of signal_dart;
class HubProxy {
invoke(String methodName, List<Object> parameters) {
List<Object> args = new List<Object>();
args.add(methodName);
args.addAll(parameters);
hubProxy.callMethod('invoke',args);
}
HubConnection hubConnection;
JsObject hubProxy;
HubProxy(this.hubConnection,String hubName) {
hubProxy = hubConnection.hubConnection.callMethod('createHubProxy',[hubName]);
}
on(String methodName,Function callback) {
hubProxy.callMethod('on',[methodName,callback]);
}
}
Obviously this doesn't wrap everything in the JS lib, but enough to get two-way communication going for a lot of situations and as you can see above, is pretty straight forward.
# Conclusion
Although that Google hasn't announced when the DartVM will be embedded in Chrome, it probably isn't far off. It has been said that JavaScript is the assembly language of the web, and for asm.js, I think that analogy is pretty damn close. After writing some JS interop, it really reminded me more of a less painful COM interop from .NET. All in all, I think people are going to want to leverage this feature a more going into the future. I still think Dart is moving in the right direction, more so than TypeScript and others. And as Dart usage continues to grow into Servers, it's going to make more sense to use it more for clients as the DartVM grows in support.