.NET SerialPort-Kommunikation mit Arduino & Co. – 2024 Guide

NET SerialPort-Kommunikation mit Arduino und Co - C# VB.NET
NET SerialPort-Kommunikation mit Arduino und Co – C# VB.NET

USB-Kommunikation zwischen SerialPort und Mikrocontroller

RFID-Chips für Zeiterfassungssysteme auslesen, mit LED’s spielen, oder anderer Interessanter Kram ist möglich – mit Mikrocontrollern. Der Bekannteste in der Mikrocontroller-Szene ist wohl der „Arduino“, Welcher mit seinen verschiedenen Ausfertigungen viele Bastler-Bedürfnisse abdeckt. Im heutigen Beitrag erfährst Du, wie Du gängige Mikrocontroller mit Hilfe der .NET SerialPort-Klasse via C#, oder auch VB.NET ansteuern kannst.

Du hast es eilig? Nutze gerne das Inhaltsverzeichnis, um an eine passende Stelle zu hüpfen! Du hast Anregungen? Ich habe mich vertan? Schreib gerne ein sachliches Kommentar in die Kommentar-Sektion weiter unten!

Eine kleine Einführung in Mikrocontroller

Im Endeffekt kann man Mikrocontroller als extremst kleine, erweiterbare Computer bezeichnen. Üblicherweise schließt man Diese mit daran verlöteten Dingen an z. B. PC’s an, oder lässt Sie im Solo-Betrieb laufen. So kann man einen Mikrocontroller beispielsweise anweisen, Temperatur-Daten zu sammeln und Diese via USB, oder sogar Wi-Fi zu übermitteln. Aufgrund ihrer Größe, sind Mikrocontroller jedoch häufig in Ihrer Leistung eingeschränkt. Heutzutage muss man glücklicherweise anerkennen, zu welcher Leistung Sie im Vergleich zu früheren Geräten fähig sind.

