zum Inhalt springen
Kontakt

Bidirektionales Messaging mit SignalR

Architektur einer .NET Applikation

 

Bi-Direktionale Kommunikation

In älteren Webapplikationen kommt das Konzept „Request-Response“zum Einsatz. Dabei wird eine Anfrage (Request) des Webbrowser an einen Webserver, z.B blog.leuchterag.ch geschickt und der Webserver schickt eine Antwort (Response) mit den aktuellen Einträgen zurück. Will man nun wissen, ob neue Einträge vorhanden sind, muss man das Browserfenster aktualisieren. Wäre es nicht toll, wenn eine Meldung ohne Aktion des Benutzers erscheint, wenn ein neuer Eintrag vorhanden ist? Bei Sport-Liveticker wird dies bereits gemacht. Dieses Prinzip nennt sich Bi-direktionale Kommunikation. Also eine Kommunikation in 2 Richtungen. Die erste Richtung ist vom Client zum Server wie bisher. Die zweite Richtung ist vom Server zum Client, das „Push“ Prinzip.

Wie geht das nun in der eigenen Applikation?
Mit ASP.NET SignalR hat Microsoft ein Messaging Framework geschaffen, welches diese Aufgabe übernimmt. Seit 2013 wird die Entwicklung vorangetrieben.

In diesem Beispiel werde ich 2 Konsolen Applikationen erstellen. Eine Dienst als Server und die andere als Client. Der Client hat dann die Möglichkeit Daten an den Server zu schicken und dieser leitet sie an alle anderen verbundenen Clients weiter. Kurz gesagt ein kleiner Chat.

Die Hostapplikation

Erstell dir in Visual Studio eine neue Solution und füge eine neue Konsolenapplikation hinzu.

Als nächstes werden die NuGet Pakete „Microsoft.AspNet.SignalR.SelfHost“ und „Microsoft.Owin.Cors“ im Projekt hinzugefügt.

Danach erstellst du ein neues File für die Startup Klasse. In dieser wird die Host Konfiguration hinterlegt.

internal class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.UseCors(CorsOptions.AllowAll);
            var hubConfiguration = new HubConfiguration();

            hubConfiguration.EnableDetailedErrors = true; 

            app.MapSignalR(hubConfiguration);
        }
    }

Nun ist alles vorhanden, um im Program.cs den SignalR Host zu starten.

static void Main(string[] args)
        {
            string url = "http://localhost:8080"; 

            using (WebApp.Start<Startup>(url))
            {
                Console.WriteLine("Server started on {0}", url);
                
                Console.ReadLine();
            }
        }

Der Server started bei der Ausführung auf dem Port 8080 und ist im eigenen Netzwerk, sofern keine Firewall aktiv ist, erreichbar.

Die Server Applikation steht somit!

 

Der Client

Erstell dir in der Solution eine weitere Konsolenapplikation und installiere die NuGet Packete „Microsoft.AspNet.SignalR.Client“ und „SignalR.Client.TypedHubProxy“

Ersetz die Main Methode mit folgendem Code. Zur „ClientApplication“ Klasse kommen wir dann gleich.

static void Main(string[] args)
    {
       var app = new ClientApplication("http://localhost:8080");
       app.Run();
    }

Erstelle für Klasse ein neues File mit dem Namen „ClientApplication.cs“ und der gleichnamigen Klasse.

Die Applikation erhält die URL vom Server vom Main Programm. Deshalb speichern wir diese im Konstruktor in ein Property.

Desweiteren habe ich den Proxy für die Aufrufe zum Server und die Connection ebenfalls in Properies abgelegt.

private IHubProxy<IServer, IClient> Proxy { get; set; } //Proxy Objekt für die Kommunikation mit dem Server

private HubConnection HubConnection { get; set; } //Die Verbindungsklasse
private string Url { get; set; } //Url zum Server
        
public ClientApplication(string url)
    {
        Url = url;
        SetupClient();
    }

Dazu wird gleich der SignalR Client initialisiert.

