Code-Beispiele
InLoox stellt ein offizielles Beispiel-Repository bereit, das Ihnen den Einstieg in die API-Nutzung mit C# und dem Simple.OData.Client-NuGet-Paket erleichtert.
👉 inlooxgroup/inloox-api-examples-current — Klonen oder laden Sie die Beispiele herunter.
Einführung​
Das Repository inloox-api-examples-current enthält eine einsatzbereite .NET-Konsolenanwendung, die die wichtigsten API-Operationen demonstriert:
- Kontoinformationen abrufen
- Projekte auflisten
- Zeiteinträge mit Paging und Filterung lesen
- Neue Zeiteinträge erstellen
- Projektnamen aktualisieren
Alle Beispiele verwenden die Bibliothek Simple.OData.Client, um OData-Anfragen typsicher und komfortabel zu formulieren.
Repository-Struktur​
inloox-api-examples-current/
├── InLoox.Api.Examples.sln # Visual Studio Solution
├── InLoox.Api.Examples/
│ ├── Program.cs # Hauptprogramm mit allen Beispielen
│ ├── InLoox.Api.Examples.csproj # Projektdatei mit NuGet-Referenzen
│ └── ...
└── README.md
Voraussetzungen​
- Visual Studio 2022 oder höher (oder eine andere .NET-kompatible IDE)
- .NET 6.0 oder höher
- Die folgenden NuGet-Pakete (werden automatisch wiederhergestellt):
Simple.OData.Client— OData-Client-BibliothekInLoox.PM.Domain.Model.Public— Typisierte InLoox-API-Modelle
Einrichtung​
1. Repository klonen​
git clone https://github.com/inlooxgroup/inloox-api-examples-current.git
cd inloox-api-examples-current
2. API-Token konfigurieren​
Ă–ffnen Sie die Datei Program.cs und tragen Sie Ihren Personal Access Token ein:
var token = "INSERT YOUR TOKEN";
Ersetzen Sie "INSERT YOUR TOKEN" durch Ihren tatsächlichen Token.
Committen Sie Ihren API-Token niemals in ein öffentliches Repository. Verwenden Sie für die Produktion Umgebungsvariablen oder einen Secrets Manager.
3. Projekt starten​
dotnet run --project InLoox.Api.Examples
Oder öffnen Sie die Solution in Visual Studio und drücken Sie F5.
Client-Konfiguration​
Die Beispiele konfigurieren den OData-Client wie folgt:
using InLoox.PM.Domain.Model.Aggregates.Api;
using Simple.OData.Client;
var EndPoint = new Uri("https://app.inloox.com");
var EndPointOdata = new Uri(EndPoint, "/api/odata/");
var token = "INSERT YOUR TOKEN";
var settings = new ODataClientSettings(EndPointOdata);
settings.BeforeRequest += delegate (HttpRequestMessage message)
{
message.Headers.Add("x-api-key", token);
};
var client = new ODataClient(settings);
Der BeforeRequest-Delegate fĂĽgt den API-Token automatisch als x-api-key-Header zu jeder ausgehenden Anfrage hinzu.
Wenn Sie InLoox Self-Hosted verwenden, ändern Sie den Endpunkt wie folgt:
var EndPoint = new Uri("https://YOUR-SELF-HOSTED-URL");
var EndPointOdata = new Uri(EndPoint, "/api/v1/odata/");
Beispiel 1: Kontoinformationen abrufen​
Ruft die Informationen des aktuell authentifizierten Benutzerkontos ab.
async Task<ApiAccountInfo> GetAccountInfo()
{
var accountInfo = await client
.For<ApiAccountInfo>("AccountInfo")
.FindEntryAsync();
Console.WriteLine($"Account: {accountInfo.UserName}");
return accountInfo;
}
Was passiert hier:
- Der Client sendet eine
GET-Anfrage an denAccountInfo-Endpunkt FindEntryAsync()gibt ein einzelnes Objekt zurĂĽck (kein Array)- Das
ApiAccountInfo-Modell enthält Eigenschaften wieUserName,Emailund weitere Kontodaten
Beispiel 2: Projekte auflisten​
Listet die ersten 100 Projekte auf, auf die der authentifizierte Benutzer Zugriff hat.
async Task<IEnumerable<ApiProject>> GetProjects()
{
var projects = await client
.For<ApiProject>("Project")
.FindEntriesAsync();
foreach (var project in projects)
{
Console.WriteLine($"Project: {project.Name} ({project.ProjectId})");
}
return projects;
}
Was passiert hier:
FindEntriesAsync()gibt eine Sammlung von Projekten zurück- Standardmäßig werden maximal 100 Einträge zurückgegeben
- FĂĽr mehr als 100 Projekte ist Paging erforderlich (siehe Beispiel 3)
Dies gibt maximal 100 Projekte zurück. Um alle Projekte abzurufen, müssen Sie Paging implementieren — siehe das nächste Beispiel für das Muster.
Beispiel 3: Zeiteinträge mit Paging und Filterung​
Ruft alle Zeiteinträge für einen bestimmten Monat ab — mit automatischer Paginierung, um alle Seiten zu laden.
async Task<List<ApiDynamicTimeEntry>> GetAllTimeEntriesForMonth(
DateTime month, Action<string> loadedFunc)
{
var filterStart = new DateTime(month.Year, month.Month, 1);
var filterEnd = new DateTime(month.Year, month.Month, 1).AddMonths(1);
var annotations = new ODataFeedAnnotations();
var timeentries = (await client
.For<ApiDynamicTimeEntry>("DynamicTimeEntry")
.Filter(k => k.TimeEntry_StartDateTime > filterStart
&& k.TimeEntry_EndDateTime < filterEnd)
.FindEntriesAsync(annotations)).ToList();
while (annotations.NextPageLink != null)
{
timeentries.AddRange(await client
.For<ApiDynamicTimeEntry>("DynamicTimeEntry")
.FindEntriesAsync(annotations.NextPageLink, annotations));
loadedFunc($"Loaded {timeentries.Count()} entries");
}
return timeentries;
}
Was passiert hier:
- Die Methode filtert Zeiteinträge nach Startdatum und Enddatum innerhalb des angegebenen Monats
- Sie verwendet
ODataFeedAnnotations, die Paginierungs-Metadaten aus der Antwort empfangen, einschlieĂźlich desNextPageLink - Die
while-Schleife folgt demNextPageLink, bis alle passenden Einträge geladen sind - Der
loadedFunc-Callback informiert ĂĽber den Fortschritt DynamicTimeEntrywird anstelle vonTimeEntryverwendet, um benutzerdefinierte Felder einzuschlieĂźen
Dieses ODataFeedAnnotations + while (NextPageLink != null) Muster ist die empfohlene Methode, um alle Datensätze von einer beliebigen Entität abzurufen. Verwenden Sie dieses Muster überall, wo Sie vollständige Datensätze benötigen.
Beispiel 4: Neuen Zeiteintrag erstellen​
Erstellt einen neuen Zeiteintrag fĂĽr ein bestimmtes Projekt.
async Task CreateTimeEntry(Guid projectId, string name, DateTime start)
{
var newEntry = new ApiTimeEntry
{
ProjectId = projectId,
Name = name,
StartDate = start,
EndDate = start.AddHours(1),
};
await client
.For<ApiTimeEntry>("TimeEntry")
.Set(newEntry)
.InsertEntryAsync();
Console.WriteLine($"Time entry '{name}' created.");
}
Was passiert hier:
- Ein neues
ApiTimeEntry-Objekt wird mit den erforderlichen Feldern erstellt ProjectIdverknĂĽpft den Eintrag mit einem bestehenden ProjektInsertEntryAsync()sendet einePOST-Anfrage an denTimeEntry-Endpunkt- Start- und Endzeit definieren die Dauer des Eintrags (hier: 1 Stunde)
Sie können auch typisierte Objekte anstelle von Dictionaries verwenden. Zum Beispiel:
var entry = new ApiTimeEntry
{
ProjectId = projectId,
DisplayName = name,
StartDateTime = start,
EndDateTime = start.AddHours(2)
};
await client.For<ApiTimeEntry>("TimeEntry").Set(entry).InsertEntryAsync();
Beispiel 5: Projektnamen aktualisieren​
Aktualisiert den Namen eines bestehenden Projekts.
async Task UpdateProjectName(Guid projectId, string newName)
{
await client
.For<ApiProject>("Project")
.Key(projectId)
.Set(new { Name = newName })
.UpdateEntryAsync();
Console.WriteLine($"Project renamed to '{newName}'.");
}
Was passiert hier:
Key(projectId)identifiziert das zu aktualisierende ProjektSet(new { Name = newName })definiert die zu ändernden Felder — hier nur den NamenUpdateEntryAsync()sendet einePATCH-Anfrage, die nur die angegebenen Felder aktualisiert- Sie können beliebige Kombinationen von Feldern im
Set()-Aufruf angeben
Benutzerdefinierte Felder​
Für den Zugriff auf benutzerdefinierte Felder verwenden Sie die Dynamic-Varianten der Entitäten:
| Standard-Entität | Dynamic-Variante | Beschreibung |
|---|---|---|
Project | DynamicProject | Projekte mit benutzerdefinierten Feldern |
Task | DynamicTaskItem | Aufgaben mit benutzerdefinierten Feldern |
TimeEntry | DynamicTimeEntry | Zeiteinträge mit benutzerdefinierten Feldern |
Budget | DynamicBudget | Budgets mit benutzerdefinierten Feldern |
LineItem | DynamicLineItem | Einzelposten mit benutzerdefinierten Feldern |
Client | DynamicContact | Kontakte mit benutzerdefinierten Feldern |
Die Dynamic-Varianten enthalten alle Standardfelder sowie zusätzliche Eigenschaften für Ihre benutzerdefinierten Felder. Die Feldnamen der benutzerdefinierten Felder entsprechen den in InLoox konfigurierten Namen.
// Beispiel: Benutzerdefinierte Felder bei Projekten abfragen
var projects = await client
.For<ApiDynamicProject>("DynamicProject")
.FindEntriesAsync();
Das NuGet-Paket InLoox.PM.Domain.Model.Public stellt Basismodellklassen wie ApiDynamicTimeEntry bereit. Wenn Sie benutzerdefinierte Felder in InLoox definiert haben, können Sie diese Klassen erweitern, um stark typisierte Eigenschaften für Ihre benutzerdefinierten Felder hinzuzufügen. Alternativ können Sie den untypisierten Dictionary-Ansatz über InsertEntryAsync / UpdateEntryAsync verwenden.
NuGet-Paket​
Die Beispiele verwenden das offizielle NuGet-Paket InLoox.PM.Domain.Model.Public, das typisierte C#-Modelle für alle API-Entitäten bereitstellt.
Enthaltene Modelle (Auswahl)​
| Modell | Beschreibung |
|---|---|
ApiProject | Projekt-Entität |
ApiDynamicProject | Projekt mit benutzerdefinierten Feldern |
ApiTimeEntry | Zeiteintrag-Entität |
ApiDynamicTimeEntry | Zeiteintrag mit benutzerdefinierten Feldern |
ApiAccountInfo | Kontoinformationen |
ApiTaskItem | Aufgaben-Entität |
ApiDynamicTaskItem | Aufgabe mit benutzerdefinierten Feldern |
Installation​
dotnet add package InLoox.PM.Domain.Model.Public
dotnet add package Simple.OData.Client
Vorteile​
Die Verwendung der typisierten Modelle mit Simple.OData.Client bietet:
- IntelliSense — automatische Vervollständigung von Eigenschaftsnamen in Ihrer IDE
- Kompilierzeit-Prüfung — Tippfehler bei Eigenschaftsnamen werden bereits vor der Laufzeit erkannt
- Typisierte Filter — Lambda-Ausdrücke statt zeichenkettenbasierter Filter:
// Typisierter Filter mit IntelliSense-UnterstĂĽtzung
var projects = await client
.For<ApiProject>("Project")
.Filter(p => p.IsClosed == false && p.StartDate > new DateTime(2024, 1, 1))
.OrderBy(p => p.Name)
.FindEntriesAsync();
Vollständige Program.cs​
Als Referenz hier die vollständige Program.cs aus dem Beispiel-Repository:
using InLoox.PM.Domain.Model.Aggregates.Api;
using Simple.OData.Client;
var EndPoint = new Uri("https://app.inloox.com");
var EndPointOdata = new Uri(EndPoint, "/api/odata/");
var token = "INSERT YOUR TOKEN";
var settings = new ODataClientSettings(EndPointOdata);
settings.BeforeRequest += delegate (HttpRequestMessage message)
{
message.Headers.Add("x-api-key", token);
};
var client = new ODataClient(settings);
var accountInfo = await GetAccountInfo();
var projects = await GetProjects();
await GetAllTimeEntriesForMonth(DateTime.Now, a => Console.WriteLine(a));
await CreateTimeEntry(projects.First().ProjectId, "Sample Time", DateTime.Now);
var project = projects.First();
await UpdateProjectName(project.ProjectId, project.Name + " updated");
async Task<ApiAccountInfo> GetAccountInfo()
{
if (client == null) throw new InvalidOperationException("Initialize client first");
return await client.For<ApiAccountInfo>("AccountInfo").FindEntryAsync();
}
async Task<IEnumerable<ApiProject>> GetProjects()
{
if (client == null) throw new InvalidOperationException("Initialize client first");
return await client.For<ApiProject>("Project").FindEntriesAsync();
}
async Task<List<ApiDynamicTimeEntry>> GetAllTimeEntriesForMonth(
DateTime month, Action<string> loadedFunc)
{
if (client == null) throw new InvalidOperationException("Initialize client first");
var filterStart = new DateTime(month.Year, month.Month, 1);
var filterEnd = new DateTime(month.Year, month.Month, 1).AddMonths(1);
var annotations = new ODataFeedAnnotations();
var timeentries = (await client
.For<ApiDynamicTimeEntry>("DynamicTimeEntry")
.Filter(k => k.TimeEntry_StartDateTime > filterStart
&& k.TimeEntry_EndDateTime < filterEnd)
.FindEntriesAsync(annotations)).ToList();
while (annotations.NextPageLink != null)
{
timeentries.AddRange(await client
.For<ApiDynamicTimeEntry>("DynamicTimeEntry")
.FindEntriesAsync(annotations.NextPageLink, annotations));
loadedFunc($"Loaded {timeentries.Count()} entries");
}
return timeentries;
}
async Task CreateTimeEntry(Guid projectId, string name, DateTime start)
{
if (client == null) throw new InvalidOperationException("Initialize client first");
var values = new Dictionary<string, object>
{
{ "ProjectId", projectId },
{ "DisplayName", name },
{ "StartDateTime", start },
{ "EndDateTime", start.AddHours(2) }
};
var res = await client.InsertEntryAsync("TimeEntry", values);
}
async Task UpdateProjectName(Guid projectId, string newName)
{
if (client == null) throw new InvalidOperationException("Initialize client first");
var project = new ApiProject() { Name = newName };
await client.For<ApiProject>().Key(projectId)
.Set(new { project.Name }).UpdateEntryAsync();
}
Nächste Schritte​
- 📖 Lesen Sie den Erste Schritte-Leitfaden für die vollständige OData-Abfragereferenz
- đź“‚ Erkunden Sie die Projekt-API fĂĽr alle verfĂĽgbaren Projektoperationen
- âś… Erfahren Sie mehr ĂĽber die Aufgaben-API zum Abfragen und Verwalten von Aufgaben
- ⏱️ Erfahren Sie mehr über die Zeiterfassungs-API
Bei Problemen mit den Beispielen besuchen Sie die GitHub Issues-Seite oder kontaktieren Sie den InLoox-Support.