SoccerDash – Fußballstatistiken in C# mit REST und JSON

SoccerDashboard - FußballstatistikenIm heutigen Artikel soll es darum gehen, wie man mittels C# eine REST-API abruft und JSON deserialisiert. Hierzu schreiben wir ein kleines Programm in C#, welches Daten von einem Fußball-Statistik-Webservice per API abruft. Wer direkt loslegen will, überspringt den folgenden Prolog. Wer einen stimmigen Einstieg in das Thema sucht, liest ihn.

Prolog

In den letzten beiden Jahren habe ich bezüglich meines Fußballkonsums eine 180-Grad-Wende hingelegt. Habe ich früher, abgesehen vom selbst spielen und den WM-Spielen, so ziemlich alles rund um Fußball gemieden, so schaue ich mir heute jedes Heimspiel der Fortuna in der Esprit-Arena an.

Und wenn einen das Fußball-Fieber erst einmal gepackt hat, dann sind Tipp-Spiele im Freundes- und Kollegenkreis und der Exkurs richtig Sportwetten nicht mehr fern. Doch wenn es um das richtige Tippen geht, scheiden sich die Geister. Von Bauchgefühl bis hin zur “jahrelangen Erfahrung” gibt es diverse Ansätze zur Festlegung der Tipps. Ich, als Informatiker, vertraue da dann doch lieber Zahlen, Fakten und Statistiken, sodass ich kurzerhand Ausschau nach einer kostenlosen Fußballdaten-API gehalten habe, die mir die passende Datengrundlage zur Entscheidungsfindung bereitstellt.

Um die API herum soll im Rahmen dieses Artikels eine kleine C#-Anwendung entstehen, die Statistiken zu einzelnen Mannschaften diverser Ligen anzeigen kann. Wer den Artikel bis zum Ende durcharbeitet, hat danach eine gute Basis, das passende Handwerkszeug und die nötigen Informationen, um weitere Auswertungen wie z.B. Spielstands-Historien verschiedener Gegner zu visualisieren.

Die football-data.org-API

football-data.org API-ÜbersichtBevor wir mit dem eigentlichen Programm anfangen, noch ein paar Worte zu der verwendeten API. Die Programmierschnittstelle von football-data.org ist eine der wenigen APIs, die Daten rund um diverse Fußballligen kostenlos zur Verfügung stellt und nahezu die einzige kostenlose API, deren Datenbestand auch wirklich aktuell ist.

Die Funktionen der API sind relativ übersichtlich und umfassen das Listing aller verfügbaren Ligen, Informationen über die Vereine der Ligen sowie die jeweiligen Spieler der Vereine. Weiter lassen sich einzelne Spielergebnisse bzw. auch alle Spielergebnisse eines Vereins in einer Saison ausgeben.

Zwar ist die API kostenlos und ohne Anmeldung nutzbar, jedoch ist in dieser Variante die Zahl der Zugriffe auf 50 pro Tag begrenzt. Das reicht um einmal reinzuschnuppern, aber nicht um ernsthaft damit zu arbeiten. Deshalb solltet ihr euch, bevor es mit der Programmierung losgeht, einen API-Schlüssel organisieren. Dieser ist ebenfalls kostenlos und innerhalb weniger Minuten registriert. Mittels des Schlüssels wird das API-Limit von 50 Zugriffen/Tag auf 50 Zugriffe/Minute angehoben.

Referenzen und Design

Ist der API-Schlüssel vorhanden, kann es losgehen. Für diesen Artikel öffnen wir das Visual Studio und legen eine C#-WinForms-Anwendung an. (Ich habe die Anwendung SoccerDashboard genannt.)

Um die Arbeit mit der REST-API zu erleichtern, nutzen wir die kostenlose RestSharp-Library. Neben dem eigentlichen Handling der HTTP-Requests verfügt diese zudem über einen integrierten JSON-Deserializer.

Die Installation der Library erfolgt per Nuget. Der Befehl für die Nuget-Konsole lautet:

Install-Package RestSharp

Ist die Bibliothek eingebunden, wechseln wir in den Form-Designer und fügen die Controls hinzu, welche wir für unsere Applikation benötigen. Hierzu nehmen wir zwei ListViews (eines für die Saison-Auswahl, eines für die Mannschaften) und eine RichTextBox in der wir die Informationen zu den Mannschaften anzeigen. Die Controls habe ich für unser Beispiel wie folgt benannt: listViewSeasons, listViewTeams, richTextBoxInformation.

