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];
}
}