protected void SetupClient()
        {
            //erstellen der Connection auf der Default URL von SignalR
            HubConnection = new HubConnection(Url, useDefaultUrl: true); 

            //Verwenden des TypedHubProxy. So kann ich Interface Methoden verwenden.
            Proxy = HubConnection.CreateHubProxy<IServer, IClient>("MyHub"); 

            //Anmelden, wenn über NewMessage oder GetInformation Nachrichten vom Server geschickt werden.
            Proxy.SubscribeOn(hub => hub.GetInformation, (Information info) => Console.WriteLine(info));

            //Connection Starten.
            HubConnection.Start().ContinueWith(task =>
            {
                if (task.IsFaulted)
                {
                    if (task.Exception != null)
                    {
                        Console.WriteLine("There was an error opening the connection:{0}", task.Exception.GetBaseException());
                    }
                }
                else
                {
                    Console.WriteLine("Connected");
                }
            }).Wait();
        }

Nachrichten, welche vom Server kommen, werden auf der Konsole ausgegeben. Die Zeile Proxy.SubscribeOn(..) ist hierbei zu beachten.

Die wohl wichtigste Zeile ist hier. Durch die Angabe 2er Interfaces kann man danach die Methoden des Interfaces mittels Lamda ausdrücken wiederverwenden.

Proxy = HubConnection.CreateHubProxy<IServer, IClient>("MyHub"); 

Die Methode GetInformation kommt aus dem Interface IClient und hat folgende Signatur

void GetInformation(Information info);

Was nun noch fehlt, ist die Möglichkeit etwas von meinem Client zu senden und bei den andere Clients anzeigen zu lassen. Dafür gibt es die Run() Methode in der ClientApplication Klasse.

public void Run()
        {
            while (true)
            {
                string message = Console.ReadLine();

                //Wenn ESC eingegeben wird, soll abgebrochen werden.
                if (message != null && message.ToUpper() == "ESC")
                {
                    break;
                }

                try
                {
                    //Aufruf der ServerMethode NotifyOthersWithInformation
                    Proxy.CallAsync(hub => hub.NotityOthersWithInformation(message)).Wait();
                }
                catch (Exception)
                {
                    Console.WriteLine("ERROR: Could not send message.");
                }
            }
        }

Hier wird immer wieder die Konsole mittels Console.ReadLine() nach neuem Benutzer Input abgefragt.

Dieser Input wird mittels Proxy.CallAsync zum Server gesendet. Die Methode NotifyOthersWithInformation aus dem IServer Interface hat folgende Signatur.

void NotityOthersWithInformation(string info);

Die Magie von SignalR

Wenn du bereits einmal mit Connection Handling auf tiefer Ebene zu tun hatest, weiss du, dass es eine Riesen Qual sein kann. Man muss sich Verbindungen zu Clients speichern, offen halten, wieder verbinden oder auch schliessen. Das erfordert viel Wissen in der Netzwerkprogrammierung und generiert auch einen grossen Aufwand für die Umsetzung. Wer sich diesen Aufwand sparen möchte, benutzt SignalR.

4 Techniken der Netzwerkprogrammierung kommen bei SignalR zum Einsatz.

  • Websockets
  • Server-Sent Events
  • Forever Frame
  • Ajax Long Polling

Durch die Verwendung von SignalR bin ich im Bereich der roten Komponenten. Der ganze Transport und das Connection-Handling wird für mich gemacht.

Der Abschluss ist noch die letzte Zeile Code. Wir haben bereits die Nachricht an den Server gesendet. Wir müssen nur noch die Nachricht an alle anderen Clients weiterversenden.

Dazu erstellen wir im Host Projekt eine neue Klasse namens MyHub. und in dieser haben wir noch folgenden Code:

public class MyHub : Hub<IClient>, IServer
    {
        public void NotityOthersWithInformation(string info)
        {
            //Nachricht an alle Clients ausser dem Sender selber senden. I
            Clients.Others.GetInformation(info);
        }
    }

Die Klasse MyHub wird vom Host im Startup mittels

 app.MapSignalR(hubConfiguration);

erkannt und eingebunden.

 

Fazit

In diesem Beispiel habe ich SignalR als Self-Hosted Applikation in einer Konsole gestartet. Das ganze funktioniert auch auf einem Webserver oder Windows Service.

Clients gibt es für c# und Javascript. Ich habe aber auch schon OpenSource Projekte für C++ gefunden.

Verwenden könnte man SignalR in einer verteilten Applikation, indem man seine Nachrichten auf mobile Geräte oder Desktop Clients versendet. Das Framework ist sehr schnell und einfach in der Anwendung.

Weitere Beispiele findet ihr auf der SignalR Webseite oder mein Beispiel hier: SignalR Source.