An dieser Stelle möchte ich jedoch nicht weiter, gar zu tief in die Materie abtauchen. Ich schätze, dass Dir bereits bewusst ist, was z. B. der Arduino, oder etwa ein Rasperry Pi ist. Vermutlich wirst Du gerade deswegen auf diesen Beitrag gestoßen sein: Du möchtest herausfinden, wie Du über .NET (egal ob nun Visual Basic .NET, oder C#) mit diesen Mini-PC’s kommunizieren kannst. Grundsätzlich geht das in .NET via der SerialPort-Klasse, Welche sich zu einem vorhandenen COM-Port verbinden kann.

Ab dann beginnt das Abenteuer aber erst, denn sich nur zu verbinden, reicht leider nicht aus!

Ein >wirklich< einfaches Beispiel – reicht es?

Simples Beispiel - .NET Mikrocontroller SerialPort Kommunikation
Simples Beispiel – .NET Mikrocontroller SerialPort Kommunikation

Da wir nun wissen, dass es die SerialPort-Klasse ist, Welche wir verwenden müssen, ist ja alles geklärt, oder!? Ich meine wir müssen doch dann eigentlich nur – eben typisch Objektorientierte Programmierung (OOP) – eine Instanz der jeweiligen Klasse erstellen und verwenden. Machen wir das doch einfach mal im ersten Schritt, wir erstellen eine Instanz der Klasse SerialPort.

Die Installation des System.IO.Ports NuGet-Paket

Bevor wir jedoch die SerialPort-Klasse verwenden können, müssen wir erst einmal das NuGet-Paket namens „System.IO.Ports“ installieren. Begib Dich dazu einfach auf einem der üblichen Wege in den genannten Paket-Manager. Z. B. durch einen Rechtsklick auf Dein Projekt im Projektmappen-Explorer und einem anschließenden Linksklick auf „NuGet-Pakete verwalten..“.

Nun suche nach dem jeweiligen Paket und installiere es. Wähle es dazu aus, begib Dich ins rechte Fenster und hake Dein Projekt an – final kannst Du dann „installieren“ drücken und die NuGet-Magie passiert.

Eine simple SerialPort-Instanz

Danach kannst Du endlich wie folgt eine einfache Instanz der SerialPort-Klasse erstellen. Hierbei geben wir in den meisten Fällen zwei wichtige Dinge an:

  • Den zu nutzenden COM-Port
  • Die zur Kommunikation verwendete Baudrate

In meinem Fall ist mein Arduino am „COM4“ angeschlossen, daher übergebe ich dies als String. Ebenso befindet sich Code auf meinem Arduino, Welche eine Verbindung mit der Baudrate „19200“ initialisiert. Damit keine komischen Zeichen bei uns ankommen, sollten wir auch seitens .NET die gleiche Baudrate verwenden.

Dim port = New SerialPort("COM4", 19200)
var port = new SerialPort("COM4", 19200);

Weitere Konfiguration

Neben den nun bekannten Einstellungen, gibt es noch Weitere, wie z. B. die Parität, die DataBits und die StopBits. Grundsätzlich reicht es meist aus, Diese Einstellungen auf ihren Standardwerten zu lassen. Dies passiert, wenn Du Sie wie im obigen Beispiel nicht angibst.

  • Parität – Standardwert = Parity.None
  • DataBits – Standardwert = 8
  • StopBits – Standardwert = StopBits.One

Restliche Informationen dazu müsstest Du zumeist Deiner Gegenstelle, also dem Gerät entnehmen, zu dem Du Dich verbindest!

Und nü? Die Verbindung zum SerialPort öffnen!

Nun haben wir ein konkretes Objekt eines Bauplans (Klasse) namens „SerialPort“ erstellt, aber was nun? Nunja, bisher können wir nicht viel damit machen, denn die Verbindung ist noch nicht so wirklich hergestellt. Eine tatsächliche Verbindung zum Port öffnen wir einfach, indem wir die Open-Methode verwenden. Sprich also einfach Deinen eben erstellten Port an und verwende Ebendiese Methode:

Dim port = New SerialPort("COM4", 19200)
port.Open()
var port = new SerialPort("COM4", 19200);
port.Open();

Daten senden

Die schnellste und einfachste Möglichkeit Daten zu senden, ist die Vereinbarung eines Trennzeichens und das Abschicken, bzw. Auslesen von Daten, die dieses Trennzeichen beinhalten. Ein simples „Hin-und-her“ kann man also so durchführen:

Dim port = New SerialPort("COM4", 19200)
port.NewLine = vbLf
port.Open()
port.WriteLine("ID:?")

' blockiert solange BIS eine Antwort mit Daten UND dem vereinbarten Linefeed kommt!
Dim data = port.ReadLine()
var port = new SerialPort("COM4", 19200);
port.NewLine = "\n";
port.Open();
port.WriteLine("ID:?");

// blockiert solange BIS eine Antwort mit Daten UND dem vereinbarten Linefeed kommt!
var data = port.ReadLine();

Die Betonung liegt hier allerdings auf „simpel“, daher lies ruhig weiter und schaue Dir zum Beispiel besonders die Kapitel über Trennzeichen und Co. an.

Vorhandene COM-Ports auslesen / anzeigen

.NET COM Ports auflisten und anzeigen
.NET COM Ports auflisten und anzeigen

Im obigen Beispiel haben wir bisher nur einen fixen Port verwendet. Das ist nicht nur relativ unflexibel per se, sondern kann sich auch sogar je nach verwendetem Anschluss – trotz gleichem Gerät – am PC ändern. Schöner wäre es hingegen, wenn wir auflisten könnten, welche COM-Ports tatsächlich zur Verfügung stehen. Die gute Nachricht ist: Das ist ziemlich easy – wenn auch nicht immer ausreichend!

Kurz und bündig

Die einfachste Option, die vorhandenen COM-Ports auszulesen ist, die statische (in VB.NET „Shared“) SerialPort-Funktion namens „GetPortNames“ zu verwenden. Anzeigen könnte man diese Ports dann natürlich in jedem beliebigen Listen-Steuerelement, wie z. B. der ComboBox. Hier haben wir natürlich auch direkt die Möglichkeit, einen Port für unser Programm zu wählen.

Tipp: Direkt als bindungsfähige Liste

Ich empfehle hier übrigens die direkte „Umwandlung“ des zurückkommenden String-Arrays in eine BindingList. Wenn Du WPF verwendest, könntest Du hier eine passende Alternative, wie z. B. die ObservableCollection verwenden. Das bietet den Vorteil, später auf Veränderungen zu reagieren. Wir können dann z. B. bei Trennung einer physischen Verbindung den jeweiligen COM-Port entfernen, oder einen Port beim Herstellen einer USB-Verbindung der Auflistung hinzufügen.

Winforms-Beispiel

Public Class Form1

  ' sonstiger Form-Code..
  
  Public ReadOnly Property Ports As BindingList(Of String)

  Sub New ()
    Dim availablePorts = SerialPort.GetPortNames()
    Ports = New BindingList(Of String)(availablePorts)
    AddHandler Me.Load, AddressOf Form1_Load
  End Sub

  Private Sub Form1_Load(sender As Object, e As EventArgs)
    ComboBox1.DataSource = Ports
  End Sub
 
  ' sonstiger Form-Code..

End Class
public class Form1 : Form
{

  // sonstiger Form-Code..

  public BindingList<string> Ports { get; }

  public Form1()
  {
    var availablePorts = SerialPort.GetPortNames();
    Ports = new BindingList<string>(availablePorts);
    Load += Form1_Load;
  }

  private void Form1_Load(object sender, EventArgs e)
  {
    ComboBox1.DataSource = Ports;
  }

  // sonstiger Form-Code..

}

Daten zum Mikrocontroller schicken

Daten via SerialPort an Mikrocontroller wie Arduino senden
Daten via SerialPort an Mikrocontroller wie Arduino senden

Nachdem wir nun eine Möglichkeit gesehen haben, eine Verbindung zum Mikrocontroller aufzubauen, können wir nun weiter gehen. Das Wichtigste wäre wohl nun, tatsächliche Daten zum z. B. Arduino zu senden. Durch die serielle Verbindung (Serial…Port) schicken wir Daten – naja, eben seriell, sprich hintereinander – in die offene Verbindung. Die Reihenfolge der gesendeten Daten spielt hierbei natürlich eine essenzielle Rolle, da zuerst Gesendetes auch dementsprechend zuerst am Gerät ankommt. Schaue Dir gerne weiter unten die Write-, bzw. die WriteLine-Methode an.

Serielle Kommunikation & Protokoll

Dies sollte auch bei der Kommunikation im Allgemeinen beachtet werden, da hier eine Art parallele Kommunikation so natürlich relativ „unmöglich“ ist. Letztendlich entscheidet dann der Konsument über die passende Verarbeitung. Ich kann ja z. B. 3 Dinge kurz nacheinander empfangen, jedoch das Dritte Ding zuerst verarbeiten. Hierbei spielt also eine Art Protokoll, sowie eine Art Warteschlange eine sehr große Rolle.

Die Write-Methode

Verwende nun also den obigen Code, um eine Verbindung zum (erneut – z. B.) Arduino herzustellen und wähle dafür entweder die flexible „Auswahl-Methode“ der ComboBox, oder gebe den Portnamen fix an. Daten kannst Du dann ganz einfach via „Write“-Methode, bzw. eine der für Dich passenden Überladungen übersenden. Der Einfachheit halber, werde ich hier simple Strings übermitteln – ich verzichte also für’s Erste auf ein vollends implementiertes Protokoll. Schaue Dir alternativ auch die WriteLine-Methode an.

Strings an Arduino & Co. senden

Senden wir nun also einmal beispielhafte Daten an unseren Arduino. Dazu habe ich in diesem Beispiel einen kleinen Befehl aus meinem eigenen kleinen Protokoll verwendet. „ID“ spiegelt hier letztendlich eine Art Befehl und das „?“ die dazugehörige Statusabfrage wieder. Übersetzt könnte man sagen: „Ich möchte gerne Deine ID abrufen“.

Hierbei sollte man darauf achten, dass die SerialPort-Klasse (laut Doku) das ASCII-Encoding standardmäßig verwendet.

Dim port = New SerialPort("COM4", 19200)
port.Open()
port.Write("ID:?")
var port = new SerialPort("COM4", 19200);
port.Open();
port.Write("ID:?");

Bytes via Write-Methode senden

Wenn wir im Gegensatz zu einem String rohe Bytes schicken wollen, können wir dies natürlich auch sehr einfach tun. Dazu nutzen wir einfach die andere Überladung der „Write“-Methode. Hierbei müssen wir die Bytes mit einem passenden Encoding dann selbst erfassen. Dafür verwende ich hier beispielsweise das UTF8-Encoding:

Dim port = New SerialPort("COM4", 19200)
port.Open()
Dim textToSend = "ID:?"
Dim data = System.Text.Encoding.UTF8.GetBytes(textToSend)
port.Write(data, 0, data.Length)
var port = new SerialPort("COM4", 19200);
port.Open();
var textToSend = "ID:?";
var data = System.Text.Encoding.UTF8.GetBytes(textToSend);
port.Write(data, 0, data.Length);

WriteLine-Methode – mit Nachrichten-Trenner

„Das hat doch kein Ende..“ – das könnte man sich mit Sicherheit in so diversen alltäglichen Situation denken. Allerdings meine ich hier eher gesagt unsere Kommunikation zum Mikrocontroller, wie z. B. den Arduino. Mit unserem bisherigen Code weiß unser Kommunikationspartner leider gar nicht, wann eine „Nachricht“, oder eine Übermittlung beendet ist. Dem gleichen Problem treten wir auch entgegen, wenn wir die Richtung Arduino->.NET Software betrachten.

Für gewöhnlich verwendet man hier eine Art „Message-Terminator“, also eine Art Trennzeichen, Welches signalisiert: „Hey, ab hier ist die Übertragung der letzten Nachricht abgeschlossen“. Theoretisch könnte man natürlich auch – analog z. B. zu HTTP & Co. – eine Art Header senden. Hier muss man schlichtweg sein eigenes Protokoll wählen / designen, Welches für den jeweiligen Einsatz geeignet ist.

NewLine-Eigenschaft mit Trennzeichen

In unserem Beispiel können wir mit dem SerialPort vorab ein Trennzeichen über die „NewLine“-Eigenschaft vereinbaren. Ich weiß, ein verwirrender Name.. Setze nun einmal beispielhaft das „LineFeed“-Zeichen als das Trennzeichen ein, dies geht wie gleich folgenden. Beachte, dass zukünftige „WriteLine“-Aufrufe nun automatisch das Trennzeichen angeheftet bekommen! ReadLine wird hingegen solang blockieren, bis es dieses Trennzeichen ankommend vom Gerät bei einem Lesevorgang erkennt!

' irgendwo bei der Initialisierung..
_port = New SerialPort("COM4", 19200)
' hier setzen wir das Zeichen 10 -> Linefeed
' als Trennzeichen der Nachrichten ein!
_port.NewLine = vbLf
// irgendwo bei der Initialisierung..
_port = new SerialPort("COM4", 19200);
// hier setzen wir das Zeichen 10 -> Linefeed
// als Trennzeichen der Nachrichten ein!
_port.NewLine = "\n";

Nachwort zum Thema Protokoll

In meinerseits geschriebenen und professionell eingesetzten Anwendungen, habe ich hier ein ganzes Protokoll in Kombination mit dynamischen Zusammensetzungen von Maschinen-Objekten gebaut. Dies würde hier allerdings mehr als den Rahmen sprengen. Eventuell werde ich dies in einem zukünftigen Beitrag einmal demonstrieren.

Meines Erachtens nach ist dies der einzig richtige Weg, da man auf die obige Art und Weise ggf. schnell auf gewisse Grenzen stößt. Wenn ich z. B. warten möchte bis gewisse Aufgaben (siehe Tasks, TaskCompletionSources, etc.) erledigt sind, habe ich aktuell keine Ahnung, Welches herausgesendete Kommando, zu welcher eingehenden Antwort gehört.

Antwortdaten empfangen

Daten von Mikrocontrollern wie Arduino über SerialPort in VB.NET und C# empfangen
Daten von Mikrocontrollern wie Arduino über SerialPort in VB.NET und C# empfangen

Bisher war die Kommunikation mit der jeweiligen Gegenstelle relativ einseitig, oder? Es ist also mehr als an der Zeit, auch eine wirkliche Antwort des Geräts zu empfangen, sowie zu verarbeiten. Auch hier gibt es verschiedene Lösungen, von Denen ich schon Einige durch hatte. Viele Entwickler – darunter auch ich – erfuhren in Vergangenheit von gewissen Problemen, wie z. B. abgeschnittene Daten, usw.

Für unser Beispiel werde ich auch hier die womöglich einfachste Variante vorstellen. Melde Dich gerne in den Kommentaren unten, falls Du komisches Verhalten feststellen, oder etwas anderes zu berichten hast! Nun aber zurück zum eigentlichen Thema..

Polling ist „plöde“, gell!?

Wie schon erwähnt, gibt es verschiedene Möglichkeiten Daten aus dem SerialPort zu empfangen. Darunter wäre einerseits das typische Polling. Wenn Dir dieser Begriff nichts sagt, stell‘ Dir einfach das gute alte Kind auf dem Rücksitz vor: „Sind wir schon da?.. Sind wir schon da?.. Sind wir schon da?.. usw.“. Im Endeffekt fragt etwas einfach die Gegenstelle permanent, ob was anliegt, bzw. ob sich was geändert hat. Für gewöhnlich passiert das dann in einer Art Endlosschleife, oder a la „solange Gerät verbunden“.

Ereignisse zur Rettung!

Wenn wir also keine Lust auf das permanente, nervige und ineffiziente „Sind wir schon da?“ haben, brauchen wir eine Alternative. Dazu gibt uns die SerialPort-Klasse ein Ereignis namens „DataReceived“ mit an die Hand. Nun müssen wir also nur noch einen Handler an geeigneter Stelle verknüpfen und schon können wir die empfangenen Daten verarbeiten.

Ein einfaches Beispiel könnte dazu wie gleich folgend aussehen. Beachte hierbei, dass ich die Instanz des SerialPorts als Form-Member ausgelagert habe, damit ich auch in der Handler-Sub/-Void darauf zugreifen kann. Das Schreiben des „Befehls“ kann natürlich in z. B. einen Button-Click-Handler o. Ä. ausgelagert werden. Für ein einfaches Beispiel lasse ich es hier im Konstruktor.

Für das Auslesen der empfangenen Daten an sich (aus dem Puffer), verwende ich die „ReadExisting“-Funktion. Diese gibt uns einen String der aktuell vorhandenen Daten im Puffer zurück. Anschließend würde es dann Deinem Protokoll unterliegen, diesen String korrekt zu verarbeiten.

Achtung – Stückelungen möglich!

Hierbei ist es wie gesagt ganz wichtig zu verstehen, dass wir einfach nur einen Haufen serieller (aufeinanderfolgender) Daten bekommen. Diese kommen auch nicht unbedingt immer an einem Stück! Wenn ich in meinem vorherigen Beispiel z. B. „ID:?“ zum Arduino schicke, könnte ich die gesamte Antwort in einem Schritt bekommen: „ID:Arduino Nano“.

Es könnte aber auch genauso gut sein, dass ich erst „ID:“ beim ersten Aufruf des Ereignisses bekomme und dann in einem zweiten Durchlauf „Arduino Nano“. Das korrekte Auseinanderfriemeln bleibt also mehr oder weniger unsere Aufgabe!

Public Class Form1

  ' sonstiger Form-Code..

  Private _port As SerialPort
  
  Sub New ()
    _port = New SerialPort("COM4", 19200)
    AddHandler _port.DataReceived, AddressOf Port_DataReceived
    _port.Open()
    _port.Write("ID:?")
  End Sub

  Private Sub Port_DataReceived(sender As Object, e As SerialDataReceivedEventArgs)
    Dim data = _port.ReadExisting()
    Debug.Write($"data received: {data}")
  End Sub
 
  ' sonstiger Form-Code..

End Class
public class Form1 : Form
{

  // sonstiger Form-Code..

  SerialPort _port;

  public Form1()
  {
    _port = new SerialPort("COM4", 19200);
    _port.DataReceived += Port_DataReceived;
    _port.Open();
    _port.Write("ID:?");
  }
  
  private void Port_DataReceived(object sender, SerialDataReceivedEventArgs e)
  {
      var data = _port.ReadExisting();
      Debug.Write($"data received: {data}");
  }
  
  // sonstiger Form-Code..

}

Cross-Thread Probleme

Beachte, dass das DataReceived-Ereignis, bzw. dessen Handler auf einem Hintergrund-Thread ausgeführt wird. Um also auf die grafische Oberfläche zugreifen zu können, müsstest Du zurück auf Deinen UI-Thread „hüpfen“. Das funktioniert z. B. so:

    Private Sub Port_DataReceived(sender As Object, e As SerialDataReceivedEventArgs)
        Dim data = _port.ReadExisting()
        BeginInvoke(Sub()
                        TextBox1.AppendText(data)
                    End Sub)
    End Sub
private void Port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
  var data = _port.ReadExisting();
  BeginInvoke(() => TextBox1.AppendText(data));
}

