159 lines
5.6 KiB
C#
159 lines
5.6 KiB
C#
using Castle.Core.Logging;
|
|
using System.Web;
|
|
|
|
namespace Pilz.Net.Api;
|
|
|
|
public class ApiClient(string apiUrl) : IApiClient
|
|
{
|
|
protected readonly List<object> clients = [];
|
|
|
|
public event ApiRequestCancelEventHandler? OnApiRequestStart;
|
|
public event ApiRequestEventHandler? OnApiRequestResponse;
|
|
public event ApiRequestEventHandler? OnApiRequestCompleted;
|
|
|
|
public HttpClient HttpClient { get; set; } = new();
|
|
|
|
public virtual string ApiUrl { get; } = apiUrl;
|
|
|
|
public string? AuthKey { get; set; }
|
|
|
|
public virtual IApiMessageSerializer Serializer { get; set; } = new DefaultApiMessageSerializer();
|
|
|
|
public ILogger Log { get; set; } = NullLogger.Instance;
|
|
|
|
public T GetClient<T>() where T : IApiSubClient
|
|
{
|
|
foreach (var c in clients)
|
|
{
|
|
if (c is T t)
|
|
return t;
|
|
}
|
|
|
|
if (T.CreateClient<T>(this) is T client)
|
|
{
|
|
clients.Add(client);
|
|
return client;
|
|
}
|
|
|
|
throw new ApiException("Could not create an instance of this client.");
|
|
}
|
|
|
|
public virtual async Task<ApiResponse> SendRequest(string route, HttpMethod method, ApiMessage? message, ApiParameterCollection? @params, IApiMessageSerializer? serializer)
|
|
{
|
|
Log.InfoFormat("Send message to {0}", route);
|
|
|
|
var context = new ApiRequestContext(route, method, message, @params, serializer, null);
|
|
await Send(context);
|
|
|
|
var response = new ApiResponse(context.HttpResponse);
|
|
context.ApiResponse = response;
|
|
|
|
return response;
|
|
}
|
|
|
|
public virtual async Task<ApiResponse<TResponse>> SendRequest<TResponse>(string route, HttpMethod method, ApiMessage? message, ApiParameterCollection? @params, IApiMessageSerializer? serializer) where TResponse : ApiMessage
|
|
{
|
|
Log.InfoFormat("Send request to {0}", route);
|
|
|
|
var context = new ApiRequestContext(route, method, message, @params, serializer, typeof(TResponse));
|
|
await Send(context);
|
|
TResponse? result = null;
|
|
|
|
if (context.HttpResponse != null && context.HttpResponse.IsSuccessStatusCode)
|
|
{
|
|
var mediaType = context.HttpResponse.Content.Headers.ContentType?.MediaType;
|
|
if (!string.IsNullOrWhiteSpace(mediaType))
|
|
{
|
|
if (mediaType == "application/json" && context.Serializer != null)
|
|
result = context.Serializer.Deserialize(await context.HttpResponse.Content.ReadAsStringAsync(), typeof(TResponse)) as TResponse;
|
|
else if (typeof(TResponse).IsAssignableTo(typeof(ApiRawByteMessage)) && mediaType == "application/octet-stream")
|
|
result = (TResponse)(object)new ApiRawByteMessage(await context.HttpResponse.Content.ReadAsByteArrayAsync());
|
|
else if (typeof(TResponse).IsAssignableTo(typeof(ApiRawStreamMessage)) && mediaType == "application/octet-stream")
|
|
result = (TResponse)(object)new ApiRawStreamMessage(await context.HttpResponse.Content.ReadAsStreamAsync());
|
|
}
|
|
}
|
|
|
|
var response = new ApiResponse<TResponse>(context.HttpResponse, result);
|
|
context.ApiResponse = response;
|
|
|
|
return response;
|
|
}
|
|
|
|
protected virtual async Task Send(ApiRequestContext context)
|
|
{
|
|
var url = ApiUrl + context.Route + BuildParameters(context.Params);
|
|
HttpContent content;
|
|
context.Serializer ??= Serializer;
|
|
|
|
Log.DebugFormat("Api endpoint url is {0}", url);
|
|
|
|
if (OnApiRequestStart != null)
|
|
{
|
|
var args = new ApiRequestCancelEventArgs(context);
|
|
OnApiRequestStart.Invoke(this, args);
|
|
if (args.Cancel)
|
|
{
|
|
context.Canceled = true;
|
|
return;
|
|
}
|
|
}
|
|
|
|
Log.Debug("Create content");
|
|
|
|
if (context.Message is ApiRawByteMessage messageRawBytes)
|
|
{
|
|
content = new ByteArrayContent(messageRawBytes.Data);
|
|
content.Headers.ContentType = new("application/octet-stream");
|
|
content.Headers.ContentLength = messageRawBytes.Data.Length;
|
|
}
|
|
else if (context.Message is ApiRawStreamMessage messageRawStream)
|
|
{
|
|
content = new StreamContent(messageRawStream.Data);
|
|
content.Headers.ContentType = new("application/octet-stream");
|
|
content.Headers.ContentLength = messageRawStream.Data.Length;
|
|
}
|
|
else if (context.Message is not null)
|
|
content = new StringContent(context.Serializer.Serialize(context.Message)!, null, "application/json");
|
|
else
|
|
content = new StringContent(string.Empty, null, "application/json");
|
|
|
|
Log.Debug("Build headers");
|
|
|
|
content.Headers.Add("API-AUTH-KEY", EncodeAuthKey());
|
|
|
|
Log.Debug("Sending request");
|
|
|
|
var httpmsg = new HttpRequestMessage(context.Method, url)
|
|
{
|
|
Content = content
|
|
};
|
|
|
|
context.HttpResponse = await HttpClient.SendAsync(httpmsg);
|
|
|
|
OnApiRequestResponse?.Invoke(this, new(context));
|
|
}
|
|
|
|
protected virtual string BuildParameters(ApiParameterCollection? @params)
|
|
{
|
|
if (@params == null || @params.Count == 0)
|
|
return string.Empty;
|
|
|
|
return "?" + string.Join("&", @params.Select(kvp =>
|
|
{
|
|
var key = kvp.Key;
|
|
var value = HttpUtility.UrlEncode(ConvertParameter(kvp.Value));
|
|
return $"{key}={value}";
|
|
}));
|
|
}
|
|
|
|
protected virtual string? ConvertParameter(object? @param)
|
|
{
|
|
return (string?)Convert.ChangeType(@param, typeof(string));
|
|
}
|
|
|
|
protected virtual string? EncodeAuthKey()
|
|
{
|
|
return AuthKey;
|
|
}
|
|
}
|