Anfängerhilfe: Daten aus API in C# Variablen speichern

Vor ca. einem Jahr bin ich in die C#-Programmierung eingestiegen und programmiere bereits einige Rechenprogramme und so weiter.
Nun habe ich mir ein relativ Ehrgeiziges Projekt vorgenommen. Ich möchte Daten aus einer API im Netz in einem Programm auswerten.
Das Problem was ich nun habe - die Daten müssen aus dem Netz in eine andere Form umgewandelt werden und in Variablen gespeichert werden - das würde ich gerne Timer-Basiert machen, da die Daten sich laufend ändern.

es geht um die API von CEX.io https://cex.io/api - Diese gibt z.B. für den Ticker momentan folgendes aus:
{“timestamp”:“1385027019”,“low”:“0.07998”,“high”:“0.08997801”,“last”:“0.08797”,“volume”:“13250.44238344”,“bid”:“0.087043040000000002”,“ask”:“0.087239999999999998”}
Daraus hätte ich gerne mehrere Variablen (int oder double) die so benennt werden, wie die API es vorgibt und durch den Timer z.B. jede Sekunde mit den neuen Werten beschrieben werden.
Wie stelle ich das an? Natürlich müssen es dann globale Variablen sein, da die Berechnung ja in einer Funktion für einen Button stattfinden wird und der Timer im Hintergrund automatisch immer die Variablen aktualisiert, unabhängig davon ob berechnet werden soll oder nicht.
Erst danach wird es ja möglich sein damit zu rechnen.

Bisher habe ich noch nie irgendwas aus dem Netz an Daten abgerufen, also bin ich in dem Gebiet ein kompletter Neuling.

Schritt 1: Netzwerk Code schreiben. Da es eine Rest-API ist kommst du mit dem WebClient schon relativ weit. Der holt dir das JSON als String runter.
Schritt 2: Parsing -> Ich hab nur kurz Google bemüht. Das funktioniert mit json.NET anscheinend ganz gut.
Schritt 3: Das ganze in deine UI einbauen. Das sollte dann schon relativ einfach gehen.

Hallo,

das Lesen geht mit der WebRequest-Klasse:
[csharp]
using System;
using System.Net;
using System.IO;
using System.Diagnostics;

