diff --git a/src/PinguApps.Appwrite.Client/ServiceCollectionExtensions.cs b/src/PinguApps.Appwrite.Client/ServiceCollectionExtensions.cs
index d3b7bb89..38f2ceaf 100644
--- a/src/PinguApps.Appwrite.Client/ServiceCollectionExtensions.cs
+++ b/src/PinguApps.Appwrite.Client/ServiceCollectionExtensions.cs
@@ -5,7 +5,6 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Http;
using Microsoft.Extensions.Logging;
using PinguApps.Appwrite.Client.Clients;
using PinguApps.Appwrite.Client.Handlers;
@@ -19,7 +18,7 @@
namespace PinguApps.Appwrite.Client;
///
-/// Provides extenions to IServiceCollection, to enable adding the SDK to your DI container
+/// Provides extensions to IServiceCollection, to enable adding the SDK to your DI container
///
public static class ServiceCollectionExtensions
{
@@ -38,52 +37,22 @@ public static IServiceCollection AddAppwriteClient(this IServiceCollection servi
var policyOptions = new ResiliencePolicyOptions();
configureResiliencePolicy?.Invoke(policyOptions);
- var customRefitSettings = AddSerializationConfigToRefitSettings(refitSettings);
-
services.AddKeyedSingleton("Client", new Config(endpoint, projectId));
+
+ services.AddSingleton>(sp =>
+ CreateResiliencePolicy(sp.GetRequiredService>(), policyOptions));
+
services.AddTransient();
services.AddTransient();
- services.AddRefitClient(customRefitSettings)
- .ConfigureHttpClient(x => ConfigureHttpClient(x, endpoint))
- .AddHttpMessageHandler()
- .AddHttpMessageHandler(() => new PolicyHttpMessageHandler(GetRetryPolicy(services, policyOptions)))
- .AddHttpMessageHandler(() => new PolicyHttpMessageHandler(GetCircuitBreakerPolicy(services, policyOptions)))
- .AddHttpMessageHandler();
-
- services.AddRefitClient(customRefitSettings)
- .ConfigureHttpClient(x => ConfigureHttpClient(x, endpoint))
- .AddHttpMessageHandler()
- .AddHttpMessageHandler(() => new PolicyHttpMessageHandler(GetRetryPolicy(services, policyOptions)))
- .AddHttpMessageHandler(() => new PolicyHttpMessageHandler(GetCircuitBreakerPolicy(services, policyOptions)))
- .AddHttpMessageHandler();
+ var customRefitSettings = AddSerializationConfigToRefitSettings(refitSettings);
- services.AddRefitClient(customRefitSettings)
- .ConfigureHttpClient(x => ConfigureHttpClient(x, endpoint))
- .AddHttpMessageHandler()
- .AddHttpMessageHandler(() => new PolicyHttpMessageHandler(GetRetryPolicy(services, policyOptions)))
- .AddHttpMessageHandler(() => new PolicyHttpMessageHandler(GetCircuitBreakerPolicy(services, policyOptions)))
- .AddHttpMessageHandler();
+ RegisterRefitClient(services, customRefitSettings, endpoint, true);
+ RegisterRefitClient(services, customRefitSettings, endpoint, true);
+ RegisterRefitClient(services, customRefitSettings, endpoint, true);
- services.AddSingleton(sp =>
- {
- var api = sp.GetRequiredService();
- var config = sp.GetRequiredKeyedService("Client");
- return new ClientAccountClient(api, config);
- });
- services.AddSingleton(sp =>
- {
- var api = sp.GetRequiredService();
- var config = sp.GetRequiredKeyedService("Client");
- return new ClientTeamsClient(api, config);
- });
- services.AddSingleton(sp =>
- {
- var api = sp.GetRequiredService();
- return new ClientDatabasesClient(api);
- });
- services.AddSingleton();
- services.AddSingleton(x => new Lazy(() => x.GetRequiredService()));
+ // Register business logic clients
+ RegisterBusinessClients(services, true);
return services;
}
@@ -103,80 +72,121 @@ public static IServiceCollection AddAppwriteClientForServer(this IServiceCollect
var policyOptions = new ResiliencePolicyOptions();
configureResiliencePolicy?.Invoke(policyOptions);
- var customRefitSettings = AddSerializationConfigToRefitSettings(refitSettings);
-
services.AddKeyedSingleton("Client", new Config(endpoint, projectId));
+
+ services.AddSingleton>(sp =>
+ CreateResiliencePolicy(sp.GetRequiredService>(), policyOptions));
+
services.AddTransient();
- services.AddRefitClient(customRefitSettings)
- .ConfigureHttpClient(x => ConfigureHttpClient(x, endpoint))
- .AddHttpMessageHandler()
- .AddHttpMessageHandler(() => new PolicyHttpMessageHandler(GetRetryPolicy(services, policyOptions)))
- .AddHttpMessageHandler(() => new PolicyHttpMessageHandler(GetCircuitBreakerPolicy(services, policyOptions)))
- .ConfigurePrimaryHttpMessageHandler(ConfigurePrimaryHttpMessageHandler);
+ var customRefitSettings = AddSerializationConfigToRefitSettings(refitSettings);
- services.AddRefitClient(customRefitSettings)
- .ConfigureHttpClient(x => ConfigureHttpClient(x, endpoint))
- .AddHttpMessageHandler()
- .AddHttpMessageHandler(() => new PolicyHttpMessageHandler(GetRetryPolicy(services, policyOptions)))
- .AddHttpMessageHandler(() => new PolicyHttpMessageHandler(GetCircuitBreakerPolicy(services, policyOptions)))
- .ConfigurePrimaryHttpMessageHandler(ConfigurePrimaryHttpMessageHandler);
+ // Register API clients
+ RegisterRefitClient(services, customRefitSettings, endpoint, false);
+ RegisterRefitClient(services, customRefitSettings, endpoint, false);
+ RegisterRefitClient(services, customRefitSettings, endpoint, false);
+
+ // Register business logic clients
+ RegisterBusinessClients(services, false);
- services.AddRefitClient(customRefitSettings)
- .ConfigureHttpClient(x => ConfigureHttpClient(x, endpoint))
+ return services;
+ }
+
+ private static void RegisterRefitClient(IServiceCollection services, RefitSettings refitSettings,
+ string endpoint, bool includeSessionHandler)
+ where T : class
+ {
+ var builder = services.AddRefitClient(refitSettings)
+ .ConfigureHttpClient(client =>
+ {
+ client.BaseAddress = new Uri(endpoint);
+ client.DefaultRequestHeaders.UserAgent.ParseAdd(BuildUserAgent());
+ client.Timeout = TimeSpan.FromSeconds(30);
+ })
.AddHttpMessageHandler()
- .AddHttpMessageHandler(() => new PolicyHttpMessageHandler(GetRetryPolicy(services, policyOptions)))
- .AddHttpMessageHandler(() => new PolicyHttpMessageHandler(GetCircuitBreakerPolicy(services, policyOptions)))
- .ConfigurePrimaryHttpMessageHandler(ConfigurePrimaryHttpMessageHandler);
+ .AddPolicyHandler((sp, _) => sp.GetRequiredService>());
- services.AddScoped(sp =>
+ if (includeSessionHandler)
+ {
+ builder.AddHttpMessageHandler();
+ }
+ else
+ {
+ builder.ConfigurePrimaryHttpMessageHandler(ConfigurePrimaryHttpMessageHandler);
+ }
+ }
+
+ private static void RegisterBusinessClients(IServiceCollection services, bool asSingleton)
+ {
+ if (asSingleton)
+ {
+ RegisterSingletonClients(services);
+ }
+ else
+ {
+ RegisterScopedClients(services);
+ }
+ }
+
+ private static void RegisterSingletonClients(IServiceCollection services)
+ {
+ services.AddSingleton(sp =>
{
var api = sp.GetRequiredService();
var config = sp.GetRequiredKeyedService("Client");
return new ClientAccountClient(api, config);
});
- services.AddScoped(sp =>
+
+ services.AddSingleton(sp =>
{
var api = sp.GetRequiredService();
var config = sp.GetRequiredKeyedService("Client");
return new ClientTeamsClient(api, config);
});
- services.AddScoped(sp =>
+
+ services.AddSingleton(sp =>
{
var api = sp.GetRequiredService();
return new ClientDatabasesClient(api);
});
- services.AddScoped();
- return services;
+ services.AddSingleton();
+ services.AddSingleton(x => new Lazy(() =>
+ x.GetRequiredService()));
}
- [ExcludeFromCodeCoverage]
- private static void ConfigurePrimaryHttpMessageHandler(HttpMessageHandler messageHandler, IServiceProvider serviceProvider)
+ private static void RegisterScopedClients(IServiceCollection services)
{
- if (messageHandler is HttpClientHandler clientHandler)
+ services.AddScoped(sp =>
{
- clientHandler.UseCookies = false;
- }
- }
+ var api = sp.GetRequiredService();
+ var config = sp.GetRequiredKeyedService("Client");
+ return new ClientAccountClient(api, config);
+ });
- private static void ConfigureHttpClient(HttpClient client, string endpoint)
- {
- client.BaseAddress = new Uri(endpoint);
- client.DefaultRequestHeaders.UserAgent.ParseAdd(BuildUserAgent());
+ services.AddScoped(sp =>
+ {
+ var api = sp.GetRequiredService();
+ var config = sp.GetRequiredKeyedService("Client");
+ return new ClientTeamsClient(api, config);
+ });
+
+ services.AddScoped(sp =>
+ {
+ var api = sp.GetRequiredService();
+ return new ClientDatabasesClient(api);
+ });
+
+ services.AddScoped();
}
- [ExcludeFromCodeCoverage]
- private static IAsyncPolicy GetRetryPolicy(IServiceCollection services, ResiliencePolicyOptions options)
+ private static IAsyncPolicy CreateResiliencePolicy(ILogger logger, ResiliencePolicyOptions options)
{
if (options.DisableResilience)
{
return Policy.NoOpAsync();
}
- var serviceProvider = services.BuildServiceProvider();
- var logger = serviceProvider.GetRequiredService>();
-
return HttpPolicyExtensions
.HandleTransientHttpError()
.Or()
@@ -186,45 +196,31 @@ private static IAsyncPolicy GetRetryPolicy(IServiceColle
onRetry: (exception, timeSpan, retryCount, context) =>
{
logger.LogWarning(exception.Exception,
- "Retry {RetryCount} for {Service} after {Seconds} seconds due to: {Message}",
+ "Retry {RetryCount} after {Seconds} seconds due to: {Message}",
retryCount,
- typeof(T).Name,
timeSpan.TotalSeconds,
exception.Exception.Message);
- });
+ })
+ .WrapAsync(HttpPolicyExtensions
+ .HandleTransientHttpError()
+ .CircuitBreakerAsync(options.CircuitBreakerThreshold, TimeSpan.FromSeconds(options.CircuitBreakerDurationSeconds),
+ (exception, duration) =>
+ {
+ logger.LogError(exception.Exception,
+ "Circuit breaker opened for {Seconds} seconds due to: {Message}",
+ duration.TotalSeconds,
+ exception.Exception.Message);
+ },
+ () => logger.LogInformation("Circuit breaker reset")));
}
[ExcludeFromCodeCoverage]
- private static IAsyncPolicy GetCircuitBreakerPolicy(IServiceCollection services, ResiliencePolicyOptions options)
+ private static void ConfigurePrimaryHttpMessageHandler(HttpMessageHandler messageHandler, IServiceProvider serviceProvider)
{
- if (options.DisableResilience)
+ if (messageHandler is HttpClientHandler clientHandler)
{
- return Policy.NoOpAsync();
+ clientHandler.UseCookies = false;
}
-
- var serviceProvider = services.BuildServiceProvider();
- var logger = serviceProvider.GetRequiredService>();
-
- return HttpPolicyExtensions
- .HandleTransientHttpError()
- .CircuitBreakerAsync(
- handledEventsAllowedBeforeBreaking: options.CircuitBreakerThreshold,
- durationOfBreak: TimeSpan.FromSeconds(options.CircuitBreakerDurationSeconds),
- onBreak: (exception, duration) =>
- {
- logger.LogError(
- exception.Exception,
- "Circuit breaker for {Service} opened for {Seconds} seconds due to: {Message}",
- typeof(T).Name,
- duration.TotalSeconds,
- exception.Exception.Message);
- },
- onReset: () =>
- {
- logger.LogInformation(
- "Circuit breaker for {Service} reset",
- typeof(T).Name);
- });
}
private static RefitSettings AddSerializationConfigToRefitSettings(RefitSettings? refitSettings)
@@ -256,4 +252,7 @@ private static string BuildUserAgent()
return $"PinguAppsAppwriteDotNetClientSdk/{Constants.Version} (.NET/{dotnetVersion}; {RuntimeInformation.OSDescription.Trim()})";
}
+
+ // Small helper class to avoid logger type conflicts
+ internal class ResiliencePolicy { }
}
diff --git a/src/PinguApps.Appwrite.Server/ServiceCollectionExtensions.cs b/src/PinguApps.Appwrite.Server/ServiceCollectionExtensions.cs
index 84bd9fe8..906482a2 100644
--- a/src/PinguApps.Appwrite.Server/ServiceCollectionExtensions.cs
+++ b/src/PinguApps.Appwrite.Server/ServiceCollectionExtensions.cs
@@ -5,7 +5,6 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Http;
using Microsoft.Extensions.Logging;
using PinguApps.Appwrite.Server.Clients;
using PinguApps.Appwrite.Server.Handlers;
@@ -19,7 +18,7 @@
namespace PinguApps.Appwrite.Server;
///
-/// Provides extenions to IServiceCollection, to enable adding the SDK to your DI container
+/// Provides extensions to IServiceCollection, to enable adding the SDK to your DI container
///
public static class ServiceCollectionExtensions
{
@@ -39,93 +38,78 @@ public static IServiceCollection AddAppwriteServer(this IServiceCollection servi
var policyOptions = new ResiliencePolicyOptions();
configureResiliencePolicy?.Invoke(policyOptions);
+ services.AddSingleton>(sp =>
+ CreateResiliencePolicy(sp.GetRequiredService>(), policyOptions));
+
var customRefitSettings = AddSerializationConfigToRefitSettings(refitSettings);
services.AddKeyedSingleton("Server", new Config(endpoint, projectId, apiKey));
- services.AddTransient();
+ services.AddScoped();
- services.AddRefitClient(customRefitSettings)
- .ConfigureHttpClient(x => ConfigureHttpClient(x, endpoint))
- .AddHttpMessageHandler()
- .AddHttpMessageHandler(() => new PolicyHttpMessageHandler(GetRetryPolicy(services, policyOptions)))
- .AddHttpMessageHandler(() => new PolicyHttpMessageHandler(GetCircuitBreakerPolicy(services, policyOptions)))
- .ConfigurePrimaryHttpMessageHandler(ConfigurePrimaryHttpMessageHandler);
+ RegisterRefitClient(services, customRefitSettings, endpoint);
+ RegisterRefitClient(services, customRefitSettings, endpoint);
+ RegisterRefitClient(services, customRefitSettings, endpoint);
+ RegisterRefitClient(services, customRefitSettings, endpoint);
- services.AddRefitClient(customRefitSettings)
- .ConfigureHttpClient(x => ConfigureHttpClient(x, endpoint))
- .AddHttpMessageHandler()
- .AddHttpMessageHandler(() => new PolicyHttpMessageHandler(GetRetryPolicy(services, policyOptions)))
- .AddHttpMessageHandler(() => new PolicyHttpMessageHandler(GetCircuitBreakerPolicy(services, policyOptions)))
- .ConfigurePrimaryHttpMessageHandler(ConfigurePrimaryHttpMessageHandler);
+ RegisterBusinessClients(services);
- services.AddRefitClient(customRefitSettings)
- .ConfigureHttpClient(x => ConfigureHttpClient(x, endpoint))
- .AddHttpMessageHandler()
- .AddHttpMessageHandler(() => new PolicyHttpMessageHandler(GetRetryPolicy(services, policyOptions)))
- .AddHttpMessageHandler(() => new PolicyHttpMessageHandler(GetCircuitBreakerPolicy(services, policyOptions)))
- .ConfigurePrimaryHttpMessageHandler(ConfigurePrimaryHttpMessageHandler);
+ return services;
+ }
- services.AddRefitClient(customRefitSettings)
- .ConfigureHttpClient(x => ConfigureHttpClient(x, endpoint))
+ private static void RegisterRefitClient(IServiceCollection services, RefitSettings refitSettings, string endpoint)
+ where T : class
+ {
+ services.AddRefitClient(refitSettings)
+ .ConfigureHttpClient((sp, client) =>
+ {
+ client.BaseAddress = new Uri(endpoint);
+ client.Timeout = TimeSpan.FromSeconds(30);
+ client.DefaultRequestHeaders.UserAgent.ParseAdd(BuildUserAgent());
+ })
+ .ConfigurePrimaryHttpMessageHandler(ConfigurePrimaryHttpMessageHandler)
.AddHttpMessageHandler()
- .AddHttpMessageHandler(() => new PolicyHttpMessageHandler(GetRetryPolicy(services, policyOptions)))
- .AddHttpMessageHandler(() => new PolicyHttpMessageHandler(GetCircuitBreakerPolicy(services, policyOptions)))
- .ConfigurePrimaryHttpMessageHandler(ConfigurePrimaryHttpMessageHandler);
+ .AddPolicyHandler((sp, _) => sp.GetRequiredService>());
+ }
+ private static void RegisterBusinessClients(IServiceCollection services)
+ {
services.AddSingleton(sp =>
{
var api = sp.GetRequiredService();
var config = sp.GetRequiredKeyedService("Server");
return new ServerAccountClient(api, config);
});
+
services.AddSingleton(sp =>
{
var api = sp.GetRequiredService();
var config = sp.GetRequiredKeyedService("Server");
return new ServerUsersClient(api, config);
});
+
services.AddSingleton(sp =>
{
var api = sp.GetRequiredService();
var config = sp.GetRequiredKeyedService("Server");
return new ServerTeamsClient(api, config);
});
+
services.AddSingleton(sp =>
{
var api = sp.GetRequiredService();
return new ServerDatabasesClient(api);
});
- services.AddSingleton();
-
- return services;
- }
-
- [ExcludeFromCodeCoverage]
- private static void ConfigurePrimaryHttpMessageHandler(HttpMessageHandler messageHandler, IServiceProvider serviceProvider)
- {
- if (messageHandler is HttpClientHandler clientHandler)
- {
- clientHandler.UseCookies = false;
- }
- }
- private static void ConfigureHttpClient(HttpClient client, string endpoint)
- {
- client.BaseAddress = new Uri(endpoint);
- client.DefaultRequestHeaders.UserAgent.ParseAdd(BuildUserAgent());
+ services.AddSingleton();
}
- [ExcludeFromCodeCoverage]
- private static IAsyncPolicy GetRetryPolicy(IServiceCollection services, ResiliencePolicyOptions options)
+ private static IAsyncPolicy CreateResiliencePolicy(ILogger logger, ResiliencePolicyOptions options)
{
if (options.DisableResilience)
{
return Policy.NoOpAsync();
}
- var serviceProvider = services.BuildServiceProvider();
- var logger = serviceProvider.GetRequiredService>();
-
return HttpPolicyExtensions
.HandleTransientHttpError()
.Or()
@@ -135,45 +119,31 @@ private static IAsyncPolicy GetRetryPolicy(IServiceColle
onRetry: (exception, timeSpan, retryCount, context) =>
{
logger.LogWarning(exception.Exception,
- "Retry {RetryCount} for {Service} after {Seconds} seconds due to: {Message}",
+ "Retry {RetryCount} after {Seconds} seconds due to: {Message}",
retryCount,
- typeof(T).Name,
timeSpan.TotalSeconds,
exception.Exception.Message);
- });
+ })
+ .WrapAsync(HttpPolicyExtensions
+ .HandleTransientHttpError()
+ .CircuitBreakerAsync(options.CircuitBreakerThreshold, TimeSpan.FromSeconds(options.CircuitBreakerDurationSeconds),
+ (exception, duration) =>
+ {
+ logger.LogError(exception.Exception,
+ "Circuit breaker opened for {Seconds} seconds due to: {Message}",
+ duration.TotalSeconds,
+ exception.Exception.Message);
+ },
+ () => logger.LogInformation("Circuit breaker reset")));
}
[ExcludeFromCodeCoverage]
- private static IAsyncPolicy GetCircuitBreakerPolicy(IServiceCollection services, ResiliencePolicyOptions options)
+ private static void ConfigurePrimaryHttpMessageHandler(HttpMessageHandler messageHandler, IServiceProvider serviceProvider)
{
- if (options.DisableResilience)
+ if (messageHandler is HttpClientHandler clientHandler)
{
- return Policy.NoOpAsync();
+ clientHandler.UseCookies = false;
}
-
- var serviceProvider = services.BuildServiceProvider();
- var logger = serviceProvider.GetRequiredService>();
-
- return HttpPolicyExtensions
- .HandleTransientHttpError()
- .CircuitBreakerAsync(
- handledEventsAllowedBeforeBreaking: options.CircuitBreakerThreshold,
- durationOfBreak: TimeSpan.FromSeconds(options.CircuitBreakerDurationSeconds),
- onBreak: (exception, duration) =>
- {
- logger.LogError(
- exception.Exception,
- "Circuit breaker for {Service} opened for {Seconds} seconds due to: {Message}",
- typeof(T).Name,
- duration.TotalSeconds,
- exception.Exception.Message);
- },
- onReset: () =>
- {
- logger.LogInformation(
- "Circuit breaker for {Service} reset",
- typeof(T).Name);
- });
}
private static RefitSettings AddSerializationConfigToRefitSettings(RefitSettings? refitSettings)
@@ -205,4 +175,7 @@ private static string BuildUserAgent()
return $"PinguAppsAppwriteDotNetServerSdk/{Constants.Version} (.NET/{dotnetVersion}; {RuntimeInformation.OSDescription.Trim()})";
}
+
+ // Small helper class to avoid logger type conflicts
+ internal class ResiliencePolicy { }
}
diff --git a/src/PinguApps.Appwrite.Shared/Constants.cs b/src/PinguApps.Appwrite.Shared/Constants.cs
index 0587666f..e5a378df 100644
--- a/src/PinguApps.Appwrite.Shared/Constants.cs
+++ b/src/PinguApps.Appwrite.Shared/Constants.cs
@@ -1,5 +1,5 @@
namespace PinguApps.Appwrite.Shared;
public static class Constants
{
- public const string Version = "2.0.4";
+ public const string Version = "2.0.5";
}