From 9557e105b953b36b579b09e770fd5f76a5486f70 Mon Sep 17 00:00:00 2001 From: Pilzinsel64 Date: Wed, 28 May 2025 10:31:16 +0200 Subject: [PATCH] api: add events to control api requests via context --- Pilz.Net/Api/ApiClient.cs | 62 +++++++++++++++-------- Pilz.Net/Api/ApiRequestCancelEventArgs.cs | 10 ++++ Pilz.Net/Api/ApiRequestContext.cs | 24 +++++++++ Pilz.Net/Api/ApiRequestEventArgs.cs | 8 +++ Pilz.Net/Api/ApiResponse.cs | 4 +- Pilz.Net/Api/ApiResponse{T}.cs | 4 +- 6 files changed, 88 insertions(+), 24 deletions(-) create mode 100644 Pilz.Net/Api/ApiRequestCancelEventArgs.cs create mode 100644 Pilz.Net/Api/ApiRequestContext.cs create mode 100644 Pilz.Net/Api/ApiRequestEventArgs.cs diff --git a/Pilz.Net/Api/ApiClient.cs b/Pilz.Net/Api/ApiClient.cs index 1ee1255..a9be748 100644 --- a/Pilz.Net/Api/ApiClient.cs +++ b/Pilz.Net/Api/ApiClient.cs @@ -7,6 +7,10 @@ public class ApiClient(string apiUrl) : IApiClient { protected readonly List 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; @@ -36,50 +40,66 @@ public class ApiClient(string apiUrl) : IApiClient public virtual async Task SendRequest(string route, HttpMethod method, ApiMessage? message, ApiParameterCollection? @params, IApiMessageSerializer? serializer) { - serializer ??= Serializer; - Log.InfoFormat("Send message to {0}", route); - var res = await Send(route, method, message, @params, serializer); + var context = new ApiRequestContext(route, method, message, @params, serializer, null); + await Send(context); - return new(res); + var response = new ApiResponse(context.HttpResponse); + context.ApiResponse = response; + + return response; } public virtual async Task> SendRequest(string route, HttpMethod method, ApiMessage? message, ApiParameterCollection? @params, IApiMessageSerializer? serializer) where TResponse : ApiMessage { - serializer ??= Serializer; - Log.InfoFormat("Send request to {0}", route); - - var res = await Send(route, method, message, @params, serializer); + + var context = new ApiRequestContext(route, method, message, @params, serializer, typeof(TResponse)); + await Send(context); TResponse? result = null; - if (res.IsSuccessStatusCode) + if (context.HttpResponse != null && context.HttpResponse.IsSuccessStatusCode) { - var mediaType = res.Content.Headers.ContentType?.MediaType; + var mediaType = context.HttpResponse.Content.Headers.ContentType?.MediaType; if (!string.IsNullOrWhiteSpace(mediaType)) { - if (mediaType == "application/json") - result = serializer.Deserialize(await res.Content.ReadAsStringAsync(), typeof(TResponse)) as TResponse; + 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(ApiRawMessage)) && mediaType == "application/octet-stream") - result = (TResponse)(object)new ApiRawMessage(await res.Content.ReadAsByteArrayAsync()); + result = (TResponse)(object)new ApiRawMessage(await context.HttpResponse.Content.ReadAsByteArrayAsync()); } } - return new(res, result); + var response = new ApiResponse(context.HttpResponse, result); + context.ApiResponse = response; + + return response; } - protected virtual async Task Send(string route, HttpMethod method, ApiMessage? message, ApiParameterCollection? @params, IApiMessageSerializer serializer) + protected virtual async Task Send(ApiRequestContext context) { - var url = ApiUrl + route + BuildParameters(@params); + 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 (message is not null) - content = new StringContent(serializer.Serialize(message)!, null, "application/json"); + 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"); @@ -89,12 +109,14 @@ public class ApiClient(string apiUrl) : IApiClient Log.Debug("Sending request"); - var httpmsg = new HttpRequestMessage(method, url) + var httpmsg = new HttpRequestMessage(context.Method, url) { Content = content }; - return await HttpClient.SendAsync(httpmsg); + context.HttpResponse = await HttpClient.SendAsync(httpmsg); + + OnApiRequestResponse?.Invoke(this, new(context)); } protected virtual string BuildParameters(ApiParameterCollection? @params) diff --git a/Pilz.Net/Api/ApiRequestCancelEventArgs.cs b/Pilz.Net/Api/ApiRequestCancelEventArgs.cs new file mode 100644 index 0000000..f942e56 --- /dev/null +++ b/Pilz.Net/Api/ApiRequestCancelEventArgs.cs @@ -0,0 +1,10 @@ +using System.ComponentModel; + +namespace Pilz.Net.Api; + +public delegate void ApiRequestCancelEventHandler(object sender, ApiRequestCancelEventArgs e); + +public class ApiRequestCancelEventArgs(ApiRequestContext context) : CancelEventArgs +{ + public ApiRequestContext Context => context; +} diff --git a/Pilz.Net/Api/ApiRequestContext.cs b/Pilz.Net/Api/ApiRequestContext.cs new file mode 100644 index 0000000..9f2fcf7 --- /dev/null +++ b/Pilz.Net/Api/ApiRequestContext.cs @@ -0,0 +1,24 @@ +namespace Pilz.Net.Api; + +public class ApiRequestContext +{ + public bool Canceled { get; internal set; } + public string Route { get; set; } + public HttpMethod Method { get; set; } + public ApiMessage? Message { get; set; } + public ApiParameterCollection? Params { get; } + public IApiMessageSerializer? Serializer { get; set; } + public Type? ResponseMessageType { get; } + public HttpResponseMessage? HttpResponse { get; internal set; } + public object? ApiResponse { get; internal set; } + + public ApiRequestContext(string route, HttpMethod method, ApiMessage? message, ApiParameterCollection? @params, IApiMessageSerializer? serializer, Type? responseMessageType) + { + Route = route; + Method = method; + Message = message; + Params = @params; + Serializer = serializer; + ResponseMessageType = responseMessageType; + } +} diff --git a/Pilz.Net/Api/ApiRequestEventArgs.cs b/Pilz.Net/Api/ApiRequestEventArgs.cs new file mode 100644 index 0000000..3c11a14 --- /dev/null +++ b/Pilz.Net/Api/ApiRequestEventArgs.cs @@ -0,0 +1,8 @@ +namespace Pilz.Net.Api; + +public delegate void ApiRequestEventHandler(object sender, ApiRequestEventArgs e); + +public class ApiRequestEventArgs(ApiRequestContext context) : EventArgs +{ + public ApiRequestContext Context => context; +} diff --git a/Pilz.Net/Api/ApiResponse.cs b/Pilz.Net/Api/ApiResponse.cs index 6d3af4f..cc306b5 100644 --- a/Pilz.Net/Api/ApiResponse.cs +++ b/Pilz.Net/Api/ApiResponse.cs @@ -3,9 +3,9 @@ namespace Pilz.Net.Api; public record class ApiResponse( - HttpResponseMessage Response) + HttpResponseMessage? Response) { - public HttpStatusCode StatusCode => Response.StatusCode; + public HttpStatusCode StatusCode => Response?.StatusCode ?? default; public bool IsOk => (int)StatusCode >= 200 && (int)StatusCode <= 299; diff --git a/Pilz.Net/Api/ApiResponse{T}.cs b/Pilz.Net/Api/ApiResponse{T}.cs index fe7dea2..865aa03 100644 --- a/Pilz.Net/Api/ApiResponse{T}.cs +++ b/Pilz.Net/Api/ApiResponse{T}.cs @@ -4,11 +4,11 @@ using System.Net; namespace Pilz.Net.Api; public record class ApiResponse( - HttpResponseMessage Response, + HttpResponseMessage? Response, T? Message) where T : ApiMessage { - public HttpStatusCode StatusCode => Response.StatusCode; + public HttpStatusCode StatusCode => Response?.StatusCode ?? default; [MemberNotNullWhen(true, nameof(Message))] public bool IsOk => (int)StatusCode >= 200 && (int)StatusCode <= 299 && Message is not null;