Skip to main content

.NET wrapper

The .NET wrapper has been made to simplify the process of interacting with the API in a .NET application.

The wrapper is available on Nuget.

Examples of how to use the wrapper are available on Github.

Each function in the wrapper maps to an endpoint on the API.

Choosing a client

The package contains two clients that cover the same set of endpoints:

  • ApiClientV2: use this for all new code. Every function is named with an Async suffix, returns an ApiResult and takes an optional per-request timeout and cancellation token.
  • ApiClient: the original client. It is marked [Obsolete] and is only kept so that existing applications keep compiling. Its functions return ApiGetResult<T> or a plain bool.

Both clients are in the HHDev.DataManagement.ApiClientWrapper namespace, use the same authentication classes and can be used side by side while an application is migrated.

Authentication

To make requests from the API, the client must be authenticated. This can be done using a normal API Key or OAuth. Both clients take the same AuthenticationManager.

API Key

The API key can be passed to the AuthenticationManager when it is created:

var authManager = new AuthenticationManager(eAuthenticationMode.ApiKey, "API KEY HERE");

OAuth

To use the OAuth protocol for authentication. Create a SimpleAuthenticationSettings instance with the OAuth AuthenticationMode. Then assign this to both the OAuthAuthenticationManager and the AuthenticationManager given to the client like so:

// The same AuthenticationSettings instance must be shared between OAuthAuthenticationManager and the AuthenticationManager given to the client instance.
var sharedAuthSettings = new SimpleAuthenticationSettings
{
AuthenticationMode = eAuthenticationMode.OAuth
};
OAuthAuthenticationManager.AuthenticationSettings = sharedAuthSettings;
var authManager = new AuthenticationManager(sharedAuthSettings);

When the application is run, the default system browser will open and ask the user to login with their username and password (just as when logging into hh-dev.com).

All relevant authentication classes are in the HHDev.DataManagement.Client.Authentication namespace.

Concept

The general concept of the wrapper follows the same concept as the API, providing functions to get lists of items as well as single items, create and delete items. One of the advantages of the wrapper is that it handles the multi-step process of uploading attached files.

All functions in the API wrapper have an account ID argument which is used in the AccountId header

ApiClientV2

Instantiation

Pass the authentication manager to the constructor:

var apiClient = new ApiClientV2(authManager);

This talks to the default API host, ApiClientV2Config.DEFAULT_API_HOST. To use another one, pass it as the second argument:

var apiClient = new ApiClientV2(authManager, "staging.hh-dev.com:10185");

The authentication manager can be replaced at any time by calling UpdateAuthenticationManager.

ApiClientV2 implements IDisposable and IAsyncDisposable.

Configuration

Pass an ApiClientV2Config to control the rest of the behaviour:

var config = new ApiClientV2Config("hhdm-api.hh-dev.com")
{
Timeout = TimeSpan.FromSeconds(30),
MaxConcurrentRequests = 10,
Logger = loggerFactory.CreateLogger<ApiClientV2>(),
};
var apiClient = new ApiClientV2(authManager, config);
  • ApiHost: the API to talk to. new ApiClientV2Config() uses DEFAULT_API_HOST, but a host that is given and left blank is rejected
  • Timeout: how long a request may take when it does not pass a timeout of its own. Defaults to 100 seconds. Use Timeout.InfiniteTimeSpan for requests without a deadline
  • MaxConcurrentRequests: if set, the client keeps no more than this number of requests in flight at once. If it is left null the number of requests is not limited
  • Logger: receives one event per request, at Debug when it succeeded and at Warning when it did not
  • SslEnabled: set to false to skip certificate validation when talking to a local API
  • FeatureFlag: sent as a header on every request

There is also a constructor that takes an HttpMessageHandler, for applications that manage their own handler. A handler passed in this way is not disposed by the client.

Timeouts and cancellation

Every function ends with two optional arguments:

