Zum Hauptinhalt springen

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.

GitHub-Repository

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 das .NET SDK für die Kommandozeile)
  • .NET 6.0 oder höher
  • Ein InLoox-Konto sowie ein gültiges Personal Access Token
  • Die folgenden NuGet-Pakete (werden beim Build automatisch wiederhergestellt):
PaketZweck
Simple.OData.ClientTypisierter OData-Client für .NET
InLoox.PM.Domain.Model.PublicInLoox-Entitätsmodelle (ApiProject, ApiTimeEntry usw.)

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 Ihr Personal Access Token ein:

var token = "INSERT YOUR TOKEN";

Ersetzen Sie "INSERT YOUR TOKEN" durch Ihr tatsächliches Token.

Wichtig

Ihr Personal Access Token gewährt vollen Zugriff auf die API in Ihrem Namen. Behandeln Sie es wie ein Passwort — committen Sie es niemals in die Versionskontrolle und teilen Sie es nicht öffentlich.

3. Projekt starten

dotnet run --project InLoox.Api.Examples

Oder öffnen Sie die Solution in Visual Studio und drücken Sie F5.


Client-Initialisierung

Jedes Beispiel beginnt mit der gleichen Client-Konfiguration. Die ODataClientSettings definieren die Basis-URL und fügen das API-Token automatisch zu jeder Anfrage hinzu:

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);
InLoox Self-Hosted URL

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/");

Beispiele

Beispiel 1: Kontoinformationen abrufen

Ruft die Profilinformationen des authentifizierten Benutzers ab. Dies ist ein guter "Hello World"-Aufruf, um zu prüfen, ob Ihr Token funktioniert.

async Task<ApiAccountInfo> GetAccountInfo()
{
if (client == null) throw new InvalidOperationException("Initialize client first");
return await client.For<ApiAccountInfo>("AccountInfo").FindEntryAsync();
}

Was passiert hier:

  • Ruft GET /odata/AccountInfo auf, um ein einzelnes ApiAccountInfo-Objekt abzurufen
  • FindEntryAsync() gibt eine einzelne Entität zurück (keine Sammlung)
  • Die Antwort enthält den Namen, die E-Mail-Adresse und weitere Kontodaten des Benutzers

Verwendung:

var accountInfo = await GetAccountInfo();
Console.WriteLine($"Logged in as: {accountInfo.DisplayName}");

Beispiel 2: Projekte auflisten

Ruft die erste Seite der Projekte (bis zu 100) ab, auf die der authentifizierte Benutzer Zugriff hat.

async Task<IEnumerable<ApiProject>> GetProjects()
{
if (client == null) throw new InvalidOperationException("Initialize client first");
return await client.For<ApiProject>("Project").FindEntriesAsync();
}

Was passiert hier:

  • Ruft GET /odata/Project auf, um eine Sammlung von ApiProject-Entitäten abzurufen
  • FindEntriesAsync() gibt die erste Seite zurück (Standardlimit: 100 Einträge)
  • Jedes Projekt enthält Eigenschaften wie ProjectId, Name, StartDate, EndDate und weitere

Verwendung:

var projects = await GetProjects();
foreach (var project in projects)
{
Console.WriteLine($"{project.Name} (ID: {project.ProjectId})");
}
hinweis

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

Dies ist das lehrreichste Beispiel: Es demonstriert sowohl Filterung (nach Datumsbereich) als auch automatisches Paging (um alle passenden Datensätze über das 100-Einträge-Limit hinaus abzurufen).

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;
}

Was passiert hier:

  1. Erstellt einen Datumsbereichsfilter — berechnet den ersten und letzten Tag des angegebenen Monats
  2. Fragt DynamicTimeEntry ab — verwendet die Dynamic-Endpunktvariante, die benutzerdefinierte Felder enthält (siehe Benutzerdefinierte Felder unten)
  3. Verwendet ODataFeedAnnotations — dieses Objekt empfängt Paginierungs-Metadaten aus der Antwort, einschließlich des NextPageLink
  4. Blättert durch alle Ergebnisse — die while-Schleife folgt dem NextPageLink, bis alle passenden Einträge geladen sind
  5. Meldet den Fortschritt über den loadedFunc-Callback

Verwendung:

var entries = await GetAllTimeEntriesForMonth(
DateTime.Now,
msg => Console.WriteLine(msg)
);
Console.WriteLine($"Total time entries this month: {entries.Count}");
Paging-Muster

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,
DisplayName = name,
StartDateTime = start,
EndDateTime = 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
  • ProjectId verknüpft den Eintrag mit einem bestehenden Projekt
  • InsertEntryAsync() sendet eine POST-Anfrage an den TimeEntry-Endpunkt
  • Start- und Endzeit definieren die Dauer des Eintrags (hier: 1 Stunde)
info

Sie können auch den untypisierten Dictionary-Ansatz anstelle von typisierten Objekten verwenden. Zum Beispiel:

var values = new Dictionary<string, object>
{
{ "ProjectId", projectId },
{ "DisplayName", name },
{ "StartDateTime", start },
{ "EndDateTime", start.AddHours(2) }
};
var res = await client.InsertEntryAsync("TimeEntry", values);

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 Projekt
  • Set(new { Name = newName }) definiert die zu ändernden Felder — hier nur den Namen
  • UpdateEntryAsync() sendet eine PATCH-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ätDynamic-VarianteBeschreibung
ProjectDynamicProjectProjekte mit benutzerdefinierten Feldern
TaskDynamicTaskItemAufgaben mit benutzerdefinierten Feldern
TimeEntryDynamicTimeEntryZeiteinträge mit benutzerdefinierten Feldern
BudgetDynamicBudgetBudgets mit benutzerdefinierten Feldern
LineItemDynamicLineItemEinzelposten mit benutzerdefinierten Feldern
ClientDynamicContactKontakte 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.

Benutzerdefinierte Felder lesen

// Dynamic-Zeiteinträge mit benutzerdefinierten Feldern abrufen
var entries = await client
.For<ApiDynamicTimeEntry>("DynamicTimeEntry")
.FindEntriesAsync();
Modelle erweitern

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)

ModellBeschreibung
ApiProjectProjekt-Entität
ApiDynamicProjectProjekt mit benutzerdefinierten Feldern
ApiTimeEntryZeiteintrag-Entität
ApiDynamicTimeEntryZeiteintrag mit benutzerdefinierten Feldern
ApiAccountInfoKontoinformationen
ApiTaskItemAufgaben-Entität
ApiDynamicTaskItemAufgabe 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

  • Erste Schritte — Authentifizierung und OData-Abfragegrundlagen
  • Projekte — Detaillierte Project-Endpunktreferenz
  • Aufgaben — Detaillierte Task-Endpunktreferenz
  • Zeiteinträge — Detaillierte TimeEntry-Endpunktreferenz
Hilfe benötigt?

Bei Problemen mit den Beispielen kontaktieren Sie den InLoox-Support.