dinsdag 16 juli 2013

SignalR in ASP.NET.

Websites worden steeds moderner. Waar we het vroeger (begin jaren 90) nog moesten doen met een homepage, waarop wat persoonlijke informatie stond en een rubriekje "Mijn favoriete links" (Het enige interessante op die pagina's omdat search engines niet of nauwelijks bestonden), worden nu complete applicaties via de webbrowser aangeboden. Deze ontwikkeling ging bepaald niet over rozen. De webarchitectuur was zo gebouwd dat alle initiatief bij de client (webbrowser) lag. De client deed de requests en de server (webserver) reageerde alleen op die requests. Het was uit den boze dat een webserver op eigen houtje contact ging opnemen met een webbrowser (waarschijnlijk zou je continu verveeld worden met commerciƫle websites waar je niet om gevraagd hebt). Toch kan het voor applicaties belangrijk zijn dat zij op de hoogte worden gehouden van de laatste wijzigingen. Denk bijvoorbeeld aan een applicatie die de aandelenkoersen laat zien of een nieuwsapplicatie die steeds het laatste nieuws wil laten zien (real-time informatie). Met de huidige webarchitectuur is dat eigenlijk niet mogelijk. Gelukkig zijn er slimme mensen die hierop iets bedacht hebben. Hier volgen een paar technieken:

  1. Polling. De browser doet herhaaldelijk, na een x aantal seconden, een request naar de webserver. Dit kan de webbrowser zelf doen of je gebruikt het XmlHttpRequest object (een onzichtbaar minibrowsertje in de webbrowser, het kloppende hart achter de Ajax technologie). Hierbij is alleen de kans groot dat er requests naar een webserver worden gestuurd, terwijl de webserver helemaal geen wijzigingen te bieden heeft.
  2. Pushlets. Met deze techiek wordt de webbrowser eigenlijk aan het lijntje gehouden. De browser doet een request naar de webserver die een response stuurt, maar de webserver zal de response nooit afsluiten. De webbrowser blijft dus continu laden. De webserver kan nu data naar de webbrowser sturen wanneer het hem uitkomt. Nadeel is de gebrekkige controle over de verbinding en je hebt de webbrowser beroofd van een verbinding hetgeen significante vertragingen kan opleveren omdat webbrowsers steeds maar twee verbindingen tegelijk naar de webserver open mogen hebben.
  3. Long Polling. De webbrowser stuurt een request naar de webserver, maar die reageert pas als hij nieuwe data heeft (opzettelijk laat reageren). Dit pakketje wordt dan naar de webbrowser gestuurd waarna het hele spel opnieuw begint.
  4. WebSockets. Nieuw in html5. Met WebSockets worden alle hierboven genoemde kunstgrepen naar de eeuwige internetvelden verwezen. Met WebSockets kan een duplexverbinding tussen webbrowser en webserver worden opgezet. Als die duplexverbinding eenmaal is opgezet, kan de webserver op ieder moment data naar de webbrowser sturen en visa versa. Nadeel is wel dat je een moderne browser moet hebben die WebSockets ondersteunt (Zie caniuse voor ondersteuning)
WebSockets is met afstand de meest gewenste oplossing, maar je zult maken krijgen met webbrowsers die WebSockets niet ondersteunen. Voor die clients zul je de oudere technieken moeten toepassen. Hier is SignalR nu heel handig in.


SignalR (R = Realtime) is een toolkit waarmee men real-time webapplicaties kan maken. Het is op zich niet nieuw. SignalR was al een tijdje via Nuget te krijgen en met de Visual Studio 2012.2 update waren ook de templates beschikbaar. De kracht van SignalR zit hem in het gegeven dat het helemaal gebouwd is om snelheid en support. In de basis zal SignalR gebruik maken van WebSockets, maar als die niet voorhanden zijn, zal SignalR moeiteloos omschakelen naar Comet technieken, zoals pushlets en long polling.

Hoogste tijd om SignalR eens in de praktijk te testen. Hiervoor heb ik in Visual Studio 2013 Preview eerst een leeg MVC-project aangemaakt. De volgende stap is een SignalR Hub class toevoegen aan het project. Dit kan het best via het contextmenu/Add New Item (afbeelding 1). 

Afbeelding 1. De SignalR template.

De template zal ook de nodige referenties leggen en wat Javascripts toevoegen. De (gemodificeerde) genereerde class zie je hieronder.

public class MySignalRHub : Hub
{
    public void Verstuur(string message)
    {
        Clients.All.straalUit(message);
    }
}
MySignalHub is een class die erft van de class Hub, een class uit de SignalR bibliotheek. De Hub class is eigenlijk de service waar alle commando's binnenkomen. De Verstuur methode bevat een dynamische aanroep (straalUit) op de SignalR property Clients.All. Het aparte van deze methode is dat hij niet bestaat. Je kunt hem dus noemen zoals je wilt en argumenten specificeren die je wilt. Geloof het of niet, de service is nu gereed. Nu moeten we alleen nog een url voor de service specificeren. Dat doe je in de Global.asax, in de Application_Start methode via de extensie MapHubs die standaard de route "/signalr" aanmaakt.

protected void Application_Start()
{
    RouteTable.Routes.MapHubs();

    AreaRegistration.RegisterAllAreas();
    RouteConfig.RegisterRoutes(RouteTable.Routes);
}
Aan de servicekant ben je nu helemaal klaar. Helaas bleek in de preview van Visual Studio 2013 de extensie MapHubs onvindbaar. Na het verwijderen van SignalR en opnieuw toevoegen in Nuget, bleek dat probleem opgelost.

Het clientgedeelte is niet veel lastiger. Je voegt eerst de volgende javascripts toe

<script src="~/Scripts/jquery-1.6.4.min.js"></script>
<script src="~/Scripts/jquery.signalR-1.1.2.min.js"></script>
<script src="/signalr/hubs"></script>
De laatste referentie naar "/signalr/hubs" zal een javascript opleveren dat een aantal handige objecten bevat. Die objecten zijn gegenereerd op basis van jouw Hub class en verdient daardoor dan ook de naam Hub-proxy. Hieronder staat de rest van de client implementatie waarbij #message een textbox is, #send een button en #values een leeg <ul> element.

<script type="text/javascript">
    $(function ()
    {
        var proxy = $.connection.mySignalRHub;
        proxy.client.straalUit = function (message)
        {
            $('<li/>').appendTo("#values").text(message);
        };

        $.connection.hub.start().done(function ()
        {
            $('#send').click(function ()
            {
                proxy.server.verstuur($('#message').val());
                $('#message').val('');
            });
        });
    });
</script>
De variabele "proxy" is een instantie van de gegenereerde Hub-proxy mySignalRHub (die niet geheel toevallig ook de naam van de Hub draagt). Merk op dat de methodenamen uit de Hub-class ook netjes zijn overgenomen. Voor de client is dit alles wat je hoeft te doen. 

SignalR is een prachtig tool om real-time webapplicaties te bouwen. Het neemt een heleboel complexiteiten uit handen. Door te erfen van de Hub-class, kunnen we vrij eenvoudig services maken die, naast het onderhouden van de verbindingen, ook nog eens de aangemelde webbrowsers bijhoudt. Als alternatief voor de Hub-class kun je ook erfen van de PersistantConnection class, die basaler is dan de Hub class, maar wel meer controle geeft.
Wanneer het op schaalbaarheid aankomt, laat SignalR zich niet onbetuigd. Met evenveel gemak kan SignalR inhaken op Windows Azure Service Bus (bijvoorbeeld via Topic/Subscription) waardoor het geheel ook in Windows Azure goed draait (hoe dat moet, zie je in deze deze blog).

Ik kan niet anders zeggen dat de makers van SignalR een erg mooi product hebben afgeleverd. Ooit heb ik een multi-user game geschreven op basis van WebSockets. Als ik toen van SignalR had geweten, had mij dat veel tijd en moeite gescheeld.

Meer informatie over SignalR:

Geen opmerkingen:

Een reactie posten