Task<ApiResult<ApiSetupModel>> GetSetupByIdAsync(string accountId, string setupId, ApiGetOptions options = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default);
  • timeout: the deadline for this one request. When it is not given the Timeout from the configuration is used
  • cancellationToken: cancelling the token abandons the request

Neither raises an exception. Running out of time gives a result with a Status of ClientTimeout, cancelling gives Cancelled.

The attachment functions are the one exception to the timeout: it covers the API calls they make, but not the transfer of the file to storage. That transfer stops only when cancellationToken is cancelled, so a large upload is not cut short by the timeout.

ApiResult and ApiResult<T>

ApiResult<T> is returned from every function that reads something back, and ApiResult from the rest. Both have the following properties:

  • bool Success: true when the request completed and the API returned a success status code
  • eApiResultStatus Status: how the request finished - Success, HttpError, ClientTimeout, Cancelled, NetworkError, AuthenticationFailed, SerializationError or UnexpectedError
  • HttpStatusCode? StatusCode: the status code of the response, or null when there was no response
  • string Message: if the request failed this property will provide more information on the reason for the failure
  • string ErrorContent: the body the API returned along with a failure status code
  • Exception Exception: the exception that ended the request, if there was one
  • string RequestMethod, string RequestUri, TimeSpan Duration: what was sent and how long it took
  • bool IsClientTimeout, bool IsCancelled: shortcuts for the matching Status values

ApiResult<T> adds:

  • T Value: if the request is successful the object returned by the API is available in this property, as it would be if a GET request was made. The item will be populated as if parametersToInclude has a value of * unless the get options say otherwise

ToString() gives a one line summary of the request, which is useful in log messages.

Add functions

To add a new item use the AddXXAsync functions. Some IDs will be required as arguments to express where the new item should be created. For example when creating a setup, the eventId and carId will be required. A CreateModel instance is also required which has the properties explained in the creating items section of the API documentation.

The add functions return an ApiResult<T>.

var addSetupResult = await _apiClient.AddSetupAsync(accountId, eventId, carId, new CreateModel()
{
CopyFromLast = true,
}).ConfigureAwait(false);

if (addSetupResult.Success)
{
var setup = addSetupResult.Value;
}

Get functions

To get an item there are multiple options, as with the API. A single item can be retrieved by ID, or a list of items can be retrieved by passing in the appropriate parent IDs. For example when getting a list of setups, the eventId and carId will be required. An ApiGetOptions instance carries the parametersToInclude information, and is optional - leave it out to get the same response as *.

The get functions return an ApiResult<T>. The T will be either an item or a list of items depending on the function called.

var getSetupResult = await _apiClient.GetSetupByIdAsync(accountId, setupId, new ApiGetOptions()
{
ParametersToInclude = new List<string>()
{
"*",
}
}).ConfigureAwait(false);

This will return a ApiResult<ApiSetupModel>.

var getSetupsResult = await _apiClient.GetAllSetupsForEventCarAsync(accountId, eventId, carId).ConfigureAwait(false);

This will return a ApiResult<IReadOnlyList<ApiSetupModel>>.

Update functions

To update an item use the UpdateXXAsync functions. The ID of the item to be updated will need to be provided as an argument. An UpdateModel instance is also required which contains the parameter updates to apply.

The update functions return an ApiResult, which explains why an update failed.

var result = await _apiClient.UpdateSetupAsync(accountId, setupId, new UpdateModel()
{
ParameterUpdates = new List<ParameterUpdateModel>()
{
new ParameterUpdateModel("Param1", "NewValue")
},
}).ConfigureAwait(false);

if (result.Success == false)
{
_logger.LogWarning("The setup could not be updated: {Result}", result);
}

Delete functions

To delete an item use the DeleteXXAsync functions. The ID of the item to be deleted will need to be provided as an argument.

The delete functions return an ApiResult.

var result = await _apiClient.DeleteSetupAsync(accountId, setupId).ConfigureAwait(false);

ApiClient

warning

ApiClient is obsolete. New code should use ApiClientV2.