namespace nsTest
{
class Test
{
static void Main(string[] args)
{
WebRequest wrGETURL = WebRequest.Create(“https://cex.io/api/ticker/GHS/BTC”);
Stream objStream = wrGETURL.GetResponse().GetResponseStream();

        using( StreamReader objReader = new StreamReader(objStream) )
        { 
			string sLine = String.Empty;

	    	while( (sLine = objReader.ReadLine()) != null )
		    {
				Debug.WriteLine( sLine );
            }					
		}
    }
}        

}
[/csharp]

Das Zerlegen des Strings (in sLine) kann man entweder mit der Split-Methode der String-Klasse erledigen oder mit regulären Ausdrücken (Regex).
Globale Variablen gibt es in C# nicht. Du musst eine projektweit zugängliche Datenstruktur erzeugen.

Gruß
albatros

Singleton ist da das Stichwort.

Die schlechteste Variante. Einfach einen Serializer drauf los lassen der einem ein schönes dynamic-Objekt ausspuckt mit dem man weiter arbeiten kann und gut ist. Das ist in 5 Zeilen erledigt und man hat weniger Code in dem Bugs stecken können. So Tokenizer sind dann doch überraschend anfällig.

danke - spart mir das Suchen :smiley: ick brauch so was auch noch

[QUOTE=albatros]Hallo,

das Lesen geht mit der WebRequest-Klasse:
[csharp]
using System;
using System.Net;
using System.IO;
using System.Diagnostics;

namespace nsTest
{
class Test
{
static void Main(string[] args)
{
WebRequest wrGETURL = WebRequest.Create(“https://cex.io/api/ticker/GHS/BTC”);
Stream objStream = wrGETURL.GetResponse().GetResponseStream();

        using( StreamReader objReader = new StreamReader(objStream) )
        { 
			string sLine = String.Empty;

	    	while( (sLine = objReader.ReadLine()) != null )
		    {
				Debug.WriteLine( sLine );
            }					
		}
    }
}        

}
[/csharp]

Das Zerlegen des Strings (in sLine) kann man entweder mit der Split-Methode der String-Klasse erledigen oder mit regulären Ausdrücken (Regex).
Globale Variablen gibt es in C# nicht. Du musst eine projektweit zugängliche Datenstruktur erzeugen.

Gruß
albatros[/QUOTE]

Erzeugt diese Code dann verschiedene Variablen mit den entsprechenden Werten aus der API? also string timestamp, string low, string high etc? Diese müssten dann ja auch in der Void main abgerufen werden können - bzw. der Code durch einen Timer immer wieder neu aufgerufen werden…

Hallo,

nein, der Code erzeugt aktuell nur einen String mit allen Daten, wie in deinem ersten Post als Beispiel genannt. Eine Lösung dafür hatte ja schon schlingel genannt (serializer).

Gruß
albatros

so - mein erster Versuch eines Serializers (nach Beispiel)
[CSHARP]public class BTCexchange
{
public string timestamp { get; set; }
public string low { get; set; }
public string high { get; set; }
public string last { get; set; }
public string volume { get; set; }
public string bid { get; set; }
public string ask { get; set; }

 public BTCexchange(string timestamp, string low, string high, string last, string volume, string bid, string ask)
 {          
     this.timestamp = timestamp;      
     this.low = low;
     this.high = high;
     this.last = last;
     this.volume = volume;
     this.bid = bid;
     this.ask = ask; 
 }

 public User() 
 { 
 }   

}
[/CSHARP]
Woher weiß dieser Code an welcher Stelle des strings aus der WebRequest-Klasse die entsprechenden Werte stehen? Ich sehe irgendwie keine Übergabe eines Strings, den wir mit der WebRequest-Klasse erstellen? Muss dafür das { get; set; } umgeschrieben werden?

Dann fürs Hauptprogramm - bzw. die Timer.tick-Funktion:
[CSHARP]BTCexchange timestamp = new timestamp(/code für übergabe von WebRequest/,1);
BTCexchange low = new low(/code für übergabe von WebRequest/,1);

[/CSHARP]
Schreibt bzw. überschreibt dies letztendlich die einzelnen strings für die Werte? - und was müsste anstelle von den Kommentaren in die Funktion eingesetzt werden, damit die richtigen Stellen im String aus der WebRequest-Klasse (der mit allen Daten aus der API-Abfrage) gefunden werden?
und ruft der Code für die WebRequest-Klasse aus den Beiträgen davor die Daten regelmäßig in Echtzeit ab, oder muss dieser auch erst aufgerufen werden um neue Werte zu schreiben? [ Test(); ] oder so?

Ich habe bisher leider noch nicht die Zeit gefunden, das Projekt richtig in Visual Studio (inzw. 2013) anzugehen - ich wollte vorher hier noch die Theorie lernen, wie es möglich wäre…

Hallo paradonym,

mit dem XML-Serializer kommst du nicht weiter.
Schau dirjson.NET an. Den Vorschlag hatte doch Schlingel schon gemacht.

Gruß
albatros

Ein Freund hat ein wenig geholfen und hat aus der WebRequest klasse einen Einzeiler gemacht - soweit so gut, fehlt nur noch der Serializer… Wie bring ich das jetzt so hin, dass er aus dem gesamten String mehrere Strings macht, die die werte enthalten?

Hier einmal die komplette Form1.cs
[CSHARP]using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Net;
using System.Diagnostics;
using System.IO;

namespace CEX.io_Windows2
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
string APIdata = “”;
private void refresh_Tick(object sender, EventArgs e)
{
APIdata = new System.Net.WebClient().DownloadString(“https://cex.io/api/ticker/GHS/BTC”);
/*WebRequest wrGETURL = WebRequest.Create(“https://cex.io/api/ticker/GHS/BTC”);
Stream objStream = wrGETURL.GetResponse().GetResponseStream();

        using (StreamReader objReader = new StreamReader(objStream))
        {
            string sLine = String.Empty;

            while ((sLine = objReader.ReadLine()) != null)
            {
                Debug.WriteLine(sLine);
                APIdata = sLine;
            }
        } */
    }

    private void button1_Click(object sender, EventArgs e)
    {
        MessageBox.Show(APIdata, "Here's RAW data *hrhrhr*", MessageBoxButtons.OK, MessageBoxIcon.Information);
    }


    private void Form1_Load_1(object sender, EventArgs e)
    {
        refresh.Enabled = true;
    }
}

}[/CSHARP]
testweise wollte ich halt das heruntergeladene in einer MessageBox anzeigen - es kommt aber nur eine leere Messagebox ohne Inhalt… Woran liegt das? der Timer wird ja beim Ausführen gestartet, und sollte dann bei jedem Tick (1,5 Sekunden) die API lesen…

ich mag Deinen Freund :slight_smile: - rest geht mit http://json.codeplex.com/

Offtopic: Das hrhrhr hab ich da hingesetzt - er hat mir nur

APIdata = new System.Net.WebClient().DownloadString(„https://cex.io/api/ticker/GHS/BTC“);
gezeigt :slight_smile:

ist das nicht software die auf dem PC, der das Programm ausführt installiert sein muss? - Ich würde mir gerne umständliche Installer-Wege sparen und das ganze portabel (bzw. in einer .exe) halten…

sorry das ich das so sagen muss, aber ich brauch codebeispiele um es zu verstehen - kannste mir das nicht schon mal soweit ergänzen, dass dann hinterher die strings mit den passenden Werten stehen?

Ich versuch mich selbst gerade an einem Splitter - Das problem das ich schon vorab vermute ist, dass daraus haufenweise Strings ohne Werte entstehen, die aber eben die Namen aus der API tragen…
Code - erweitert um nen splitter und einem Versuch aus dem Array wieder einzelne Strings zu machen:
[CSHARP]using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Net;
using System.Diagnostics;
using System.IO;

namespace CEX.io_Windows2
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
string APIdata = „“;
string splittedData;
private void refresh_Tick(object sender, EventArgs e)
{
APIdata = new System.Net.WebClient().DownloadString(„https://cex.io/api/ticker/GHS/BTC“);

        splittedData = APIdata.Split(',');
    }

    private void button1_Click(object sender, EventArgs e)
    {
        MessageBox.Show(APIdata, "Here's RAW data *hrhrhr*", MessageBoxButtons.OK, MessageBoxIcon.Information);
    }


    private void Form1_Load_1(object sender, EventArgs e)
    {
        refresh.Enabled = true;
    }
    
    public static string ConvertStringArrayToString(string[] array)
    {
        StringBuilder builder = new StringBuilder();
        foreach (string value in splittedData)
        {
            builder.Append(value);
        }
    }
}

}[/CSHARP]
da gibts aber noch einen Fehler im Code der Konvertierungsmethode (inner foreach) -

momentan hab ich noch keine Ahnung wie ich das beheben kann

Die greifst in deiner statischen ConvertStringArrayToString() Methode auf einen nicht statischen Kontext zu!
Das ist nicht erlaubt und wird mit entsprechender Fehlermeldung gemeldet (;))

