SignalR på .NET Core

SignalR er en abstraksjon som gjør det mulig å enkelt lage løsninger som er koplet til en server med en fast forbindelse og kunne deretter få meldinger fra serveren om noe oppstår. Inkludert i abstraksjonen finnes det en server komponent og klient komponenter for blant annet Web, .NET klienter og mobile klienter som iOS, Android, Xamarin og mer. Klientene er laget på en måte som gjør at de automatisk finner ut hvilken transport-teknikk som fungerer best. Dette er blant annet WebSockets, long-polling HTTP, EventSource m.fl. Når man åpner en forbindelse fra klienten vil biblioteket finne den transporten som er best egnet fra et klient og server ståsted. Alt dette er sømløst gjemt bort.

SignalR har allerede eksistert i noen år, men er nå i en omskrivingsfase hvor den blir ytterligere rafinert og forbedret og ikke minst støtter .NET Core og vil med dette være også få cross-platform støtte på Linux og Mac i tillegg til Windows. Når .NET Core ble releaset i sommer, var ikke SignalR klart - dette er noe som kommer til release senere. Har man derfor eksisterende løsninger som benytter seg av dette og man ønsker å ta i bruk SignalR har dette frem til ganske nylig vært umulig - med mindre man ønsket å bare støtte Windows. Dette på grunn av at tidligere versjoner av SignalR på .NET Core hadde en avhengighet til Windows. I den tidlige utgaven som nå er tilgjengelig er dette blitt fikset og den kjører fint på kryss av alle plattformer.

DotNet new

La oss starte helt fra bunn, lag en folder som heter SignalR (eller hva du vil egentlig :)) og åpne opp en commandline og naviger deg til folderen og skriv inn:


Dette forutsetter at du har installert DotNet Core.

Åpne din favoritteditor, f.eks. Visual Studio Code (code .).

Du skal nå ha et prosjekt bestående av en project.json fil og en Program.cs fil. For å få tilgang til de tidlige utgavene av SignalR må vi legge til en NuGet feed. NuGet støtter å ha flere lokasjoner hvor den kan søke etter pakker. Dette kan være veldig praktisk hvis man ønsker å ta i bruk continuous integration versjoner av biblioteker eller feeds man eksponerer for interne biblioteker. I samme katalog som de andre filene, legg til en fil som heter NuGet.Config og legg til følgende.
Dette gir oss tilgang på ASP.NET sine CI bygg.

Nå kan vi legge til de nødvendige pakkene. Åpne project.json filen. I dependencies under Microsoft.NETCore.App avhengigheten, legger man til følgende:

For å få disse pakkene må man kjøre en “dotnet restore”. Har man Visual Studio Code

Det vi drar inn her er muligheten til å føre loggingen til console, kestrel serveren, muligheten for statiske filer, SignalR server siden og sist men ikke minst støtte for WebSockets.

I .NET Core er alle applikasjoner i utgangspunktet en Console applikasjon. Og Program.cs som kommer ut fra “dotnet new” inneholder da selve “Main”. Vi lager en enkel self-hosted løsning ved å bygge en host via WebHostBuilder:

I koden ser man .UserStartup() , denne klassen må vi lage. For enkelhetens skyld kan man plassere den rett over selve Main metoden. Jeg opplever på macOS ihvertfall, at det er problemer med den automatiske konvensjonen som sier at /wwwroot er rotfolderen for statisk innhold, derfor har jeg lagt inn .UseContentRoot() .

startup

Det Startup klassen gjør er å konfigurere opp alle tjenestene for SignalR slik at den klarer å finne alle avhengighetene til alle implementasjoner under laging av objekter internt. Her har jeg også valgt å slå på detaljerte feil rapporter som vil komme i konsollet. I tillegg i Configure() metoden legger jeg da også til Console som en logger output. Vi ønsker en statisk fil, vår HTML fil - dette får vi støtte for ved å legge til et middleware for håndtering av dette; .UseStaticFiles() . Tilsvarende utfordring i forhold til default oppførsel med /wwwroot, setter jeg derfor opp en FileProvider som håndterer dette. Sist er da .UseSignalR() som setter opp selve SignalR og deriblant en route som heter /signalr/js som gir blant annet proxy genererte utgaver av C# kode vi ønsker å kalle fra JavaScript i browseren. Det finnes et CLI verktøy for å gjøre dette, hvis man ikke ønsker å gjøre det runtime også.

