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 eine andere .NET-kompatible IDE)
  • .NET 6.0 oder höher
  • Die folgenden NuGet-Pakete (werden automatisch wiederhergestellt):
    • Simple.OData.Client — OData-Client-Bibliothek
    • InLoox.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.

Sicherheitshinweis

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.

Self-Hosted-Benutzer

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 den AccountInfo-Endpunkt
  • FindEntryAsync() gibt ein einzelnes Objekt zurĂĽck (kein Array)
  • Das ApiAccountInfo-Modell enthält Eigenschaften wie UserName, Email und 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)
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​

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 des NextPageLink
  • Die while-Schleife folgt dem NextPageLink, bis alle passenden Einträge geladen sind
  • Der loadedFunc-Callback informiert ĂĽber den Fortschritt
  • DynamicTimeEntry wird anstelle von TimeEntry verwendet, um benutzerdefinierte Felder einzuschlieĂźen
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,
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
  • 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 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 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.

// Beispiel: Benutzerdefinierte Felder bei Projekten abfragen
var projects = await client
.For<ApiDynamicProject>("DynamicProject")
.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​

  • đź“– 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
Hilfe benötigt?

Bei Problemen mit den Beispielen besuchen Sie die GitHub Issues-Seite oder kontaktieren Sie den InLoox-Support.