Ich nehme an statt in der foreach Schleife “splittedData” durch zugehen willst du den Methodenparameter “array” durchgehen.
Außerdem fehlt dir in dieser Methode noch der Rückgabewert!

Kann mir jemand mit dem JSON-Serializer helfen?
Also wie ich aus dem splittedData array die relevanten Daten rausfische und die zu einzelnen strings mit passenden Werten machen kann? Stehe da trotz Doku ziemlich auf dem Schlauch… Und mit dem StringBuilder sehe ich auch nur Probleme, da dieser ja nicht unterscheiden kann, was Wert und was string-name ist… bzw. der StringArrayBuilder dann nicht das genaue Gegenteil von dem Splitter macht und doch wieder alles zusammenfügt…

Die Klassenbibliothek (.dll) von Json (ich hab die aus dem Ordner “portable” genommen) habe ich eingebunden und die using-sache geschrieben - Kann mir hier jemand schreiben, wie ich aus dem Array dann einzelne Strings mache? - Damit wäre dieses Thema auch komplett gelöst und ich könnte selbst weiter an meiner UI arbeiten…

*** Edit ***

so - Kleine Vorbereitung zur Weiterprogrammierung:
Aktueller Stand:
[CSHARP]/* Programmierer: ******

*/

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Net;
using System.Diagnostics;
using System.IO;
using Newtonsoft.Json;