Achtung – typische SerialPort Fehler!

Gängige Fehler bei der Verwendung der .NET SerialPort Klasse
Gängige Fehler bei der Verwendung der .NET SerialPort Klasse

Eigentlich ist der obige Code relativ simpel, aber natürlich stößt man auch hier auf gewisse Probleme. Was ist z. B., wenn ich einen COM-Port öffnen möchte, der gar nicht „existiert“!? Was, wenn ich einen bereits geöffneten COM-Port versuche, erneut zu öffnen? Die Antwort ist ganz einfach: Dann gibt es dazu passende Ausnahmen (Exceptions).

Öffnen eines nicht existenten COM-Ports

Der vermutlich häufigste Fehler ist gleichzeitig der Offensichtlichste: Du öffnest die Verbindung zu einem COM-Port, Welcher gar nicht verfügbar ist. Eventuell liegt es daran, dass der Nutzer in Deinen App-Einstellungen, in Vergangenheit einen falschen Port gewählt hatte. Vielleicht hat er/sie es auch einfach vergessen, das Gerät via USB anzuschließen.

In so einem Fall wird Dich mit aller Wahrscheinlichkeit der folgende Fehler begrüßen: System.IO.FileNotFoundException: „Could not find file ‚COM6‘.“.

Ausnahme FileNotFoundException beim Öffnen eines fehlenden COM-Ports
Ausnahme FileNotFoundException beim Öffnen eines fehlenden COM-Ports