SoccerDashboard - Layout-DesignDie Anordnung der Controls kann dem nebenstehenden Screenshot entnommen werden. Sind die Controls hinzugefügt, markieren wir die beiden ListViews und setzen im Einstellungsdialog folgende Settings:

  • Enabled = False
  • MultiSelect = True
  • View = List

Anschließend legen wir für das Form den Form_Load-Eventhandler und für die ListViews jeweils einen DoubleClick-Eventhandler an. Dies kann entweder über den “Ereignisse”-Dialog oder direkt per Coding geschehen.

Ermitteln der Fußball-Saisons per REST-Abfrage

Bevor wir die Eventhandler ausprogrammieren, fügen wir im Kopf der Form.cs-Datei noch die using-Anweisung für die RestSharp-Bibliothek ein.

using RestSharp;

Danach legen wir direkt im Kopf der Form-Klasse einen string mit dem API-Key an, da wir diesen an verschiedenen Stellen im Coding benötigen werden.

public partial class Form1 : Form
{
    const string apiKey = "29799fc09ba947ae890711*********";

    //[...] weiterer Code...
}

Jetzt geht es an die Ausprogrammierung der Eventhandler. Zu Beginn nehmen wir uns die Form_Load-Methode vor. Nachfolgend seht ihr den gesamten Quelltext der Methode. In den einzelnen Zeilen habe ich dem Code Kommentare angefügt, sodass dessen Funktionsweise verständlich sein sollte. (Wenn dennoch Fragen aufkommen, schreibt mir einen Kommentar unter den Artikel.)

private void Form1_Load(object sender, EventArgs e)
{
    //Initialisierung des RestClients mit der Basis-Url der API
    var rClient = new RestClient("http://api.football-data.org");

    //Erstellung eines Requests auf die Ressource/URI mit den Spielsaison & Ligen
    var rRequest = new RestRequest("alpha/soccerseasons", Method.GET);

    //Hinzufügen des "X-Auth-Token"-Headers um den vollen API-Zugriff zu erlangen
    rRequest.AddHeader("X-Auth-Token", apiKey);

    //Asynchrones abfragen der API mit gleichzeitigem deserialisieren der JSON-Response
    rClient.ExecuteAsync<List<Season>>(rRequest, response =>
    {
        //Iterieren über alle Saesons
        response.Data.ForEach(season =>
        {
            //Erstellung eines neuen ListView-Eintrags mit dem Saison-Titel
            ListViewItem lvi = new ListViewItem(season.caption);
            //Hinterlegung der Mannschaften-URI am ListView-Eintrag
            lvi.Tag = season._links.teams.href;
            //Hinzufügen des ListView-Eintrags
            listViewSeasons.BeginInvoke((Action)delegate { listViewSeasons.Items.Add(lvi); });
        });
        //Aktivierung des ListViews
        listViewSeasons.BeginInvoke((Action)delegate { listViewSeasons.Enabled = true; });
    });
}

In der Form_Load-Methode wird also das erste ListView mit einer Liste, aller über die API verfügbaren Seasons/Ligen, befüllt.

C#-Klassen aus JSON-Objekten erstellen

Wer den obigen Quelltext aufmerksam gelesen hat, wird feststellen, dass bei der REST-Abfrage eine Liste vom Typ Season verwendet wird. Diese Klasse ist jedoch kein Bestandteil des Frameworks, sondern eine Abbildung der Seasons-Datenstruktur der API. Sie wird im Befehl angegeben, damit RestSharp weiß, in welches Format die JSON-Daten deserialisiert werden sollen.

Wer wie ich, ungerne leere Klassen von Hand nach vorgegebener Datenstruktur abtippt, kann z.B. das kostenlose json2csharp-Tool nutzen, welches aus bestehendem JSON-Code die passenden C#-Klassen generiert.

Da wir in diesem Artikel mehrere Funktionen der API-Nutzen, die teilweise gleichnamige JSON-Objekte enthalten, die wiederum verschiedene Inhalte haben und man deshalb dennoch ein wenig Hand anlegen muss, habe ich euch die Klassen nachfolgend einmal kopiert.

/**************************************************
    * Nachfolgend stehen die Klassen, die zur        *
    * Deserialisierung der JSON-Responses nötig sind *
    **************************************************/
public class Self
{
    public string href { get; set; }
}

public class Teams
{
    public string href { get; set; }
}

public class Fixtures
{
    public string href { get; set; }
}

public class LeagueTable
{
    public string href { get; set; }
}

public class Links
{
    public Self self { get; set; }
    public Teams teams { get; set; }
    public Fixtures fixtures { get; set; }
    public LeagueTable leagueTable { get; set; }
}