namespace CEX.io_Windows2
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
string APIdata = “”;
string[] splittedData;
private void refresh_Tick(object sender, EventArgs e)
{
APIdata = new System.Net.WebClient().DownloadString(“https://cex.io/api/ticker/GHS/BTC”);

        splittedData = APIdata.Split(',');
    }

    private void button1_Click(object sender, EventArgs e)
    {
        MessageBox.Show(APIdata, "Here's RAW data *hrhrhr*", MessageBoxButtons.OK, MessageBoxIcon.Information);
    }


    private void Form1_Load_1(object sender, EventArgs e)
    {
        APIdata = new System.Net.WebClient().DownloadString("https://cex.io/api/ticker/GHS/BTC");
        refresh.Enabled = true;
    }
    
   /* public static string ConvertStringArrayToString(string[] array)
    {
        StringBuilder builder = new StringBuilder();
        foreach (string value in splittedData)
        {
            builder.Append(value);
        }
        return builder.ToString(); 
    }*/
}

}

// string html = new System.Net.WebClient().DownloadString(“http://twitter.com”);

/*WebRequest wrGETURL = WebRequest.Create(“https://cex.io/api/ticker/GHS/BTC”);
Stream objStream = wrGETURL.GetResponse().GetResponseStream();

using (StreamReader objReader = new StreamReader(objStream))
{
string sLine = String.Empty;

while ((sLine = objReader.ReadLine()) != null)
{
    Debug.WriteLine(sLine);
    APIdata = sLine;
}

} */
[/CSHARP]

