Sample Implementation TimeCockpitClient
In this article we provide some utility classes that should help you get started with the time cockpit Web API.
- TimeCockpitClient class: This class encapsulates both the OData endpoint for CRUD operations and the query endpoint for more sophisticated querying of data.
- TcProject class: This class should give you an idea how you can manually map the result of a TCQL query via the query endpoint to a POCO. Note that the POCO is not only used for the query endpoint, but also as payload for the OData endpoint.
- Settings class: This class is a strongly-typed representation of your configuration. It holds settings for all your configured configuration providers like appsettings.json, environment variables or e.g. KeyVault.
TimeCockpitClient Class
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Text;
namespace source_code_samples;
/// <summary>
/// This class provides methods to access the time cockpit web api.
/// </summary>
public class TimeCockpitClient
{
private readonly HttpClient httpClient;
private readonly string baseUri;
private readonly ILogger logger;
private readonly IOptions<Settings> configuration;
public TimeCockpitClient(IHttpClientFactory httpClientFactory, ILogger<TimeCockpitClient> logger, IOptions<Settings> configuration)
{
this.httpClient = httpClientFactory.CreateClient("TimeCockpit");
this.configuration = configuration;
this.baseUri = this.configuration.Value.TimeCockpitBaseUri;
this.logger = logger;
}
/// <summary>
/// Refreshes the server-side datacontext to get all data model changes
/// </summary>
public async Task Refresh()
{
try
{
var result = await httpClient.GetAsync($"{this.baseUri}/refresh");
}
catch
{
throw;
}
}
#region query
public async Task<List<TcProject>> GetProjects(string code)
{
var query = @"From T In Project Where T.Code = @Code Select T";
var jsonRequest = new JObject(new JProperty("query", query),
new JProperty("parameters",
new JObject(new JProperty("Code", $"'{code}'"))));
Func<JObject, List<TcProject>> deserialize = json =>
{
return json["value"].Select(jsonItem => TcProject.FromJson(jsonItem)).ToList();
};
return await GetResult(jsonRequest, deserialize);
}
#endregion
#region odata
/// <summary>
/// Generic method to perform posts against tc web api
/// </summary>
/// <param name="entityName">The entity/guid to post against</param>
/// <param name="payload">The json payload to post</param>
/// <returns>An instance of HttpResponseMessage</returns>
public async Task<HttpResponseMessage> PostAsync(string entityName, object payload)
{
this.logger.LogInformation($"Posting new object of type {entityName}");
var jsonPayload = JsonConvert.SerializeObject(payload);
var jsonContent = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync($"{this.baseUri}/odata/{entityName}", jsonContent);
if (!response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
throw new Exception(content);
}
return response;
}
/// <summary>
/// Generic method to perform patch against tc web api
/// </summary>
/// <param name="entityName">The entity/guid to post against</param>
/// <param name="payload">The json payload to post</param>
/// <returns>An instance of HttpResponseMessage</returns>
public async Task<HttpResponseMessage> PatchAsync(string entityName, Guid objectId, object payload)
{
this.logger.LogInformation($"Patching object of type {entityName} with id '{objectId}'");
var jsonPayload = JsonConvert.SerializeObject(payload);
var jsonContent = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
var response = await httpClient.PatchAsync($"{this.baseUri}/odata/{entityName}(guid'{objectId}')", jsonContent);
if (!response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
throw new Exception(content);
}
return response;
}
/// <summary>
/// Generic method to perform delete against tc web api
/// </summary>
/// <param name="uri">The entity/guid to post against</param>
/// <returns>An instance of HttpResponseMessage</returns>
public async Task<HttpResponseMessage> DeleteAsync(string entityName, Guid objectId)
{
return await httpClient.DeleteAsync($"{this.baseUri}/odata/{entityName}(guid'{objectId}')");
}
#endregion
#region reporting
public async Task<Guid> GetReportUuid(HttpClient httpClient, string reportName)
{
var query = @" From I In EntityViewReport
Where I.EntityViewReportName = @EntityViewReportName
Select New With
{
I.EntityViewReportUuid
}";
var parametersObject = new JObject();
parametersObject.Add(new JProperty("@EntityViewReportName", $"'{reportName}'"));
var jsonRequest = new JObject(new JProperty("query", query), new JProperty("parameters", parametersObject));
try
{
var content = new StringContent(jsonRequest.ToString(), Encoding.UTF8, "application/json");
var result = await httpClient.PostAsync($"https://api.timecockpit.com/select", content);
var json = JObject.Parse(await result.Content.ReadAsStringAsync());
if (result.IsSuccessStatusCode)
{
if (json["value"].Count() == 1)
{
return Guid.Parse(json["value"][0]["USR_EntityViewReportUuid"].Value<string>());
}
else
{
throw new Exception($"Either none or multiple reports with the name {reportName} were found.");
}
}
throw new Exception(json["odata.error"].ToString());
}
catch
{
throw;
}
}
#endregion
#region actions
/// <summary>
/// Executes a time cockpit action
/// </summary>
/// <param name="actionName">The entity/guid to post against</param>
/// <param name="payload">The json payload to post</param>
/// <returns>An instance of HttpResponseMessage</returns>
public async Task<HttpResponseMessage> ExecuteAction(string actionName, object payload)
{
this.logger.LogInformation($"Executing action {actionName}");
var jsonPayload = JsonConvert.SerializeObject(payload);
var jsonContent = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync($"{this.baseUri}/action/{actionName}", jsonContent);
if (!response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
throw new Exception(content);
}
return response;
}
#endregion
#region helper
private async Task<T> GetResult<T>(JObject jsonRequest, Func<JObject, T> deserialize)
{
var content = new StringContent(jsonRequest.ToString(), Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync($"{this.baseUri}/select", content);
var json = JObject.Parse(await response.Content.ReadAsStringAsync());
if (response.IsSuccessStatusCode)
{
var result = deserialize(json);
return result;
}
throw new Exception(json["odata.error"].ToString());
}
#endregion
}
TcProject Class
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.ComponentModel;
namespace source_code_samples;
public class TcProject : IChangeTracking
{
public TcProject() => this.AcceptChanges();
#region Fields
private string projectName;
private bool? billable = false;
#endregion
#region Properties
public Guid APP_ProjectUuid { get; set; }
public string APP_Code { get; set; }
public string APP_ProjectName
{
get => projectName; set
{
if (projectName != value)
{
projectName = value;
IsChanged = true;
}
}
}
public bool? APP_Billable
{
get => billable; set
{
if (billable != value)
{
billable = value;
IsChanged = true;
}
}
}
[JsonIgnore]
public bool IsChanged { get; private set; }
#endregion
#region Methods
public static TcProject FromJson(JToken jsonItem)
{
var project = new TcProject();
project.APP_ProjectUuid = new Guid(jsonItem["APP_ProjectUuid"].Value<string>());
project.APP_Code = jsonItem["APP_Code"]?.Value<string>();
project.APP_ProjectName = jsonItem["APP_ProjectName"]?.Value<string>();
project.APP_Billable = jsonItem["APP_Billable"]?.Value<bool>();
project.AcceptChanges();
return project;
}
public void AcceptChanges() => IsChanged = false;
#endregion
}
Settings Class
namespace source_code_samples
{
public class Settings
{
public string? TimeCockpitBaseUri { get; set; }
}
}