using System.Collections.Concurrent; using System.Net.Http.Json; using Microsoft.Extensions.Logging; using Dapr.Client; using MaksIT.Results; using MaksIT.Core.Extensions; namespace MaksIT.Dapr.Services; /// /// Dapr service invocation with outcomes. /// public interface IDaprInvocationService { /// /// Invokes a method with no request body (HTTP POST). /// Task InvokeAsync(string appId, string methodName, CancellationToken cancellationToken = default); /// /// Invokes a method with a JSON request body (HTTP POST). /// Task InvokeAsync(string appId, string methodName, TRequest data, CancellationToken cancellationToken = default); /// /// Invokes a method with no request body and deserializes the JSON response (HTTP POST). /// Task> InvokeAsync(string appId, string methodName, CancellationToken cancellationToken = default); /// /// Invokes a method with a JSON request body and deserializes the JSON response (HTTP POST). /// Task> InvokeAsync(string appId, string methodName, TRequest data, CancellationToken cancellationToken = default); } /// /// Default using . /// public sealed class DaprInvocationService( ILogger logger, DaprClient client ) : IDaprInvocationService, IDisposable { private const string ErrorMessage = "MaksIT.Dapr - Invocation error"; private readonly ConcurrentDictionary _clients = new(StringComparer.Ordinal); private bool _disposed; /// public async Task InvokeAsync(string appId, string methodName, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(appId) || string.IsNullOrWhiteSpace(methodName)) return Result.BadRequest("appId and methodName are required."); try { using var response = await GetClient(appId).PostAsync(methodName, content: null, cancellationToken); response.EnsureSuccessStatusCode(); return Result.Ok(); } catch (OperationCanceledException) { throw; } catch (Exception ex) { logger.LogError(ex, ErrorMessage); return Result.InternalServerError([ErrorMessage, .. ex.ExtractMessages()]); } } /// public async Task InvokeAsync(string appId, string methodName, TRequest data, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(appId) || string.IsNullOrWhiteSpace(methodName)) return Result.BadRequest("appId and methodName are required."); try { using var response = await GetClient(appId).PostAsJsonAsync(methodName, data, client.JsonSerializerOptions, cancellationToken); response.EnsureSuccessStatusCode(); return Result.Ok(); } catch (OperationCanceledException) { throw; } catch (Exception ex) { logger.LogError(ex, ErrorMessage); return Result.InternalServerError([ErrorMessage, .. ex.ExtractMessages()]); } } /// public async Task> InvokeAsync(string appId, string methodName, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(appId) || string.IsNullOrWhiteSpace(methodName)) return Result.BadRequest(default, "appId and methodName are required."); try { using var response = await GetClient(appId).PostAsync(methodName, content: null, cancellationToken); response.EnsureSuccessStatusCode(); var body = await response.Content.ReadFromJsonAsync(client.JsonSerializerOptions, cancellationToken); return Result.Ok(body); } catch (OperationCanceledException) { throw; } catch (Exception ex) { logger.LogError(ex, ErrorMessage); return Result.InternalServerError(default, [ErrorMessage, .. ex.ExtractMessages()]); } } /// public async Task> InvokeAsync( string appId, string methodName, TRequest data, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(appId) || string.IsNullOrWhiteSpace(methodName)) return Result.BadRequest(default, "appId and methodName are required."); try { using var response = await GetClient(appId).PostAsJsonAsync(methodName, data, client.JsonSerializerOptions, cancellationToken); response.EnsureSuccessStatusCode(); var body = await response.Content.ReadFromJsonAsync(client.JsonSerializerOptions, cancellationToken); return Result.Ok(body); } catch (OperationCanceledException) { throw; } catch (Exception ex) { logger.LogError(ex, ErrorMessage); return Result.InternalServerError(default, [ErrorMessage, .. ex.ExtractMessages()]); } } /// public void Dispose() { if (_disposed) return; _disposed = true; foreach (var http in _clients.Values) http.Dispose(); _clients.Clear(); } private HttpClient GetClient(string appId) { ObjectDisposedException.ThrowIf(_disposed, this); if (_clients.TryGetValue(appId, out var existing)) return existing; var created = client.CreateInvokableHttpClient(appId); if (_clients.TryAdd(appId, created)) return created; created.Dispose(); return _clients[appId]; } }