public class SeasonsList
{
    public List<Season> Seasons { get; set; }
}

public class Season
{
    public Links _links { get; set; }
    public string caption { get; set; }
    public string league { get; set; }
    public string year { get; set; }
    public int numberOfTeams { get; set; }
    public int numberOfGames { get; set; }
    public string lastUpdated { get; set; }
}

public class Link
{
    public string self { get; set; }
    public string soccerseason { get; set; }
}

public class Players
{
    public string href { get; set; }
}

public class LinksTeam
{
    public Self self { get; set; }
    public Fixtures fixtures { get; set; }
    public Players players { get; set; }
}

public class Team
{
    public LinksTeam _links { get; set; }
    public string name { get; set; }
    public string code { get; set; }
    public string shortName { get; set; }
    public string squadMarketValue { get; set; }
    public string crestUrl { get; set; }
}

public class TeamList
{
    public List<Link> _links { get; set; }
    public int count { get; set; }
    public List<Team> teams { get; set; }
}

public class TeamLink
{
    public string href { get; set; }
}

public class Self2
{
    public string href { get; set; }
}

public class Soccerseason
{
    public string href { get; set; }
}

public class HomeTeam
{
    public string href { get; set; }
}

public class AwayTeam
{
    public string href { get; set; }
}

public class Links2
{
    public Self2 self { get; set; }
    public Soccerseason soccerseason { get; set; }
    public HomeTeam homeTeam { get; set; }
    public AwayTeam awayTeam { get; set; }
}

public class Result
{
    public int goalsHomeTeam { get; set; }
    public int goalsAwayTeam { get; set; }
}

public class Fixture
{
    public Links2 _links { get; set; }
    public string date { get; set; }
    public string status { get; set; }
    public int matchday { get; set; }
    public string homeTeamName { get; set; }
    public string awayTeamName { get; set; }
    public Result result { get; set; }
}

public class FixtureList
{
    public TeamLink _links { get; set; }
    public int count { get; set; }
    public List<Fixture> fixtures { get; set; }
}

Fügt diese Klassen am Ende des Namespace-Blocks (aber außerhalb der Form-Klasse) in eurer Form.cs-Datei ein.

Mannschaften einer Saison anzeigen

Im nächsten Schritt nehmen wir uns den DoubleClick-Eventhandler des ersten ListViews vor. Dieser löst aus, wenn ein Doppelklick auf die Liste (idealerweise auf ein Listenelement) erfolgt. Bei Auslösen des Events sollen alle zur Saison zugehörigen Mannschaften in das zweite ListView geladen werden. Der Code hierfür sieht wie folgt aus:

private void listViewSeasons_DoubleClick(object sender, EventArgs e)
{
    //Sicherstellen, dass der Doppelklick auf einen Eintrag getätigt wurde
    if (listViewSeasons.SelectedItems.Count > 0)
    {
        //Auslesen der Mannschaften-URI aus dem Tag des geklickten Eintrags
        var teamUrl = listViewSeasons.SelectedItems[0].Tag.ToString().Replace("http://api.football-data.org/","");

        //Erstellung des Clients, des Requests und des Auth-Headers wie im Load-Event
        var rClient = new RestClient("http://api.football-data.org");
        var rRequest = new RestRequest(teamUrl, Method.GET);
        rRequest.AddHeader("X-Auth-Token", apiKey);

        //Asynchrone Abfrage der Mannschaften der gewählten Saison inkl. Deserialisierung
        rClient.ExecuteAsync<TeamList>(rRequest, response =>
        {
            var teams = response.Data;
            //Leeren des Team-ListViews
            listViewTeams.BeginInvoke((Action)delegate { listViewTeams.Items.Clear(); });

            //Iterieren über alle Mannschaften/Teams
            teams.teams.ForEach(team =>
            {
                //Erstellen eines neuen ListView-Eintrags mit dem Namen der Mannschaft
                ListViewItem lvi = new ListViewItem(team.name);
                //Speichern aller Mannschaftsinformationen am Eintrag
                lvi.Tag = team;
                //Anfügen des Eintrags an das Mannschafts-ListView
                listViewTeams.BeginInvoke((Action)delegate { listViewTeams.Items.Add(lvi); });
            });
            //Aktivieren des Mannschafts-ListViews
            listViewTeams.BeginInvoke((Action)delegate { listViewTeams.Enabled = true; });
        });
    }
}

Statistiken einer Fußballmannschaft anzeigen

