From 9e1bee9ea39570522fc3b206a90f8a6354dc5547 Mon Sep 17 00:00:00 2001 From: TekH Date: Thu, 21 May 2026 08:32:04 +0200 Subject: [PATCH] Refactor and enhance static ReCClient configuration Introduced a new `BuildStaticClient(Action)` method for flexible and detailed static `IServiceProvider` configuration. Added the `StaticBuildConfiguration` class to encapsulate optional settings like `BaseAddress`, `ConfigureClient`, `Logger`, and more. Refactored existing `BuildStaticClient` overloads to use the new method, ensuring consistency and reducing duplication. Added support for optional `ILogger` instances and improved validation to enforce proper configuration. Marked existing `BuildStaticClient` methods as obsolete, recommending the new method. Enhanced thread-safety using `Interlocked.CompareExchange`. Updated XML documentation and added conditional compilation for `NETFRAMEWORK` compatibility. These changes improve maintainability, usability, and alignment with modern .NET practices. --- src/ReC.Client/ReCClient.cs | 79 ++++++++++++++++++---- src/ReC.Client/StaticBuildConfiguration.cs | 63 +++++++++++++++++ 2 files changed, 130 insertions(+), 12 deletions(-) create mode 100644 src/ReC.Client/StaticBuildConfiguration.cs diff --git a/src/ReC.Client/ReCClient.cs b/src/ReC.Client/ReCClient.cs index 51aa917..fc537e9 100644 --- a/src/ReC.Client/ReCClient.cs +++ b/src/ReC.Client/ReCClient.cs @@ -101,6 +101,53 @@ namespace ReC.Client return services.BuildServiceProvider(); }, System.Threading.LazyThreadSafetyMode.ExecutionAndPublication); + /// + /// Configures and builds the static for creating instances. + /// + /// + /// This method should only be called once during application startup. The underlying + /// is created lazily and thread-safely on first access via . + /// + /// Callback that populates a instance. + /// Thrown when is null. + /// Thrown when neither nor is set, when both are set, or when the static provider has already been built. + [Obsolete("Use a local service collection instead of the static provider.")] + public static void BuildStaticClient(Action configure) + { + if (configure == null) + throw new ArgumentNullException(nameof(configure)); + + var cfg = new StaticBuildConfiguration(); + configure(cfg); + + var hasBaseAddress = !string.IsNullOrWhiteSpace(cfg.BaseAddress); + var hasConfigureClient = cfg.ConfigureClient != null; + + if (!hasBaseAddress && !hasConfigureClient) + throw new InvalidOperationException( + $"Either {nameof(StaticBuildConfiguration.BaseAddress)} or {nameof(StaticBuildConfiguration.ConfigureClient)} must be set on {nameof(StaticBuildConfiguration)}."); + + if (hasBaseAddress && hasConfigureClient) + throw new InvalidOperationException( + $"{nameof(StaticBuildConfiguration.BaseAddress)} and {nameof(StaticBuildConfiguration.ConfigureClient)} are mutually exclusive on {nameof(StaticBuildConfiguration)}."); + + Action register = services => + { + if (hasBaseAddress) + services.AddRecClient(cfg.BaseAddress, cfg.ConfigureOptions); + else + services.AddRecClient(cfg.ConfigureClient, cfg.ConfigureOptions); + + if (cfg.Logger != null) + services.AddSingleton(cfg.Logger); + + cfg.ConfigureServices?.Invoke(services); + }; + + if (System.Threading.Interlocked.CompareExchange(ref _staticConfigure, register, null) != null) + throw new InvalidOperationException("Static Provider is already built."); + } + /// /// Configures and builds the static for creating instances. /// @@ -110,17 +157,21 @@ namespace ReC.Client /// /// The base URI of the ReC API. /// An optional callback to configure . + /// An optional instance to be used by the . When provided, it is registered as a singleton in the internal service collection. /// Thrown if the static provider has already been built. - [Obsolete("Use a local service collection instead of the static provider.")] + [Obsolete("Use BuildStaticClient(Action) instead.")] #if NETFRAMEWORK - public static void BuildStaticClient(string apiUri, Action configureOptions = null) + public static void BuildStaticClient(string apiUri, Action configureOptions = null, ILogger logger = null) #else - public static void BuildStaticClient(string apiUri, Action? configureOptions = null) + public static void BuildStaticClient(string apiUri, Action? configureOptions = null, ILogger? logger = null) #endif { - Action configure = services => services.AddRecClient(apiUri, configureOptions); - if (System.Threading.Interlocked.CompareExchange(ref _staticConfigure, configure, null) != null) - throw new InvalidOperationException("Static Provider is already built."); + BuildStaticClient(cfg => + { + cfg.BaseAddress = apiUri; + cfg.ConfigureOptions = configureOptions; + cfg.Logger = logger; + }); } /// @@ -132,17 +183,21 @@ namespace ReC.Client /// /// An action to configure the . /// An optional callback to configure . + /// An optional instance to be used by the . When provided, it is registered as a singleton in the internal service collection. /// Thrown if the static provider has already been built. - [Obsolete("Use a local service collection instead of the static provider.")] + [Obsolete("Use BuildStaticClient(Action) instead.")] #if NETFRAMEWORK - public static void BuildStaticClient(Action configureClient, Action configureOptions = null) + public static void BuildStaticClient(Action configureClient, Action configureOptions = null, ILogger logger = null) #else - public static void BuildStaticClient(Action configureClient, Action? configureOptions = null) + public static void BuildStaticClient(Action configureClient, Action? configureOptions = null, ILogger? logger = null) #endif { - Action configure = services => services.AddRecClient(configureClient, configureOptions); - if (System.Threading.Interlocked.CompareExchange(ref _staticConfigure, configure, null) != null) - throw new InvalidOperationException("Static Provider is already built."); + BuildStaticClient(cfg => + { + cfg.ConfigureClient = configureClient; + cfg.ConfigureOptions = configureOptions; + cfg.Logger = logger; + }); } /// diff --git a/src/ReC.Client/StaticBuildConfiguration.cs b/src/ReC.Client/StaticBuildConfiguration.cs new file mode 100644 index 0000000..ec898c0 --- /dev/null +++ b/src/ReC.Client/StaticBuildConfiguration.cs @@ -0,0 +1,63 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using System; +using System.Net.Http; + +namespace ReC.Client +{ + /// + /// Configuration object for . + /// Groups all optional settings for the static bootstrap path. + /// + /// + /// Either or must be set; setting both at the same time is not allowed. + /// + public class StaticBuildConfiguration + { + /// + /// Base URI of the ReC API. Mutually exclusive with . + /// +#if NETFRAMEWORK + public string BaseAddress { get; set; } +#else + public string? BaseAddress { get; set; } +#endif + + /// + /// Callback that configures the underlying . Mutually exclusive with . + /// +#if NETFRAMEWORK + public Action ConfigureClient { get; set; } +#else + public Action? ConfigureClient { get; set; } +#endif + + /// + /// Optional callback to configure . + /// +#if NETFRAMEWORK + public Action ConfigureOptions { get; set; } +#else + public Action? ConfigureOptions { get; set; } +#endif + + /// + /// Optional logger instance to be registered as a singleton in the internal service collection. + /// +#if NETFRAMEWORK + public ILogger Logger { get; set; } +#else + public ILogger? Logger { get; set; } +#endif + + /// + /// Optional callback for additional service registrations on the internal + /// (e.g. services.AddLogging(...) or custom dependencies). + /// +#if NETFRAMEWORK + public Action ConfigureServices { get; set; } +#else + public Action? ConfigureServices { get; set; } +#endif + } +}