EDIT: Legg merke til app.UseWebSockets(), dette gjør at SignalR kan benytte seg av WebSockets som en transport.

Det siste man da trenger er følgende using statements på toppen:

The Hub

SignalR har 2 måter å kople en klient til; PersistentConnection og Hub. I denne posten hopper jeg over PersistentConnection. Jeg personlig syns Hub er en mer interessant abstraksjon i forhold til det man som regel ønsker å oppnå; APIer eksponert til klient.

La oss lage en ny C# fil som heter ChatHub:

Denne filen er veldig enkel. Den eksponerer en metode som kan benyttes direkte fra klienten. Implementasjonen gjør en enkel broadcast til alle klienter som er koplet til og videresender meldingen som kommer inn.

Det vi nå trenger er en klient-del. Lag en folder i prosjektet som heter wwwroot, her skal vi ha en fil som heter index.html. Den skal se ut som følger:

For all enkelhetens skyld har jeg valgt å ikke dra inn noe rammeverk eller biblioteker som Angular, Aurelia, React e.l., ettersom dette ikke er målet her. jQuery er med fordi foreløpig har Web-klient støtten i SignalR en avhengighet til jQuery. Dette er under omskriving og vil falle bort i fremtiden. Siden består av et textfelt for meldinger, en input for skriving av meldinger og en knapp. På jQuery objektet finner vi property som heter connection, dette er start-punktet for alt SignalR. Siden vi har med en script referanse /signalr/js, får vi også en klient representant av ChatHub som vi lagde i C#. Denne befinner seg da på connection objektet. En hub på klienten har 2 ting man kan jobbe med; client og server. På client objektet setter man opp alle funksjoner som server siden kaller på, mens på server objektet ligger alle metodene fra server siden som da er proxy generert. Løsningen over sender enkelt ned til serveren en melding når brukeren trykker på Send knappen og får en callback på “messageReceived” når serveren kaller på denne.

Nå kan vi kjøre opp løsningen ved enten å gjøre en “dotnet run” i CLI i folderen til prosjektet, eller sette opp VSCode til å kjøre via F5 - eller om du er i vanlig Visual Studio og alt er satt opp for å kjøre via F5. Web serveren er eksponert default på port 5000, så da er det bare å åpne nettleseren og gå til https://localhost:5000/index.html.

Alt skal nå fungere - åpne gjerne to nettlesere og se at den kommuniserer på tvers.

Konklusjon

Dette er et veldig enkelt eksempel, og kanskje litt vel typisk eksempel for denne typen teknologi. Men det betyr ikke at SignalR kan brukes til mange andre ting. SignalR er svært robust og håndterer store mengder tilkoplinger. Det har også blitt bygget støtte for fler-server scenario med bruk av message backplanes. Her støttes det en hel del løsninger med blant annet Redis o.l. En av de viktigste grunnene til å benytte seg av SignalR og lignende teknologier er å gi brukerne en bedre opplevelse. Når noe skjer på serveren har man muligheten til å varsle brukerne som er koplet til. Dette kan være data som er relevant for den ene brukeren til en bestemt gruppe eller til alle som er koplet til, dette er ting man selv filtrerer via Clients objektet på en Hub f.eks.

Jeg har skrevet to bøker om SignalR, den ene tar for seg en Chat applikasjon og eksponerer de grunnleggende mulighetene. “Real-Time Application Development” kan være en grei bok for å komme i gang. Mens den andre boken jeg har skrevet er mer for å gi innblikk i ulike applikasjoner man kan benytte og bruker SignalR sammen med andre rammeverk også; “SignalR Blueprints.