Nun haben wir also eine Übersicht aller vorhandenen Saisons und können uns zu jeder Saison/Liga auch die Mannschaften anzeigen lassen. Im nächsten Schritt befüllen wir den DoubleClick-Eventhandler des listViewTeams mit Leben. In diesem ListView werden die verfügbaren Mannschaften angezeigt. Macht der Nutzer einen Doppelklick auf eine Mannschaft, sollen Informationen zur Mannschaft in der RichTextBox angezeigt werden. Nachfolgend der Code inklusive erläuternder Kommentare:

private void listViewTeams_DoubleClick(object sender, EventArgs e)
{
    //Sicherstellen, dass der Doppelklick auf einen Eintrag getätigt wurde
    if (listViewTeams.SelectedItems.Count > 0)
    {
        //Auslesen des Mannschaftsobjekts
        var team = (Team)listViewTeams.SelectedItems[0].Tag;

        //Ausgabe der Mannschaftsinformationen "Name", "Kürzel" und "Marktwert" auf Basis
        //der Informationen aus dem Mannschaftsobjekt
        richTextBoxInformation.Text = "===================================\r\n"
                                    + "Name: " + team.name + "\r\n"
                                    + "Kürzel: " + team.code + "\r\n"
                                    + "Gesamtmarktwert: " + team.squadMarketValue + "\r\n"
                                    + "===================================\r\n"
                                    + "\r\n\r\n";

        //Auslesen der Spiel-Informations-URI (Fixtures)
        var fixtureUrl = team._links.fixtures.href.Replace("http://api.football-data.org/","");

        //Erstellung von Client, Anfrage und setzen des Auth-Headers
        var rClient = new RestClient("http://api.football-data.org");
        var rRequest = new RestRequest(fixtureUrl, Method.GET);
        rRequest.AddHeader("X-Auth-Token", apiKey);

        //Synchrone Abfrage der API
        var response = rClient.Execute<FixtureList>(rRequest);
        var fixtures = response.Data;

        //Absteigendes sortieren der Spieltage und anschließendes iterieren über die Spieltage
        fixtures.fixtures.OrderByDescending(x => x.matchday).ToList().ForEach(fixture =>
        {
            //Ausgabe von "Spielpartner", "Location", "Spielergebnis" und "Spieltag"
            richTextBoxInformation.Text += fixture.homeTeamName + " vs. " + fixture.awayTeamName + "\r\n"
                                        + "Location: " + (fixture.homeTeamName == team.name ? "Heimspiel" : "Auswärtsspiel") + "\r\n"
                                        + "Ergebnis: " + (fixture.status != "FINISHED" ? "Spiel noch nicht ausgetragen"
                                                                                        : fixture.result.goalsHomeTeam + ":"
                                                                                        + fixture.result.goalsAwayTeam) + "\r\n"
                                        + "Spieltag: " + fixture.matchday + " (" + fixture.date + ")\r\n\r\n";

        });
    }
}

An dieser Stelle ist unser Tutorial bereits beendet. Die grundlegende Funktionsweise der API sowie das Absetzen von REST-Abfragen inkl. JSON-Deserialisierung sollte nun klar sein. Wer mag kann nun beliebig weitere Statistiken und Auswertungen erstellen. Auch an der Visualisierung der Daten mit optisch ansprechenden Graphen könnte weitergearbeitet werden.

Projektdateien und Download

Wer sein Projekt nicht zum Laufen bekommt oder keine Lust hat den Code selber zu schreiben, kann unter folgendem Link das Visual-Studio-Projekt herunterladen.

Download: SoccerDashboard – Visual-Studio-Projekt

Fazit

Mittels RestSharp lassen sich REST-APIs ohne großen Aufwand in C# nutzen. Sollte es einmal zu Problemen mit der JSON-Deserialisierung kommen oder der Wunsch bestehen, JSON dynamisch zu deserialisieren, gebe ich an dieser Stelle noch den Tipp, zusätzlich mit der Json.NET Library zu arbeiten und mittels RestSharp nur den Content abzufragen. Für das Deserialisieren wäre dann die Json.NET-Library zuständig, die noch etwas flexibler und mächtiger als die in RestSharp implementierte Logik ist.

Solltet ihr aufbauend auf SoccerDashboard bzw. den Infos aus diesem Artikel eine eigene Anwendung aufsetzen, würde ich mich freuen von euren Resultaten in den Kommentaren zu lesen. Welche Features, Statistiken und Auswertungen würdet ihr mit der gezeigten API erstellen wollen?

3 Kommentare

  1. Der Downloadlink funktioniert leider nicht….

Hinterlasse einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Sie dient nur dem Spamschutz.