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 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):
| Paket | Zweck |
|---|---|
Simple.OData.Client | Typisierter OData-Client für .NET |
InLoox.PM.Domain.Model.Public | InLoox-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.
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);
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/AccountInfoauf, um ein einzelnesApiAccountInfo-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/Projectauf, um eine Sammlung vonApiProject-Entitäten abzurufen FindEntriesAsync()gibt die erste Seite zurück (Standardlimit: 100 Einträge)- Jedes Projekt enthält Eigenschaften wie
ProjectId,Name,StartDate,EndDateund weitere
Verwendung:
var projects = await GetProjects();
foreach (var project in projects)
{
Console.WriteLine($"{project.Name} (ID: {project.ProjectId})");
}
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:
- Erstellt einen Datumsbereichsfilter — berechnet den ersten und letzten Tag des angegebenen Monats
- Fragt
DynamicTimeEntryab — verwendet die Dynamic-Endpunktvariante, die benutzerdefinierte Felder enthält (siehe Benutzerdefinierte Felder unten) - Verwendet
ODataFeedAnnotations— dieses Objekt empfängt Paginierungs-Metadaten aus der Antwort, einschließlich desNextPageLink - Blättert durch alle Ergebnisse — die
while-Schleife folgt demNextPageLink, bis alle passenden Einträge geladen sind - 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}");
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 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 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 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.
Benutzerdefinierte Felder lesen
// Dynamic-Zeiteinträge mit benutzerdefinierten Feldern abrufen
var entries = await client
.For<ApiDynamicTimeEntry>("DynamicTimeEntry")
.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
- Erste Schritte — Authentifizierung und OData-Abfragegrundlagen
- Projekte — Detaillierte
Project-Endpunktreferenz - Aufgaben — Detaillierte
Task-Endpunktreferenz - Zeiteinträge — Detaillierte
TimeEntry-Endpunktreferenz
Bei Problemen mit den Beispielen kontaktieren Sie den InLoox-Support.