Ich notier mal, was im Array splittedData jetzt (nach der Split-Methode) gespeichert sein müsste: (mit dem Eingabestring {“timestamp”:“1386612763”,“low”:“0.06810001”,“high”:“0.07803”,“last”:“0.07199999”,“volume”:“21957.09521137”,“bid”:“0.071999980000000005”,“ask”:“0.07199999” })
[ol]
[li]{“timestamp”:“1386612763”
[/li][li]“low”:“0.06810001”
[/li][li]“high”:“0.07803”
[/li][li]“last”:“0.07199999”
[/li][li]“volume”:“21957.09521137”
[/li][li]“bid”:“0.071999980000000005”
[/li][li]“ask”:“0.07199999” }
[/li][/ol]
Was ich jetzt noch machen müsste:
[ul]
[li]Löschen einiger Zeichen aus den Array-Werten ( {, " und : )
[/li][li]Aufteilen des Arrays in Einzelwerte für Buchstaben und Ziffernfolgen, wobei die Reihenfolge nicht verloren gehen darf.
[/li][li]Dem JSON-Serializer irgendwie sagen, dass Buchstabenfolgen einen string-namen darstellen und die unmittelbar darauf folgenden Ziffernfolgen die jeweiligen Werte eben dieser strings.
[/li][/ul]
Wie stelle ich das an? - Übernimmt der Serializer mehr als das, was ich oben geschrieben habe, also übernimmt er schon das Löschen bestimmter Zeichen und das Aufteilen in strings? -

nimm doch bitte einfach http://json.codeplex.com/

Das was du da vor hast zu bauen nennt sich Tokenizer oder Lexer. Ein Serializer/Deserializer ist einen ganzen Ticken mächtiger. Ein Serializer ist nämlich ein Parser der dazu in der Lage ist, eine valide JSON-Zeichenkette wie deine einzulesen und daraus eine echte Objektinstanz zu machen - und umgekehrt. Soviel zum kurzen Exkurs in die Terminologie des Compiler-Baus.

Im Moment wirkt es, als hättest du die Seite entweder nicht gelesen oder nicht verstanden. Kein Beinbruch, so ist das am Anfang wenn man beginnt sich in diese ganzen englischen Dokus einzulesen. Allerdings ist das ein essentieller Skill den man auf jeden Fall trainieren u. ausbauen muss!
Die Website hat unter Samples → Serializing JSON → Deserialize an Object genau das was du brauchst.

Aber in aller Kürze was du brauchst:

  • Binde die DLL in dein Projekt ein.
  • Inkludier die entsprechenden Namespaces
  • Bau dir eine Modelklasse. In deinem Fall würde die in etwa so aussehen:

public class TickData 
{
   public Int64 timestamp { get; set; }
   public double low { get; set; }
   public double high  { get; set; }
   public double volume  { get; set; }
   public double last  { get; set; }
   public double bid  { get; set; }
   public double ask  { get; set; }
}

  • Mit dieser Model-Klasse kannst du jetzt den Deserializer anwerfen.

var jsonString = // dein string von oben;
TickData tick = JsonConvert.DeserializeObject<TickData>(jsonString);

Das ist auch schon alles. Kein blödes und fehleranfälliges String lexen und in Tokens einteilen. (Dafür sollte man übrigens etwas von Automatentheorie verstehen um das richtig zu machen! Und sogar dann braucht man meistens mehr als einen Anlauf weil man irgendeinen Randfall nicht bedacht hat. Wenn dir jetzt einfällt, „aber Regex“; Regex ist eine reguläre Sprache und kann am leichtesten ebenfalls als Automat implementiert werden.)

[CSHARP]/* Programmierer: ********************

*/

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Net;
using System.Diagnostics;
using System.IO;
using Newtonsoft.Json;

namespace CEX.io_Windows2
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
string APIdata = “”;
string[] splittedData;
private void refresh_Tick(object sender, EventArgs e)
{
APIdata = new System.Net.WebClient().DownloadString(“https://cex.io/api/ticker/GHS/BTC”);
splittedData = APIdata.Split(’,’);
var jsonString = APIdata;
TickData tick = JsonConvert.DeserializeObject(jsonString);

    }

    private void button1_Click(object sender, EventArgs e)
    {
        MessageBox.Show(APIdata, "Here's RAW data *hrhrhr*", MessageBoxButtons.OK, MessageBoxIcon.Information);
    }


    private void Form1_Load_1(object sender, EventArgs e)
    {
        APIdata = new System.Net.WebClient().DownloadString("https://cex.io/api/ticker/GHS/BTC");
        refresh.Enabled = true;
    }
    public class TickData
    {
        public Int64 timestamp { get; set; }
        public double low { get; set; }
        public double high { get; set; }
        public double volume { get; set; }
        public double last { get; set; }
        public double bid { get; set; }
        public double ask { get; set; }
    }
    

}

}[/CSHARP]

Auf dem Schulrechner kann ich die JSON-Strings hier leider nicht auslesen, da scheint es irgendwas im Netzwerk zu geben (vermutlich der Proxy-Server der hier auch Dropbox-Synchronisation etc blockt), der die regelmäßige Abfrage blockt.

Eine Frage habe ich dazu noch. Anscheinend sind ja jetzt die Einzelwerte als double-Werte im Programm gespeichert. Wie rufe ich jetzt z.B. High und Low ab, um sie in Labels darzustellen? Die Funktion ist ja [CSHARP]label1.Text=Convert.ToString(/Hier der Wert/);[/CSHARP]
Was muss ich da jetzt eintragen um z.B. den double “low” abzurufen und darzustellen? - Wenn ich raten müsste würde ich es mit TickData.low probieren, ich will mir aber sicher sein, dass es richtig ist, bevor ich mir da noch irgendwas zerschieße…

TickData tick, sieht mir so aus wie ein Timer, muss ich dafür im Programmdesign noch irgendwas hinzufügen? (Timer sind ja an sich Sache des Programmdesigns)

Als die nächsten Schritte werde ich mal schauen wie ich das Order-book (https://cex.io/api/order_book/GHS/BTC) durch den Deserializer durchgejagt bekomme, aber mit der reinen Anzeige der Kursdaten wäre mein “Schulprojekt” schon abgeschlossen…

WTF!?

Du programmierst keine Festplattensteuerung die, die Leseköpfe zertrümmern kann oder irgendeine Schaltungslogik die abraucht wenn man einen Fehler macht.

Fail often. Fail hard => Nur so wird man besser!

Zudem fehlen die absoluten Basics wenn dir Properties nichts sagen.

Ausprobieren!

Bei der Börse werden Kursbewegungen in Zeitabschnitte zusammen gefasst, diese nennt man auch Ticks. Deshalb heißt die Klasse die du da kopiert hast auch TickData. Mit einem Timer hat das also nichts zu tun.

[QUOTE=schlingel]WTF!?

Du programmierst keine Festplattensteuerung die, die Leseköpfe zertrümmern kann oder irgendeine Schaltungslogik die abraucht wenn man einen Fehler macht.

Fail often. Fail hard => Nur so wird man besser!

[…][/QUOTE]
Zerschießen im Sinne von “Fehler im Programmdesign” weil es die Einstellungen dafür irgendwie zerschießt und ich mich erst durch den ganzen Quelltext wühlen muss der das Interface aufbaut um da die Fehlerlinie zu finden (Einfaches Beispiel: In form1.cs die private void funktionen umbenennen). Mir ist schon klar, dass man gerade beim compiling und “Debugging” schon keine Hardware zerstört…

aber mal ganz logisch erklärt: “public double” bedeutet ja letztendlich, dass es ein öffentlicher double-wert ist, damit würde soweit ich das interpretieren kann, ein programmweiter double-wert angelegt, der irgendwo anders im Programm wie ein normaler double abgerufen werden kann.
Testen kann ich das aber erst wieder zu Hause, weil hier ja leider der Proxy einige Protokolle und stream-Methodiken blockt…

Dafür gibt’s Source Code Verwaltung. Z.B. mit git. Da brauchst du dann nicht einmal einen Server und bekommst mit Sourcetree eine recht ansehnliche GUI.

Da hast du genug in deinem Programm drinnen:

  • Fehlendes Exception-Handling
  • Blockierende Funktionen im onLoad-Handler
  • Eine Model-Klasse in einer Form-Klasse eingebettet
  • Logik in der View-Klasse
  • etc.

Das sind tatsächliche Design-Fehler. Alles nicht weiter tragisch für ein Schulprojekt also mach dir da nicht allzu viel Sorgen, nur der Vollständigkeit halber ein paar Hinweise. Refactoring sollte die IDE erledigen, soweit wie möglich. Lies dich dazu vielleicht mal ein, erspart viele Kopfschmerzen.

aber mal ganz logisch erklärt: „public double“ bedeutet ja letztendlich, dass es ein öffentlicher double-wert ist, damit würde soweit ich das interpretieren kann, ein programmweiter double-wert angelegt, der irgendwo anders im Programm wie ein normaler double abgerufen werden kann.

Es ist nicht „programmweit“, das wäre eine globale Variable. Es wird als Eigenschaft der Klasse definiert. Das heißt, dass man auf dieses Property der Instanz von außerhalb zugreifen darf. Außerhalb bezieht dabei auf jeden Scope in dem auch die Variable die, die Klasseninstanz enthält drinnen ist.

Das ist ein großer Unterschied zu einer globalen Variable!

Abgesehen davon, ist ein Property ein Shortcut um dir das Schreiben von Getter/Setter Methoden zu ersparen. Das heißt, das ist auch ein Unterschied zu einem public field.

Lies dich noch einmal ein in OOP. Da scheint es schon bei den Basisbegriffen ein wenig zu happern.