Instantiation

Pass the authentication manager to the constructor:

var apiClient = new ApiClient(authManager);

ApiClient implements IDisposable.

Configuration

ApiClient is configured through ApiClientConfig:

var apiClient = new ApiClient(authManager, new ApiClientConfig("hhdm-api.hh-dev.com"));
  • ApiHost: the API to talk to
  • SslEnabled: set to false to skip certificate validation when talking to a local API
  • FeatureFlag: sent as a header on every request
  • MaxConcurrentRequests: only takes effect when RateLimitingEnabled is set as well

There is no per-request timeout and no logging. All requests run under one client wide timeout, set through the static ApiClient.DefaultTimeoutSeconds property.

ApiGetResult<T>

The generic class ApiGetResult<T> is returned from all Add and Get functions. T will be a different type depending on the endpoint. For the setups endpoint T will be ApiSetupModel.

The ApiGetResult<T> has the following properties:

  • bool Success: will be false if the request failed - in which case more information can be found in the Message and StatusCode properties
  • string Message: if the request failed this property will provide more information on the reason for the failure
  • HttpStatusCode StatusCode: the status code of the request
  • T ReturnValue: if the request is successful the created object will be available in this property, as it would be if a GET request was made. The item will be populated as if parametersToInclude has a value of *

The Update and Delete functions return a plain bool, so a request that failed cannot be told apart from one the API refused.

Add functions

To add a new item use the AddXX functions. Some IDs will be required as arguments to express where the new item should be created. For example when creating a setup, the eventId and carId will be required. A CreateModel instance is also required which has the properties explained in the creating items section of the API documentation.

The add functions return an ApiGetResult<T>.

var addSetupResult = await _apiClient.AddSetup(accountId, eventId, carId, new CreateModel()
{
CopyFromLast = true,
});

Get functions

To get an item there are multiple options, as with the API. A single item can be retrieved by ID, or a list of items can be retrieved by passing in the appropriate parent IDs. For example when getting a list of setups, the eventId and carId will be required. An ApiGetOptions instance is also required which contains the parametersToInclude information.

The get functions return an ApiGetResult<T>. The T will be either an item or a List of items depending on the function called.

var getSetupResult = await _apiClient.GetSetupById(accountId, setupId, new ApiGetOptions()
{
ParametersToInclude = new List<string>()
{
"*",
}
});

This will return a ApiGetResult<ApiSetupModel>.

var getSetupsResult = await _apiClient.GetAllSetupsForEventCar(accountId, eventId, carId, new ApiGetOptions()
{
ParametersToInclude = new List<string>()
{
"*",
}
});

This will return a ApiGetResult<List<ApiSetupModel>>.

Update functions

To update an item use the UpdateXX functions. The ID of the item to be updated will need to be provided as an argument. An UpdateModel instance is also required which contains the parameter updates to apply.

The update functions return a bool that indicates if the request was successful.

var result = await _apiClient.UpdateSetup(accountId, setupId, new UpdateModel()
{
ParameterUpdates = new List<ParameterUpdateModel>()
{
new ParameterUpdateModel("Param1", "NewValue")
},
});

Delete functions

To delete an item use the DeleteXX functions. The ID of the item to be deleted will need to be provided as an argument.

The delete functions return a bool that indicates if the request was successful.

var result = await _apiClient.DeleteSetup(accountId, setupId);

Migrating from ApiClient to ApiClientV2

ApiClientApiClientV2
GetSetupById(...)GetSetupByIdAsync(...) - every function gains an Async suffix
ApiGetResult<T>ApiResult<T>
result.ReturnValueresult.Value
Task<bool> from Update and Delete functionsTask<ApiResult>
Task<T> from attachment functionsTask<ApiResult<T>>
required ApiGetOptions argumentoptional ApiGetOptions argument
ApiClientConfigApiClientV2Config
static ApiClient.DefaultTimeoutSecondsApiClientV2Config.Timeout and the per-request timeout argument