Doppelte Verbindungen auf einem COM-Port

Versuchst Du z. B. eine COM-Port-Verbindung zu öffnen, obwohl Du bereits eine offene Verbindung hast, kommt es zu einem Fehler. Dies geschieht auch, wenn eine andere Software bereits die Verbindung auf diesem COM-Port geöffnet hat. Hierbei handelt es sich um die „UnauthorizedAccessException“ aus dem folgenden Bild. Dazu habe ich einfach zwei Instanzen der SerialPort-Klasse für den gleichen Port (COM4) erstellt und versucht, Beide zu öffnen:

Dim portA = New SerialPort("COM4", 19200)
Dim portB = New SerialPort("COM4", 19200)
portA.Open()
' wird einen Fehler werfen!
portB.Open()
var portA = new SerialPort("COM4", 19200);
var portB = new SerialPort("COM4", 19200);
portA.Open();
// wird einen Fehler werfen!
portB.Open();

Der resultierende Fehler: „System.UnauthorizedAccessException: „Access to the path ‚COM4‘ is denied.““

Ausnahme UnauthorizedAccessException bei doppelter Verbindung zum COM-Port
Ausnahme UnauthorizedAccessException bei doppelter Verbindung zum COM-Port

Komische Zeichen in den Antwort-Daten

