Files
ReC/src/ReC.Client/ReCClient.cs
TekH 278fcfd75b Remove obsolete sync API methods from ReCClient
All synchronous (blocking) HTTP API methods have been removed from the ReCClient class, leaving only their asynchronous counterparts. These sync methods were previously marked as [Obsolete] and simply wrapped the async methods with .GetAwaiter().GetResult(). Additionally, the ToJsonContent method's return type was updated from HttpContent to JsonContent for improved type specificity.
2026-01-16 11:04:11 +01:00

500 lines
27 KiB
C#

using Microsoft.Extensions.DependencyInjection;
using System;
using System.Globalization;
using System.Linq;
using System.Net.Http.Json;
using System.Threading;
using System.Threading.Tasks;
#if NETFRAMEWORK
using System.Net.Http;
#endif
namespace ReC.Client
{
/// <summary>
/// A client for interacting with the ReC API.
/// </summary>
public class ReCClient
{
private readonly HttpClient _http;
/// <summary>
/// A unique name for the HttpClient used by the ReCClient.
/// </summary>
public static readonly string ClientName = Guid.NewGuid().ToString();
/// <summary>
/// Initializes a new instance of the <see cref="ReCClient"/> class.
/// </summary>
/// <param name="httpClientFactory">The factory to create HttpClients.</param>
public ReCClient(IHttpClientFactory httpClientFactory)
{
_http = httpClientFactory.CreateClient(ClientName);
}
#if NETFRAMEWORK
private static string BuildQuery(params (string Key, object Value)[] parameters)
#else
private static string BuildQuery(params (string Key, object? Value)[] parameters)
#endif
{
var parts = parameters
.Where(p => p.Value != null)
.Select(p => $"{Uri.EscapeDataString(p.Key)}={Uri.EscapeDataString(Convert.ToString(p.Value, CultureInfo.InvariantCulture) ?? string.Empty)}");
var query = string.Join("&", parts);
return string.IsNullOrWhiteSpace(query) ? string.Empty : $"?{query}";
}
private static JsonContent ToJsonContent<T>(T payload) => JsonContent.Create(payload);
#region RecActionController
/// <summary>
/// Asynchronously invokes a ReC action for a specific profile.
/// </summary>
/// <remarks>
/// This method sends a POST request to the <c>api/RecAction/invoke/{profileId}</c> endpoint.
/// </remarks>
/// <param name="profileId">The ID of the profile to invoke the action for.</param>
/// <param name="cancellationToken">A token to cancel the asynchronous operation.</param>
/// <returns>A <see cref="Task{TResult}"/> that represents the asynchronous operation. The task result is <see langword="true"/> if the request was successful; otherwise, <see langword="false"/>.</returns>
public async Task<bool> InvokeRecActionAsync(int profileId, CancellationToken cancellationToken = default)
{
var resp = await _http.PostAsync($"api/RecAction/invoke/{profileId}", content: null, cancellationToken);
return resp.IsSuccessStatusCode;
}
/// <summary>
/// Asynchronously retrieves a list of ReC actions for the configured profile.
/// </summary>
/// <remarks>
/// This method sends a GET request to the <c>api/RecAction</c> endpoint with optional query parameters.
/// </remarks>
/// <param name="profileId">The ID of the profile to retrieve actions for. If <c>null</c>, actions for all profiles are retrieved.</param>
/// <param name="invoked">Filter for invoked actions. If <c>null</c>, both invoked and not invoked actions are retrieved.</param>
/// <param name="cancel">A token to cancel the asynchronous operation.</param>
/// <returns>A task representing the asynchronous operation, with a <see cref="HttpResponseMessage"/> result containing the response data.</returns>
public Task<HttpResponseMessage> GetRecActionsAsync(long? profileId = null, bool? invoked = null, CancellationToken cancel = default)
{
var query = BuildQuery(("ProfileId", profileId), ("Invoked", invoked));
return _http.GetAsync($"api/RecAction{query}", cancel);
}
/// <summary>
/// Asynchronously creates a new ReC action.
/// </summary>
/// <remarks>
/// This method sends a POST request to the <c>api/RecAction</c> endpoint with the action data as JSON.
/// </remarks>
/// <param name="procedure">The action data to create.</param>
/// <param name="cancel">A token to cancel the asynchronous operation.</param>
/// <returns>A task representing the asynchronous operation, with a <see cref="HttpResponseMessage"/> result containing the response data.</returns>
public Task<HttpResponseMessage> CreateRecActionAsync<T>(T procedure, CancellationToken cancel = default)
=> _http.PostAsync("api/RecAction", ToJsonContent(procedure), cancel);
/// <summary>
/// Asynchronously updates an existing ReC action.
/// </summary>
/// <remarks>
/// This method sends a PUT request to the <c>api/RecAction/{id}</c> endpoint with the updated action data as JSON.
/// </remarks>
/// <param name="id">The ID of the action to update.</param>
/// <param name="procedure">The updated action data.</param>
/// <param name="cancel">A token to cancel the asynchronous operation.</param>
/// <returns>A task representing the asynchronous operation, with a <see cref="HttpResponseMessage"/> result containing the response data.</returns>
public Task<HttpResponseMessage> UpdateRecActionAsync<T>(long id, T procedure, CancellationToken cancel = default)
=> _http.PutAsync($"api/RecAction/{id}", ToJsonContent(procedure), cancel);
/// <summary>
/// Asynchronously deletes one or more ReC actions.
/// </summary>
/// <remarks>
/// This method sends a DELETE request to the <c>api/RecAction</c> endpoint with the IDs of the actions to delete as JSON.
/// </remarks>
/// <param name="procedure">An object containing the IDs of the actions to delete.</param>
/// <param name="cancel">A token to cancel the asynchronous operation.</param>
/// <returns>A task representing the asynchronous operation, with a <see cref="HttpResponseMessage"/> result containing the response data.</returns>
public Task<HttpResponseMessage> DeleteRecActionsAsync<T>(T procedure, CancellationToken cancel = default)
{
var request = new HttpRequestMessage(HttpMethod.Delete, "api/RecAction")
{
Content = ToJsonContent(procedure)
};
return _http.SendAsync(request, cancel);
}
#endregion
#region OutResController
/// <summary>
/// Asynchronously retrieves results for the configured profile.
/// </summary>
/// <remarks>
/// This method sends a GET request to the <c>api/OutRes</c> endpoint with optional query parameters.
/// </remarks>
/// <param name="id">Filter by result ID.</param>
/// <param name="actionId">Filter by action ID.</param>
/// <param name="profileId">Filter by profile ID.</param>
/// <param name="cancel">A token to cancel the asynchronous operation.</param>
/// <returns>A task representing the asynchronous operation, with a <see cref="HttpResponseMessage"/> result containing the response data.</returns>
public Task<HttpResponseMessage> GetResultsAsync(long? id = null, long? actionId = null, long? profileId = null, CancellationToken cancel = default)
{
var query = BuildQuery(("Id", id), ("ActionId", actionId), ("ProfileId", profileId));
return _http.GetAsync($"api/OutRes{query}", cancel);
}
/// <summary>
/// Asynchronously creates a new result.
/// </summary>
/// <remarks>
/// This method sends a POST request to the <c>api/OutRes</c> endpoint with the result data as JSON.
/// </remarks>
/// <param name="procedure">The result data to create.</param>
/// <param name="cancel">A token to cancel the asynchronous operation.</param>
/// <returns>A task representing the asynchronous operation, with a <see cref="HttpResponseMessage"/> result containing the response data.</returns>
public Task<HttpResponseMessage> CreateResultAsync<T>(T procedure, CancellationToken cancel = default)
=> _http.PostAsync("api/OutRes", ToJsonContent(procedure), cancel);
/// <summary>
/// Asynchronously updates an existing result.
/// </summary>
/// <remarks>
/// This method sends a PUT request to the <c>api/OutRes/{id}</c> endpoint with the updated result data as JSON.
/// </remarks>
/// <param name="id">The ID of the result to update.</param>
/// <param name="procedure">The updated result data.</param>
/// <param name="cancel">A token to cancel the asynchronous operation.</param>
/// <returns>A task representing the asynchronous operation, with a <see cref="HttpResponseMessage"/> result containing the response data.</returns>
public Task<HttpResponseMessage> UpdateResultAsync<T>(long id, T procedure, CancellationToken cancel = default)
=> _http.PutAsync($"api/OutRes/{id}", ToJsonContent(procedure), cancel);
/// <summary>
/// Asynchronously deletes one or more results.
/// </summary>
/// <remarks>
/// This method sends a DELETE request to the <c>api/OutRes</c> endpoint with the IDs of the results to delete as JSON.
/// </remarks>
/// <param name="procedure">An object containing the IDs of the results to delete.</param>
/// <param name="cancel">A token to cancel the asynchronous operation.</param>
/// <returns>A task representing the asynchronous operation, with a <see cref="HttpResponseMessage"/> result containing the response data.</returns>
public Task<HttpResponseMessage> DeleteResultsAsync<T>(T procedure, CancellationToken cancel = default)
{
var request = new HttpRequestMessage(HttpMethod.Delete, "api/OutRes")
{
Content = ToJsonContent(procedure)
};
return _http.SendAsync(request, cancel);
}
#endregion
#region ProfileController
/// <summary>
/// Asynchronously retrieves a profile by ID.
/// </summary>
/// <remarks>
/// This method sends a GET request to the <c>api/Profile</c> endpoint with the profile ID as a query parameter.
/// </remarks>
/// <param name="id">The ID of the profile to retrieve.</param>
/// <param name="includeActions">Whether to include associated actions in the response.</param>
/// <param name="cancel">A token to cancel the asynchronous operation.</param>
/// <returns>A task representing the asynchronous operation, with a <see cref="HttpResponseMessage"/> result containing the response data.</returns>
public Task<HttpResponseMessage> GetProfileAsync(long id, bool includeActions = false, CancellationToken cancel = default)
{
var query = BuildQuery(("Id", id), ("IncludeActions", includeActions));
return _http.GetAsync($"api/Profile{query}", cancel);
}
/// <summary>
/// Asynchronously creates a new profile.
/// </summary>
/// <remarks>
/// This method sends a POST request to the <c>api/Profile</c> endpoint with the profile data as JSON.
/// </remarks>
/// <param name="procedure">The profile data to create.</param>
/// <param name="cancel">A token to cancel the asynchronous operation.</param>
/// <returns>A task representing the asynchronous operation, with a <see cref="HttpResponseMessage"/> result containing the response data.</returns>
public Task<HttpResponseMessage> CreateProfileAsync<T>(T procedure, CancellationToken cancel = default)
=> _http.PostAsync("api/Profile", ToJsonContent(procedure), cancel);
/// <summary>
/// Asynchronously updates an existing profile.
/// </summary>
/// <remarks>
/// This method sends a PUT request to the <c>api/Profile/{id}</c> endpoint with the updated profile data as JSON.
/// </remarks>
/// <param name="id">The ID of the profile to update.</param>
/// <param name="procedure">The updated profile data.</param>
/// <param name="cancel">A token to cancel the asynchronous operation.</param>
/// <returns>A task representing the asynchronous operation, with a <see cref="HttpResponseMessage"/> result containing the response data.</returns>
public Task<HttpResponseMessage> UpdateProfileAsync<T>(long id, T procedure, CancellationToken cancel = default)
=> _http.PutAsync($"api/Profile/{id}", ToJsonContent(procedure), cancel);
/// <summary>
/// Asynchronously deletes one or more profiles.
/// </summary>
/// <remarks>
/// This method sends a DELETE request to the <c>api/Profile</c> endpoint with the IDs of the profiles to delete as JSON.
/// </remarks>
/// <param name="procedure">An object containing the IDs of the profiles to delete.</param>
/// <param name="cancel">A token to cancel the asynchronous operation.</param>
/// <returns>A task representing the asynchronous operation, with a <see cref="HttpResponseMessage"/> result containing the response data.</returns>
public Task<HttpResponseMessage> DeleteProfilesAsync<T>(T procedure, CancellationToken cancel = default)
{
var request = new HttpRequestMessage(HttpMethod.Delete, "api/Profile")
{
Content = ToJsonContent(procedure)
};
return _http.SendAsync(request, cancel);
}
#endregion
#region EndpointAuthController
/// <summary>
/// Asynchronously creates a new endpoint authentication.
/// </summary>
/// <remarks>
/// This method sends a POST request to the <c>api/EndpointAuth</c> endpoint with the authentication data as JSON.
/// </remarks>
/// <param name="procedure">The authentication data to create.</param>
/// <param name="cancel">A token to cancel the asynchronous operation.</param>
/// <returns>A task representing the asynchronous operation, with a <see cref="HttpResponseMessage"/> result containing the response data.</returns>
public Task<HttpResponseMessage> CreateEndpointAuthAsync<T>(T procedure, CancellationToken cancel = default)
=> _http.PostAsync("api/EndpointAuth", ToJsonContent(procedure), cancel);
/// <summary>
/// Asynchronously updates an existing endpoint authentication.
/// </summary>
/// <remarks>
/// This method sends a PUT request to the <c>api/EndpointAuth/{id}</c> endpoint with the updated authentication data as JSON.
/// </remarks>
/// <param name="id">The ID of the authentication to update.</param>
/// <param name="procedure">The updated authentication data.</param>
/// <param name="cancel">A token to cancel the asynchronous operation.</param>
/// <returns>A task representing the asynchronous operation, with a <see cref="HttpResponseMessage"/> result containing the response data.</returns>
public Task<HttpResponseMessage> UpdateEndpointAuthAsync<T>(long id, T procedure, CancellationToken cancel = default)
=> _http.PutAsync($"api/EndpointAuth/{id}", ToJsonContent(procedure), cancel);
/// <summary>
/// Asynchronously deletes one or more endpoint authentications.
/// </summary>
/// <remarks>
/// This method sends a DELETE request to the <c>api/EndpointAuth</c> endpoint with the IDs of the authentications to delete as JSON.
/// </remarks>
/// <param name="procedure">An object containing the IDs of the authentications to delete.</param>
/// <param name="cancel">A token to cancel the asynchronous operation.</param>
/// <returns>A task representing the asynchronous operation, with a <see cref="HttpResponseMessage"/> result containing the response data.</returns>
public Task<HttpResponseMessage> DeleteEndpointAuthAsync<T>(T procedure, CancellationToken cancel = default)
{
var request = new HttpRequestMessage(HttpMethod.Delete, "api/EndpointAuth")
{
Content = ToJsonContent(procedure)
};
return _http.SendAsync(request, cancel);
}
#endregion
#region EndpointParamsController
/// <summary>
/// Asynchronously creates new endpoint parameters.
/// </summary>
/// <remarks>
/// This method sends a POST request to the <c>api/EndpointParams</c> endpoint with the parameters data as JSON.
/// </remarks>
/// <param name="procedure">The parameters data to create.</param>
/// <param name="cancel">A token to cancel the asynchronous operation.</param>
/// <returns>A task representing the asynchronous operation, with a <see cref="HttpResponseMessage"/> result containing the response data.</returns>
public Task<HttpResponseMessage> CreateEndpointParamsAsync<T>(T procedure, CancellationToken cancel = default)
=> _http.PostAsync("api/EndpointParams", ToJsonContent(procedure), cancel);
/// <summary>
/// Asynchronously updates existing endpoint parameters.
/// </summary>
/// <remarks>
/// This method sends a PUT request to the <c>api/EndpointParams/{id}</c> endpoint with the updated parameters data as JSON.
/// </remarks>
/// <param name="id">The ID of the parameters to update.</param>
/// <param name="procedure">The updated parameters data.</param>
/// <param name="cancel">A token to cancel the asynchronous operation.</param>
/// <returns>A task representing the asynchronous operation, with a <see cref="HttpResponseMessage"/> result containing the response data.</returns>
public Task<HttpResponseMessage> UpdateEndpointParamsAsync<T>(long id, T procedure, CancellationToken cancel = default)
=> _http.PutAsync($"api/EndpointParams/{id}", ToJsonContent(procedure), cancel);
/// <summary>
/// Asynchronously deletes one or more endpoint parameters.
/// </summary>
/// <remarks>
/// This method sends a DELETE request to the <c>api/EndpointParams</c> endpoint with the IDs of the parameters to delete as JSON.
/// </remarks>
/// <param name="procedure">An object containing the IDs of the parameters to delete.</param>
/// <param name="cancel">A token to cancel the asynchronous operation.</param>
/// <returns>A task representing the asynchronous operation, with a <see cref="HttpResponseMessage"/> result containing the response data.</returns>
public Task<HttpResponseMessage> DeleteEndpointParamsAsync<T>(T procedure, CancellationToken cancel = default)
{
var request = new HttpRequestMessage(HttpMethod.Delete, "api/EndpointParams")
{
Content = ToJsonContent(procedure)
};
return _http.SendAsync(request, cancel);
}
#endregion
#region EndpointsController
/// <summary>
/// Asynchronously creates a new endpoint.
/// </summary>
/// <remarks>
/// This method sends a POST request to the <c>api/Endpoints</c> endpoint with the endpoint data as JSON.
/// </remarks>
/// <param name="procedure">The endpoint data to create.</param>
/// <param name="cancel">A token to cancel the asynchronous operation.</param>
/// <returns>A task representing the asynchronous operation, with a <see cref="HttpResponseMessage"/> result containing the response data.</returns>
public Task<HttpResponseMessage> CreateEndpointAsync<T>(T procedure, CancellationToken cancel = default)
=> _http.PostAsync("api/Endpoints", ToJsonContent(procedure), cancel);
/// <summary>
/// Asynchronously updates an existing endpoint.
/// </summary>
/// <remarks>
/// This method sends a PUT request to the <c>api/Endpoints/{id}</c> endpoint with the updated endpoint data as JSON.
/// </remarks>
/// <param name="id">The ID of the endpoint to update.</param>
/// <param name="procedure">The updated endpoint data.</param>
/// <param name="cancel">A token to cancel the asynchronous operation.</param>
/// <returns>A task representing the asynchronous operation, with a <see cref="HttpResponseMessage"/> result containing the response data.</returns>
public Task<HttpResponseMessage> UpdateEndpointAsync<T>(long id, T procedure, CancellationToken cancel = default)
=> _http.PutAsync($"api/Endpoints/{id}", ToJsonContent(procedure), cancel);
/// <summary>
/// Asynchronously deletes one or more endpoints.
/// </summary>
/// <remarks>
/// This method sends a DELETE request to the <c>api/Endpoints</c> endpoint with the IDs of the endpoints to delete as JSON.
/// </remarks>
/// <param name="procedure">An object containing the IDs of the endpoints to delete.</param>
/// <param name="cancel">A token to cancel the asynchronous operation.</param>
/// <returns>A task representing the asynchronous operation, with a <see cref="HttpResponseMessage"/> result containing the response data.</returns>
public Task<HttpResponseMessage> DeleteEndpointAsync<T>(T procedure, CancellationToken cancel = default)
{
var request = new HttpRequestMessage(HttpMethod.Delete, "api/Endpoints")
{
Content = ToJsonContent(procedure)
};
return _http.SendAsync(request, cancel);
}
#endregion
#region CommonController
/// <summary>
/// Asynchronously creates a new object.
/// </summary>
/// <remarks>
/// This method sends a POST request to the <c>api/Common</c> endpoint with the object data as JSON.
/// </remarks>
/// <param name="procedure">The object data to create.</param>
/// <param name="cancel">A token to cancel the asynchronous operation.</param>
/// <returns>A task representing the asynchronous operation, with a <see cref="HttpResponseMessage"/> result containing the response data.</returns>
public Task<HttpResponseMessage> CreateObjectAsync<T>(T procedure, CancellationToken cancel = default)
=> _http.PostAsync("api/Common", ToJsonContent(procedure), cancel);
/// <summary>
/// Asynchronously updates an existing object.
/// </summary>
/// <remarks>
/// This method sends a PUT request to the <c>api/Common</c> endpoint with the updated object data as JSON.
/// </remarks>
/// <param name="procedure">The updated object data.</param>
/// <param name="cancel">A token to cancel the asynchronous operation.</param>
/// <returns>A task representing the asynchronous operation, with a <see cref="HttpResponseMessage"/> result containing the response data.</returns>
public Task<HttpResponseMessage> UpdateObjectAsync<T>(T procedure, CancellationToken cancel = default)
=> _http.PutAsync("api/Common", ToJsonContent(procedure), cancel);
/// <summary>
/// Asynchronously deletes one or more objects.
/// </summary>
/// <remarks>
/// This method sends a DELETE request to the <c>api/Common</c> endpoint with the IDs of the objects to delete as JSON.
/// </remarks>
/// <param name="procedure">An object containing the IDs of the objects to delete.</param>
/// <param name="cancel">A token to cancel the asynchronous operation.</param>
/// <returns>A task representing the asynchronous operation, with a <see cref="HttpResponseMessage"/> result containing the response data.</returns>
public Task<HttpResponseMessage> DeleteObjectAsync<T>(T procedure, CancellationToken cancel = default)
{
var request = new HttpRequestMessage(HttpMethod.Delete, "api/Common")
{
Content = ToJsonContent(procedure)
};
return _http.SendAsync(request, cancel);
}
#endregion
#region Static
private static readonly IServiceCollection Services = new ServiceCollection();
#if NET8_0_OR_GREATER
private static IServiceProvider? Provider = null;
#else
private static IServiceProvider Provider = null;
#endif
/// <summary>
/// Configures and builds the static <see cref="IServiceProvider"/> for creating <see cref="ReCClient"/> instances.
/// </summary>
/// <remarks>
/// This method should only be called once during application startup.
/// </remarks>
/// <param name="apiUri">The base URI of the ReC API.</param>
/// <exception cref="InvalidOperationException">Thrown if the static provider has already been built.</exception>
[Obsolete("Use a local service collection instead of the static provider.")]
public static void BuildStaticClient(string apiUri)
{
if(Provider != null)
throw new InvalidOperationException("Static Provider is already built.");
Services.AddRecClient(apiUri);
Provider = Services.BuildServiceProvider();
}
/// <summary>
/// Configures and builds the static <see cref="IServiceProvider"/> for creating <see cref="ReCClient"/> instances.
/// </summary>
/// <remarks>
/// This method should only be called once during application startup.
/// </remarks>
/// <param name="configureClient">An action to configure the <see cref="HttpClient"/>.</param>
/// <exception cref="InvalidOperationException">Thrown if the static provider has already been built.</exception>
[Obsolete("Use a local service collection instead of the static provider.")]
public static void BuildStaticClient(Action<HttpClient> configureClient)
{
if (Provider != null)
throw new InvalidOperationException("Static Provider is already built.");
Services.AddRecClient(configureClient);
Provider = Services.BuildServiceProvider();
}
/// <summary>
/// Creates a new <see cref="ReCClient"/> instance using the statically configured provider.
/// </summary>
/// <returns>A new instance of the <see cref="ReCClient"/>.</returns>
/// <exception cref="InvalidOperationException">Thrown if <see cref="BuildStaticClient(string)"/> has not been called yet.</exception>
[Obsolete("Use a local service collection instead of the static provider.")]
public static ReCClient Create()
{
if (Provider == null)
throw new InvalidOperationException("Static Provider is not built. Call BuildStaticClient first.");
return Provider.GetRequiredService<ReCClient>();
}
#endregion
}
/// <summary>
/// Specifies which part of the result to return for result view endpoints.
/// </summary>
public enum ResultType
{
Full,
OnlyHeader,
OnlyBody
}
}