Der wohl – ich würde sagen – dritt-häufigste Fehler ist es, komische Zeichen in seinen SerialPort Antwort-Daten zu haben. Da tauchen dann unter anderem komische Striche, bzw. auch Fragezeichen auf. Sieht häufig blöd aus, aber die Lösung ist eigentlich relativ einfach: Du verwendest eine falsche Baudrate! Ebenso kommt das Problem zustande, wenn Du schon einmal Daten mit falscher Baudrate gesendet hast und DANN auf die richtige Baudrate wechselst!

Im Screenshot kannst Du sehen, wie so eine „fehlerhafte“ Kommunikation aussehen könnte. Wenn Du im zweiten Versuch (nach dem Erhalt einer solchen Nachricht) eine erneute Nachricht sendest, sollte die Kommunikation normal laufen.

Komische Zeichen – Falsche Baudrate bei der .NET Kommunikation mit einem SerialPort
Komische Zeichen – Falsche Baudrate bei der .NET Kommunikation mit einem SerialPort

Downloads & Weiteres

Falls Du es eilig hast, oder einfach nur loslegen möchtest, kannst Du Dir das passende Beispielprojekt hier direkt herunterladen. Alternativ empfehle ich Dir auch die Verwendung eines meiner NuGet-Pakete, damit geht es ggf. ebenso schnell.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert